diff --git a/AlienBlue-Info.plist b/AlienBlue-Info.plist new file mode 100755 index 0000000..a3d8a6d --- /dev/null +++ b/AlienBlue-Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleDisplayName + Alien Blue + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + Icon.png + CFBundleIdentifier + com.designshed.alienblue + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 1.0.1 + LSRequiresIPhoneOS + + NSMainNibFile + MainWindow + SBUsesNetwork + TRUE + UIPrerenderedIcon + + UIStatusBarStyle + UIStatusBarStyleBlackOpaque + + diff --git a/AlienBlue.xcodeproj/project.pbxproj b/AlienBlue.xcodeproj/project.pbxproj new file mode 100755 index 0000000..155cca2 --- /dev/null +++ b/AlienBlue.xcodeproj/project.pbxproj @@ -0,0 +1,753 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXBuildFile section */ + 1D3623260D0F684500981E51 /* AlienBlueAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D3623250D0F684500981E51 /* AlienBlueAppDelegate.m */; }; + 1D60589B0D05DD56006BFB54 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; }; + 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; + 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; }; + 28216C970DB411BC00E5133A /* BrowserViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 28216C960DB411BC00E5133A /* BrowserViewController.m */; }; + 288765080DF74369002DB57D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 288765070DF74369002DB57D /* CoreGraphics.framework */; }; + 28AD73880D9D96C1002E5188 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28AD73870D9D96C1002E5188 /* MainWindow.xib */; }; + 4701FCA31180136900FE9A76 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = 4701FCA21180136900FE9A76 /* Default.png */; }; + 47294ED5117C9092001A8EAF /* SettingsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 47294ED4117C9092001A8EAF /* SettingsView.xib */; }; + 472B189B11784586000204D7 /* MessagesTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 472B189A11784586000204D7 /* MessagesTableViewController.m */; }; + 472B193C11787DEA000204D7 /* MessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 472B193B11787DEA000204D7 /* MessageCell.m */; }; + 472B19E311788D41000204D7 /* mail-gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 472B19E111788D41000204D7 /* mail-gray.png */; }; + 472B1A1F11789729000204D7 /* mail-red-thin.png in Resources */ = {isa = PBXBuildFile; fileRef = 472B1A1E11789729000204D7 /* mail-red-thin.png */; }; + 472B1A221178985C000204D7 /* gear-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 472B1A211178985C000204D7 /* gear-icon.png */; }; + 472B1A2411789895000204D7 /* mail-red.png in Resources */ = {isa = PBXBuildFile; fileRef = 472B1A2311789895000204D7 /* mail-red.png */; }; + 4731919D115F2F0D00FDA23A /* PostCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4731919C115F2F0D00FDA23A /* PostCell.m */; }; + 4731935D115F7DEC00FDA23A /* black-gradient.png in Resources */ = {isa = PBXBuildFile; fileRef = 4731935C115F7DEC00FDA23A /* black-gradient.png */; }; + 4731DC3211720923006806D0 /* button-background.png in Resources */ = {isa = PBXBuildFile; fileRef = 4731DC3111720923006806D0 /* button-background.png */; }; + 4740AF25118C031200A7FA2A /* upgrade-to-pro-label.png in Resources */ = {isa = PBXBuildFile; fileRef = 4740AF24118C031200A7FA2A /* upgrade-to-pro-label.png */; }; + 474F4C0711680F4B005F1BAD /* RedditAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = 474F4C0611680F4B005F1BAD /* RedditAPI.m */; }; + 474F4DA91168A8A1005F1BAD /* SettingsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 474F4DA81168A8A1005F1BAD /* SettingsTableViewController.m */; }; + 474F4FFB11698A78005F1BAD /* PostsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 474F4FFA11698A78005F1BAD /* PostsView.xib */; }; + 4760A9CC1165875C00E9BB16 /* BrowserView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4760A9CB1165875C00E9BB16 /* BrowserView.xib */; }; + 4760ADAC1166F64600E9BB16 /* vote-up.png in Resources */ = {isa = PBXBuildFile; fileRef = 4760ADA61166F64600E9BB16 /* vote-up.png */; }; + 4760ADAD1166F64600E9BB16 /* vote-down-selected.png in Resources */ = {isa = PBXBuildFile; fileRef = 4760ADA71166F64600E9BB16 /* vote-down-selected.png */; }; + 4760ADAE1166F64600E9BB16 /* vote-down.png in Resources */ = {isa = PBXBuildFile; fileRef = 4760ADA81166F64600E9BB16 /* vote-down.png */; }; + 4760ADAF1166F64600E9BB16 /* vote-up-selected.png in Resources */ = {isa = PBXBuildFile; fileRef = 4760ADA91166F64600E9BB16 /* vote-up-selected.png */; }; + 4760ADB01166F64600E9BB16 /* reply.png in Resources */ = {isa = PBXBuildFile; fileRef = 4760ADAA1166F64600E9BB16 /* reply.png */; }; + 4760ADB11166F64600E9BB16 /* reply-selected.png in Resources */ = {isa = PBXBuildFile; fileRef = 4760ADAB1166F64600E9BB16 /* reply-selected.png */; }; + 477ADA8E11759A4000342ECD /* level-arrows.png in Resources */ = {isa = PBXBuildFile; fileRef = 477ADA8D11759A4000342ECD /* level-arrows.png */; }; + 47BC8560115EEC1700A4362E /* PostsTableControllerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 47BC855F115EEC1700A4362E /* PostsTableControllerView.m */; }; + 47BFA8B5117CA2B900A2FD56 /* top-scroll.png in Resources */ = {isa = PBXBuildFile; fileRef = 47BFA8B3117CA2B900A2FD56 /* top-scroll.png */; }; + 47BFA8B6117CA2B900A2FD56 /* bottom-scroll.png in Resources */ = {isa = PBXBuildFile; fileRef = 47BFA8B4117CA2B900A2FD56 /* bottom-scroll.png */; }; + 47BFAA5F117DC41700A2FD56 /* IconBig.png in Resources */ = {isa = PBXBuildFile; fileRef = 47BFAA5E117DC41700A2FD56 /* IconBig.png */; }; + 47BFAC90117E837400A2FD56 /* comment-parent-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 47BFAC8F117E837400A2FD56 /* comment-parent-icon.png */; }; + 47BFAE49117EC19600A2FD56 /* readability-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 47BFAE47117EC19600A2FD56 /* readability-icon.png */; }; + 47BFAE4A117EC19600A2FD56 /* readability-back-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 47BFAE48117EC19600A2FD56 /* readability-back-icon.png */; }; + 47BFAE53117EC2B800A2FD56 /* vote-up-small.png in Resources */ = {isa = PBXBuildFile; fileRef = 47BFAE4E117EC2B800A2FD56 /* vote-up-small.png */; }; + 47BFAE54117EC2B800A2FD56 /* vote-down-small.png in Resources */ = {isa = PBXBuildFile; fileRef = 47BFAE4F117EC2B800A2FD56 /* vote-down-small.png */; }; + 47BFAE55117EC2B800A2FD56 /* vote-down-selected-small.png in Resources */ = {isa = PBXBuildFile; fileRef = 47BFAE50117EC2B800A2FD56 /* vote-down-selected-small.png */; }; + 47BFAE56117EC2B800A2FD56 /* load-all-images.png in Resources */ = {isa = PBXBuildFile; fileRef = 47BFAE51117EC2B800A2FD56 /* load-all-images.png */; }; + 47BFAE57117EC2B800A2FD56 /* vote-up-selected-small.png in Resources */ = {isa = PBXBuildFile; fileRef = 47BFAE52117EC2B800A2FD56 /* vote-up-selected-small.png */; }; + 47BFAE70117EC69C00A2FD56 /* edit-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 47BFAE6F117EC69C00A2FD56 /* edit-icon.png */; }; + 47C4937C118669200095DB82 /* hide-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 47C49379118669200095DB82 /* hide-icon.png */; }; + 47C4937D118669200095DB82 /* star-selected-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 47C4937A118669200095DB82 /* star-selected-icon.png */; }; + 47C4937E118669200095DB82 /* star-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 47C4937B118669200095DB82 /* star-icon.png */; }; + 47C86401118A9B2E00DF5F4E /* pro-feature-label.png in Resources */ = {isa = PBXBuildFile; fileRef = 47C86400118A9B2E00DF5F4E /* pro-feature-label.png */; }; + 47C86403118A9B3900DF5F4E /* hide-selected-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 47C86402118A9B3900DF5F4E /* hide-selected-icon.png */; }; + 47DEF6D711606A55000976EA /* CommentsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 47DEF6D611606A55000976EA /* CommentsTableViewController.m */; }; + 47DEF6DB11606A8F000976EA /* CommentsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 47DEF6DA11606A8F000976EA /* CommentsView.xib */; }; + 47DEF79311607D68000976EA /* NavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 47DEF79211607D68000976EA /* NavigationController.m */; }; + 47DEF9281160BB8A000976EA /* CommentCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 47DEF9271160BB8A000976EA /* CommentCell.m */; }; + 47DFAF9311816BBB0036AC43 /* CommentCellView.m in Sources */ = {isa = PBXBuildFile; fileRef = 47DFAF9211816BBB0036AC43 /* CommentCellView.m */; }; + 47DFAFA911816D3F0036AC43 /* CommentWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 47DFAFA811816D3F0036AC43 /* CommentWrapper.m */; }; + 47E1C5A51189520E00B65F7E /* video-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 47E1C5A21189520E00B65F7E /* video-icon.png */; }; + 47E1C5A61189520E00B65F7E /* image-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 47E1C5A31189520E00B65F7E /* image-icon.png */; }; + 47E1C5A71189520E00B65F7E /* article-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 47E1C5A41189520E00B65F7E /* article-icon.png */; }; + 7E03F0361193D028008D3917 /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 7E03F0351193D028008D3917 /* Icon.png */; }; + 7E130DAD11919CCB00FB7009 /* Resources.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E130DAC11919CCB00FB7009 /* Resources.m */; }; + 7E130E631191A78800FB7009 /* button-background-dark.png in Resources */ = {isa = PBXBuildFile; fileRef = 7E130E621191A78800FB7009 /* button-background-dark.png */; }; + 7E382AFF1190F718000F94DD /* DefaultBGBlack.png in Resources */ = {isa = PBXBuildFile; fileRef = 7E382AFE1190F718000F94DD /* DefaultBGBlack.png */; }; + 7E60872411944684008AA06E /* tips-sheet.png in Resources */ = {isa = PBXBuildFile; fileRef = 7E60872311944684008AA06E /* tips-sheet.png */; }; + 7E6087761194505C008AA06E /* info-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 7E6087751194505C008AA06E /* info-icon.png */; }; + 7E61C94211A79C9C00B77ADB /* MessagesView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7E61C94111A79C9C00B77ADB /* MessagesView.xib */; }; + 7E61C94411A79CAA00B77ADB /* CustomMessageCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7E61C94311A79CAA00B77ADB /* CustomMessageCell.xib */; }; + 7E926FD8118888E900FC2037 /* MKStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E926FCF118888E800FC2037 /* MKStoreManager.m */; }; + 7E926FD9118888E900FC2037 /* MKStoreObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E926FD1118888E800FC2037 /* MKStoreObserver.m */; }; + 7E926FDA118888E900FC2037 /* readme.txt in Resources */ = {isa = PBXBuildFile; fileRef = 7E926FD2118888E800FC2037 /* readme.txt */; }; + 7E926FDB118888E900FC2037 /* AddDevice.html in Resources */ = {isa = PBXBuildFile; fileRef = 7E926FD4118888E900FC2037 /* AddDevice.html */; }; + 7E926FDC118888E900FC2037 /* DBSetup.sql in Resources */ = {isa = PBXBuildFile; fileRef = 7E926FD5118888E900FC2037 /* DBSetup.sql */; }; + 7E926FDD118888E900FC2037 /* featureCheck.php in Resources */ = {isa = PBXBuildFile; fileRef = 7E926FD6118888E900FC2037 /* featureCheck.php */; }; + 7E926FDE118888E900FC2037 /* requestAccess.php in Resources */ = {isa = PBXBuildFile; fileRef = 7E926FD7118888E900FC2037 /* requestAccess.php */; }; + 7E92703111888A7F00FC2037 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E92703011888A7F00FC2037 /* StoreKit.framework */; }; + 7E93E854119A6CA90022A45A /* NSObject+SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E93E849119A6CA90022A45A /* NSObject+SBJSON.m */; }; + 7E93E855119A6CA90022A45A /* NSString+SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E93E84B119A6CA90022A45A /* NSString+SBJSON.m */; }; + 7E93E856119A6CA90022A45A /* SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E93E84D119A6CA90022A45A /* SBJSON.m */; }; + 7E93E857119A6CA90022A45A /* SBJsonBase.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E93E84F119A6CA90022A45A /* SBJsonBase.m */; }; + 7E93E858119A6CA90022A45A /* SBJsonParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E93E851119A6CA90022A45A /* SBJsonParser.m */; }; + 7E93E859119A6CA90022A45A /* SBJsonWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E93E853119A6CA90022A45A /* SBJsonWriter.m */; }; + 7E9E49BB1187F1FE00A3C308 /* DefaultBG.png in Resources */ = {isa = PBXBuildFile; fileRef = 7E9E49BA1187F1FE00A3C308 /* DefaultBG.png */; }; + 7ECD0CFF11A0CA1000B7CFC6 /* ImageCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ECD0CFE11A0CA1000B7CFC6 /* ImageCache.m */; }; + 7ECD0DDC11A0E66C00B7CFC6 /* loading-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 7ECD0DDB11A0E66C00B7CFC6 /* loading-icon.png */; }; + 7ECD6F4411A9460B001081A5 /* delete-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 7ECD6F4311A9460B001081A5 /* delete-icon.png */; }; + 7ECD709011A9778C001081A5 /* fullscreen-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 7ECD708E11A9778C001081A5 /* fullscreen-icon.png */; }; + 7ECD709111A9778C001081A5 /* show-toolbars-button.png in Resources */ = {isa = PBXBuildFile; fileRef = 7ECD708F11A9778C001081A5 /* show-toolbars-button.png */; }; + 7ECFAC2111883F5C00ACDB60 /* posts-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 7ECFAC2011883F5C00ACDB60 /* posts-icon.png */; }; + 7EDC74D9118C30E900C79F25 /* why-go-pro-splash.png in Resources */ = {isa = PBXBuildFile; fileRef = 7EDC74D8118C30E900C79F25 /* why-go-pro-splash.png */; }; + 7EDC74FC118C5B0700C79F25 /* orange-back-button.png in Resources */ = {isa = PBXBuildFile; fileRef = 7EDC74FB118C5B0700C79F25 /* orange-back-button.png */; }; + 7EDF4D27118D3DBD005A7435 /* no-internet-connection.png in Resources */ = {isa = PBXBuildFile; fileRef = 7EDF4D26118D3DBD005A7435 /* no-internet-connection.png */; }; + 7EDF4D40118D45D4005A7435 /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 7EDF4D3E118D45D4005A7435 /* Reachability.m */; }; + 7EDF4D45118D464B005A7435 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7EDF4D44118D464B005A7435 /* SystemConfiguration.framework */; }; + 7EDF4E26118D7D62005A7435 /* more-options-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 7EDF4E25118D7D62005A7435 /* more-options-icon.png */; }; + 7EE1C9F5118D8AC500B4B7B2 /* green-tick-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 7EE1C9F4118D8AC500B4B7B2 /* green-tick-icon.png */; }; + 7EE5370C11AC6DFD006FFF12 /* fullscreen-back-button.png in Resources */ = {isa = PBXBuildFile; fileRef = 7EE5370B11AC6DFD006FFF12 /* fullscreen-back-button.png */; }; + 7EE5372111AC720E006FFF12 /* right-arrow-dim.png in Resources */ = {isa = PBXBuildFile; fileRef = 7EE5372011AC720E006FFF12 /* right-arrow-dim.png */; }; + 7EEDFE4E11ADDA5E008C039B /* README in Resources */ = {isa = PBXBuildFile; fileRef = 7EEDFE4D11ADDA5E008C039B /* README */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 1D3623240D0F684500981E51 /* AlienBlueAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AlienBlueAppDelegate.h; sourceTree = ""; }; + 1D3623250D0F684500981E51 /* AlienBlueAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AlienBlueAppDelegate.m; sourceTree = ""; }; + 1D6058910D05DD3D006BFB54 /* AlienBlue.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AlienBlue.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 28216C950DB411BC00E5133A /* BrowserViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BrowserViewController.h; sourceTree = ""; }; + 28216C960DB411BC00E5133A /* BrowserViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BrowserViewController.m; sourceTree = ""; }; + 288765070DF74369002DB57D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 28A0AB4B0D9B1048005BE974 /* AlienBlue_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AlienBlue_Prefix.pch; sourceTree = ""; }; + 28AD73870D9D96C1002E5188 /* MainWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = MainWindow.xib; path = Xibs/MainWindow.xib; sourceTree = ""; }; + 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 4701FCA21180136900FE9A76 /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = ""; }; + 47294ED4117C9092001A8EAF /* SettingsView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = SettingsView.xib; path = Xibs/SettingsView.xib; sourceTree = ""; }; + 472B189911784586000204D7 /* MessagesTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MessagesTableViewController.h; sourceTree = ""; }; + 472B189A11784586000204D7 /* MessagesTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MessagesTableViewController.m; sourceTree = ""; }; + 472B193A11787DEA000204D7 /* MessageCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MessageCell.h; sourceTree = ""; }; + 472B193B11787DEA000204D7 /* MessageCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MessageCell.m; sourceTree = ""; }; + 472B19E111788D41000204D7 /* mail-gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "mail-gray.png"; path = "Images/mail-gray.png"; sourceTree = ""; }; + 472B1A1E11789729000204D7 /* mail-red-thin.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "mail-red-thin.png"; path = "Images/mail-red-thin.png"; sourceTree = ""; }; + 472B1A211178985C000204D7 /* gear-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "gear-icon.png"; path = "Images/gear-icon.png"; sourceTree = ""; }; + 472B1A2311789895000204D7 /* mail-red.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "mail-red.png"; path = "Images/mail-red.png"; sourceTree = ""; }; + 4731919B115F2F0D00FDA23A /* PostCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PostCell.h; sourceTree = ""; }; + 4731919C115F2F0D00FDA23A /* PostCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PostCell.m; sourceTree = ""; }; + 4731935C115F7DEC00FDA23A /* black-gradient.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "black-gradient.png"; path = "Images/black-gradient.png"; sourceTree = ""; }; + 4731DC3111720923006806D0 /* button-background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "button-background.png"; path = "Images/button-background.png"; sourceTree = ""; }; + 4740AF24118C031200A7FA2A /* upgrade-to-pro-label.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "upgrade-to-pro-label.png"; path = "Images/upgrade-to-pro-label.png"; sourceTree = ""; }; + 474F4C0511680F4B005F1BAD /* RedditAPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RedditAPI.h; sourceTree = ""; }; + 474F4C0611680F4B005F1BAD /* RedditAPI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RedditAPI.m; sourceTree = ""; }; + 474F4DA71168A8A1005F1BAD /* SettingsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsTableViewController.h; sourceTree = ""; }; + 474F4DA81168A8A1005F1BAD /* SettingsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsTableViewController.m; sourceTree = ""; }; + 474F4FFA11698A78005F1BAD /* PostsView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = PostsView.xib; path = Xibs/PostsView.xib; sourceTree = ""; }; + 4760A9CB1165875C00E9BB16 /* BrowserView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = BrowserView.xib; path = Xibs/BrowserView.xib; sourceTree = ""; }; + 4760ADA61166F64600E9BB16 /* vote-up.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "vote-up.png"; path = "Images/vote-up.png"; sourceTree = ""; }; + 4760ADA71166F64600E9BB16 /* vote-down-selected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "vote-down-selected.png"; path = "Images/vote-down-selected.png"; sourceTree = ""; }; + 4760ADA81166F64600E9BB16 /* vote-down.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "vote-down.png"; path = "Images/vote-down.png"; sourceTree = ""; }; + 4760ADA91166F64600E9BB16 /* vote-up-selected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "vote-up-selected.png"; path = "Images/vote-up-selected.png"; sourceTree = ""; }; + 4760ADAA1166F64600E9BB16 /* reply.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = reply.png; path = Images/reply.png; sourceTree = ""; }; + 4760ADAB1166F64600E9BB16 /* reply-selected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "reply-selected.png"; path = "Images/reply-selected.png"; sourceTree = ""; }; + 477ADA8D11759A4000342ECD /* level-arrows.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "level-arrows.png"; path = "Images/level-arrows.png"; sourceTree = ""; }; + 47BC855E115EEC1700A4362E /* PostsTableControllerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PostsTableControllerView.h; sourceTree = ""; }; + 47BC855F115EEC1700A4362E /* PostsTableControllerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PostsTableControllerView.m; sourceTree = ""; }; + 47BFA8B3117CA2B900A2FD56 /* top-scroll.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "top-scroll.png"; path = "Images/top-scroll.png"; sourceTree = ""; }; + 47BFA8B4117CA2B900A2FD56 /* bottom-scroll.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "bottom-scroll.png"; path = "Images/bottom-scroll.png"; sourceTree = ""; }; + 47BFAA5E117DC41700A2FD56 /* IconBig.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = IconBig.png; sourceTree = ""; }; + 47BFAC8F117E837400A2FD56 /* comment-parent-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "comment-parent-icon.png"; path = "Images/comment-parent-icon.png"; sourceTree = ""; }; + 47BFAE47117EC19600A2FD56 /* readability-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "readability-icon.png"; path = "Images/readability-icon.png"; sourceTree = ""; }; + 47BFAE48117EC19600A2FD56 /* readability-back-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "readability-back-icon.png"; path = "Images/readability-back-icon.png"; sourceTree = ""; }; + 47BFAE4E117EC2B800A2FD56 /* vote-up-small.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "vote-up-small.png"; path = "Images/vote-up-small.png"; sourceTree = ""; }; + 47BFAE4F117EC2B800A2FD56 /* vote-down-small.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "vote-down-small.png"; path = "Images/vote-down-small.png"; sourceTree = ""; }; + 47BFAE50117EC2B800A2FD56 /* vote-down-selected-small.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "vote-down-selected-small.png"; path = "Images/vote-down-selected-small.png"; sourceTree = ""; }; + 47BFAE51117EC2B800A2FD56 /* load-all-images.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "load-all-images.png"; path = "Images/load-all-images.png"; sourceTree = ""; }; + 47BFAE52117EC2B800A2FD56 /* vote-up-selected-small.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "vote-up-selected-small.png"; path = "Images/vote-up-selected-small.png"; sourceTree = ""; }; + 47BFAE6F117EC69C00A2FD56 /* edit-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "edit-icon.png"; path = "Images/edit-icon.png"; sourceTree = ""; }; + 47C49379118669200095DB82 /* hide-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "hide-icon.png"; path = "Images/hide-icon.png"; sourceTree = ""; }; + 47C4937A118669200095DB82 /* star-selected-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "star-selected-icon.png"; path = "Images/star-selected-icon.png"; sourceTree = ""; }; + 47C4937B118669200095DB82 /* star-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "star-icon.png"; path = "Images/star-icon.png"; sourceTree = ""; }; + 47C86400118A9B2E00DF5F4E /* pro-feature-label.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "pro-feature-label.png"; path = "Images/pro-feature-label.png"; sourceTree = ""; }; + 47C86402118A9B3900DF5F4E /* hide-selected-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "hide-selected-icon.png"; path = "Images/hide-selected-icon.png"; sourceTree = ""; }; + 47DEF6D511606A55000976EA /* CommentsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommentsTableViewController.h; sourceTree = ""; }; + 47DEF6D611606A55000976EA /* CommentsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CommentsTableViewController.m; sourceTree = ""; }; + 47DEF6DA11606A8F000976EA /* CommentsView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = CommentsView.xib; path = Xibs/CommentsView.xib; sourceTree = ""; }; + 47DEF79111607D68000976EA /* NavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NavigationController.h; sourceTree = ""; }; + 47DEF79211607D68000976EA /* NavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NavigationController.m; sourceTree = ""; }; + 47DEF9261160BB8A000976EA /* CommentCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommentCell.h; sourceTree = ""; }; + 47DEF9271160BB8A000976EA /* CommentCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CommentCell.m; sourceTree = ""; }; + 47DFAF9111816BBB0036AC43 /* CommentCellView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommentCellView.h; sourceTree = ""; }; + 47DFAF9211816BBB0036AC43 /* CommentCellView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CommentCellView.m; sourceTree = ""; }; + 47DFAFA711816D3F0036AC43 /* CommentWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommentWrapper.h; sourceTree = ""; }; + 47DFAFA811816D3F0036AC43 /* CommentWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CommentWrapper.m; sourceTree = ""; }; + 47E1C5A21189520E00B65F7E /* video-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "video-icon.png"; path = "Images/video-icon.png"; sourceTree = ""; }; + 47E1C5A31189520E00B65F7E /* image-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-icon.png"; path = "Images/image-icon.png"; sourceTree = ""; }; + 47E1C5A41189520E00B65F7E /* article-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "article-icon.png"; path = "Images/article-icon.png"; sourceTree = ""; }; + 7E03F0351193D028008D3917 /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Icon.png; sourceTree = ""; }; + 7E130DAB11919CCB00FB7009 /* Resources.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Resources.h; sourceTree = ""; }; + 7E130DAC11919CCB00FB7009 /* Resources.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Resources.m; sourceTree = ""; }; + 7E130E621191A78800FB7009 /* button-background-dark.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "button-background-dark.png"; path = "Images/button-background-dark.png"; sourceTree = ""; }; + 7E382AFE1190F718000F94DD /* DefaultBGBlack.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = DefaultBGBlack.png; path = Images/DefaultBGBlack.png; sourceTree = ""; }; + 7E60872311944684008AA06E /* tips-sheet.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "tips-sheet.png"; path = "Images/tips-sheet.png"; sourceTree = ""; }; + 7E6087751194505C008AA06E /* info-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "info-icon.png"; path = "Images/info-icon.png"; sourceTree = ""; }; + 7E61C94111A79C9C00B77ADB /* MessagesView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = MessagesView.xib; path = Xibs/MessagesView.xib; sourceTree = ""; }; + 7E61C94311A79CAA00B77ADB /* CustomMessageCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = CustomMessageCell.xib; path = Xibs/CustomMessageCell.xib; sourceTree = ""; }; + 7E926FCE118888E800FC2037 /* MKStoreManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MKStoreManager.h; sourceTree = ""; }; + 7E926FCF118888E800FC2037 /* MKStoreManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MKStoreManager.m; sourceTree = ""; }; + 7E926FD0118888E800FC2037 /* MKStoreObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MKStoreObserver.h; sourceTree = ""; }; + 7E926FD1118888E800FC2037 /* MKStoreObserver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MKStoreObserver.m; sourceTree = ""; }; + 7E926FD2118888E800FC2037 /* readme.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = readme.txt; sourceTree = ""; }; + 7E926FD4118888E900FC2037 /* AddDevice.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = AddDevice.html; sourceTree = ""; }; + 7E926FD5118888E900FC2037 /* DBSetup.sql */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = DBSetup.sql; sourceTree = ""; }; + 7E926FD6118888E900FC2037 /* featureCheck.php */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.php; path = featureCheck.php; sourceTree = ""; }; + 7E926FD7118888E900FC2037 /* requestAccess.php */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.php; path = requestAccess.php; sourceTree = ""; }; + 7E92703011888A7F00FC2037 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; + 7E93E847119A6CA90022A45A /* JSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSON.h; sourceTree = ""; }; + 7E93E848119A6CA90022A45A /* NSObject+SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+SBJSON.h"; sourceTree = ""; }; + 7E93E849119A6CA90022A45A /* NSObject+SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+SBJSON.m"; sourceTree = ""; }; + 7E93E84A119A6CA90022A45A /* NSString+SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+SBJSON.h"; sourceTree = ""; }; + 7E93E84B119A6CA90022A45A /* NSString+SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+SBJSON.m"; sourceTree = ""; }; + 7E93E84C119A6CA90022A45A /* SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJSON.h; sourceTree = ""; }; + 7E93E84D119A6CA90022A45A /* SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJSON.m; sourceTree = ""; }; + 7E93E84E119A6CA90022A45A /* SBJsonBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonBase.h; sourceTree = ""; }; + 7E93E84F119A6CA90022A45A /* SBJsonBase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonBase.m; sourceTree = ""; }; + 7E93E850119A6CA90022A45A /* SBJsonParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonParser.h; sourceTree = ""; }; + 7E93E851119A6CA90022A45A /* SBJsonParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonParser.m; sourceTree = ""; }; + 7E93E852119A6CA90022A45A /* SBJsonWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonWriter.h; sourceTree = ""; }; + 7E93E853119A6CA90022A45A /* SBJsonWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonWriter.m; sourceTree = ""; }; + 7E9E49BA1187F1FE00A3C308 /* DefaultBG.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = DefaultBG.png; path = Images/DefaultBG.png; sourceTree = ""; }; + 7ECD0CFD11A0CA1000B7CFC6 /* ImageCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageCache.h; sourceTree = ""; }; + 7ECD0CFE11A0CA1000B7CFC6 /* ImageCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ImageCache.m; sourceTree = ""; }; + 7ECD0DDB11A0E66C00B7CFC6 /* loading-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "loading-icon.png"; path = "Images/loading-icon.png"; sourceTree = ""; }; + 7ECD6F4311A9460B001081A5 /* delete-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "delete-icon.png"; path = "Images/delete-icon.png"; sourceTree = ""; }; + 7ECD708E11A9778C001081A5 /* fullscreen-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "fullscreen-icon.png"; path = "Images/fullscreen-icon.png"; sourceTree = ""; }; + 7ECD708F11A9778C001081A5 /* show-toolbars-button.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "show-toolbars-button.png"; path = "Images/show-toolbars-button.png"; sourceTree = ""; }; + 7ECFAC2011883F5C00ACDB60 /* posts-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "posts-icon.png"; path = "Images/posts-icon.png"; sourceTree = ""; }; + 7EDC74D8118C30E900C79F25 /* why-go-pro-splash.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "why-go-pro-splash.png"; path = "Images/why-go-pro-splash.png"; sourceTree = ""; }; + 7EDC74FB118C5B0700C79F25 /* orange-back-button.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "orange-back-button.png"; path = "Images/orange-back-button.png"; sourceTree = ""; }; + 7EDF4D26118D3DBD005A7435 /* no-internet-connection.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "no-internet-connection.png"; path = "Images/no-internet-connection.png"; sourceTree = ""; }; + 7EDF4D3E118D45D4005A7435 /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Reachability.m; path = Classes/Reachability.m; sourceTree = ""; }; + 7EDF4D3F118D45D4005A7435 /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Reachability.h; path = Classes/Reachability.h; sourceTree = ""; }; + 7EDF4D44118D464B005A7435 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; + 7EDF4E25118D7D62005A7435 /* more-options-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "more-options-icon.png"; path = "Images/more-options-icon.png"; sourceTree = ""; }; + 7EE1C9F4118D8AC500B4B7B2 /* green-tick-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "green-tick-icon.png"; path = "Images/green-tick-icon.png"; sourceTree = ""; }; + 7EE5370B11AC6DFD006FFF12 /* fullscreen-back-button.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "fullscreen-back-button.png"; path = "Images/fullscreen-back-button.png"; sourceTree = ""; }; + 7EE5372011AC720E006FFF12 /* right-arrow-dim.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "right-arrow-dim.png"; path = "Images/right-arrow-dim.png"; sourceTree = ""; }; + 7EEDFE4D11ADDA5E008C039B /* README */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README; sourceTree = ""; }; + 8D1107310486CEB800E47090 /* AlienBlue-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "AlienBlue-Info.plist"; plistStructureDefinitionIdentifier = "com.apple.xcode.plist.structure-definition.iphone.info-plist"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 1D60588F0D05DD3D006BFB54 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */, + 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */, + 288765080DF74369002DB57D /* CoreGraphics.framework in Frameworks */, + 7E92703111888A7F00FC2037 /* StoreKit.framework in Frameworks */, + 7EDF4D45118D464B005A7435 /* SystemConfiguration.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 080E96DDFE201D6D7F000001 /* Classes */ = { + isa = PBXGroup; + children = ( + 472B193A11787DEA000204D7 /* MessageCell.h */, + 472B193B11787DEA000204D7 /* MessageCell.m */, + 472B189911784586000204D7 /* MessagesTableViewController.h */, + 472B189A11784586000204D7 /* MessagesTableViewController.m */, + 47DEF79111607D68000976EA /* NavigationController.h */, + 47DEF79211607D68000976EA /* NavigationController.m */, + 47BC855E115EEC1700A4362E /* PostsTableControllerView.h */, + 47BC855F115EEC1700A4362E /* PostsTableControllerView.m */, + 28216C950DB411BC00E5133A /* BrowserViewController.h */, + 28216C960DB411BC00E5133A /* BrowserViewController.m */, + 1D3623240D0F684500981E51 /* AlienBlueAppDelegate.h */, + 1D3623250D0F684500981E51 /* AlienBlueAppDelegate.m */, + 4731919B115F2F0D00FDA23A /* PostCell.h */, + 4731919C115F2F0D00FDA23A /* PostCell.m */, + 47DEF6D511606A55000976EA /* CommentsTableViewController.h */, + 47DEF6D611606A55000976EA /* CommentsTableViewController.m */, + 47DEF9261160BB8A000976EA /* CommentCell.h */, + 47DEF9271160BB8A000976EA /* CommentCell.m */, + 47DFAF9111816BBB0036AC43 /* CommentCellView.h */, + 47DFAF9211816BBB0036AC43 /* CommentCellView.m */, + 474F4C0511680F4B005F1BAD /* RedditAPI.h */, + 474F4C0611680F4B005F1BAD /* RedditAPI.m */, + 474F4DA71168A8A1005F1BAD /* SettingsTableViewController.h */, + 474F4DA81168A8A1005F1BAD /* SettingsTableViewController.m */, + 47DFAFA711816D3F0036AC43 /* CommentWrapper.h */, + 47DFAFA811816D3F0036AC43 /* CommentWrapper.m */, + 7E130DAB11919CCB00FB7009 /* Resources.h */, + 7E130DAC11919CCB00FB7009 /* Resources.m */, + 7ECD0CFD11A0CA1000B7CFC6 /* ImageCache.h */, + 7ECD0CFE11A0CA1000B7CFC6 /* ImageCache.m */, + ); + path = Classes; + sourceTree = ""; + }; + 19C28FACFE9D520D11CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 1D6058910D05DD3D006BFB54 /* AlienBlue.app */, + ); + name = Products; + sourceTree = ""; + }; + 29B97314FDCFA39411CA2CEA /* NeuReddit */ = { + isa = PBXGroup; + children = ( + 7E03F0351193D028008D3917 /* Icon.png */, + 4701FCA21180136900FE9A76 /* Default.png */, + 47BFAA5E117DC41700A2FD56 /* IconBig.png */, + 080E96DDFE201D6D7F000001 /* Classes */, + 29B97315FDCFA39411CA2CEA /* Other Sources */, + 29B97317FDCFA39411CA2CEA /* Resources */, + 29B97323FDCFA39411CA2CEA /* Frameworks */, + 19C28FACFE9D520D11CA2CBB /* Products */, + 7EEDFE4D11ADDA5E008C039B /* README */, + ); + name = NeuReddit; + sourceTree = ""; + }; + 29B97315FDCFA39411CA2CEA /* Other Sources */ = { + isa = PBXGroup; + children = ( + 28A0AB4B0D9B1048005BE974 /* AlienBlue_Prefix.pch */, + 29B97316FDCFA39411CA2CEA /* main.m */, + ); + name = "Other Sources"; + sourceTree = ""; + }; + 29B97317FDCFA39411CA2CEA /* Resources */ = { + isa = PBXGroup; + children = ( + 474F521E116A2248005F1BAD /* Backgrounds */, + 474F4DA21168A825005F1BAD /* Xibs */, + 4760ADA41166F63D00E9BB16 /* Images */, + 8D1107310486CEB800E47090 /* AlienBlue-Info.plist */, + ); + name = Resources; + sourceTree = ""; + }; + 29B97323FDCFA39411CA2CEA /* Frameworks */ = { + isa = PBXGroup; + children = ( + 7EF9772411ADCDF900B5D81F /* Reachability */, + 7E93E846119A6CA90022A45A /* JSON */, + 7EDF4D44118D464B005A7435 /* SystemConfiguration.framework */, + 7E92703011888A7F00FC2037 /* StoreKit.framework */, + 7E926FCD118888E800FC2037 /* MKStoreKit */, + 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */, + 1D30AB110D05D00D00671497 /* Foundation.framework */, + 288765070DF74369002DB57D /* CoreGraphics.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 474F4DA21168A825005F1BAD /* Xibs */ = { + isa = PBXGroup; + children = ( + 7E61C94311A79CAA00B77ADB /* CustomMessageCell.xib */, + 7E61C94111A79C9C00B77ADB /* MessagesView.xib */, + 47294ED4117C9092001A8EAF /* SettingsView.xib */, + 474F4FFA11698A78005F1BAD /* PostsView.xib */, + 47DEF6DA11606A8F000976EA /* CommentsView.xib */, + 28AD73870D9D96C1002E5188 /* MainWindow.xib */, + 4760A9CB1165875C00E9BB16 /* BrowserView.xib */, + ); + name = Xibs; + sourceTree = ""; + }; + 474F521E116A2248005F1BAD /* Backgrounds */ = { + isa = PBXGroup; + children = ( + 7E130E621191A78800FB7009 /* button-background-dark.png */, + 7E382AFE1190F718000F94DD /* DefaultBGBlack.png */, + 7E9E49BA1187F1FE00A3C308 /* DefaultBG.png */, + 4731DC3111720923006806D0 /* button-background.png */, + ); + name = Backgrounds; + sourceTree = ""; + }; + 4760ADA41166F63D00E9BB16 /* Images */ = { + isa = PBXGroup; + children = ( + 7EE5372011AC720E006FFF12 /* right-arrow-dim.png */, + 7EE5370B11AC6DFD006FFF12 /* fullscreen-back-button.png */, + 7ECD708E11A9778C001081A5 /* fullscreen-icon.png */, + 7ECD708F11A9778C001081A5 /* show-toolbars-button.png */, + 7ECD6F4311A9460B001081A5 /* delete-icon.png */, + 7ECD0DDB11A0E66C00B7CFC6 /* loading-icon.png */, + 7E6087751194505C008AA06E /* info-icon.png */, + 7E60872311944684008AA06E /* tips-sheet.png */, + 7EE1C9F4118D8AC500B4B7B2 /* green-tick-icon.png */, + 7EDF4E25118D7D62005A7435 /* more-options-icon.png */, + 7EDF4D26118D3DBD005A7435 /* no-internet-connection.png */, + 7EDC74FB118C5B0700C79F25 /* orange-back-button.png */, + 7EDC74D8118C30E900C79F25 /* why-go-pro-splash.png */, + 4740AF24118C031200A7FA2A /* upgrade-to-pro-label.png */, + 47C86402118A9B3900DF5F4E /* hide-selected-icon.png */, + 47C86400118A9B2E00DF5F4E /* pro-feature-label.png */, + 47E1C5A21189520E00B65F7E /* video-icon.png */, + 47E1C5A31189520E00B65F7E /* image-icon.png */, + 47E1C5A41189520E00B65F7E /* article-icon.png */, + 7ECFAC2011883F5C00ACDB60 /* posts-icon.png */, + 47C49379118669200095DB82 /* hide-icon.png */, + 47C4937A118669200095DB82 /* star-selected-icon.png */, + 47C4937B118669200095DB82 /* star-icon.png */, + 47BFAE6F117EC69C00A2FD56 /* edit-icon.png */, + 47BFAE4E117EC2B800A2FD56 /* vote-up-small.png */, + 47BFAE4F117EC2B800A2FD56 /* vote-down-small.png */, + 47BFAE50117EC2B800A2FD56 /* vote-down-selected-small.png */, + 47BFAE51117EC2B800A2FD56 /* load-all-images.png */, + 47BFAE52117EC2B800A2FD56 /* vote-up-selected-small.png */, + 47BFAE47117EC19600A2FD56 /* readability-icon.png */, + 4731935C115F7DEC00FDA23A /* black-gradient.png */, + 47BFAE48117EC19600A2FD56 /* readability-back-icon.png */, + 47BFAC8F117E837400A2FD56 /* comment-parent-icon.png */, + 47BFA8B3117CA2B900A2FD56 /* top-scroll.png */, + 47BFA8B4117CA2B900A2FD56 /* bottom-scroll.png */, + 472B1A2311789895000204D7 /* mail-red.png */, + 472B1A211178985C000204D7 /* gear-icon.png */, + 472B1A1E11789729000204D7 /* mail-red-thin.png */, + 472B19E111788D41000204D7 /* mail-gray.png */, + 477ADA8D11759A4000342ECD /* level-arrows.png */, + 4760ADA61166F64600E9BB16 /* vote-up.png */, + 4760ADA71166F64600E9BB16 /* vote-down-selected.png */, + 4760ADA81166F64600E9BB16 /* vote-down.png */, + 4760ADA91166F64600E9BB16 /* vote-up-selected.png */, + 4760ADAA1166F64600E9BB16 /* reply.png */, + 4760ADAB1166F64600E9BB16 /* reply-selected.png */, + ); + name = Images; + sourceTree = ""; + }; + 7E926FCD118888E800FC2037 /* MKStoreKit */ = { + isa = PBXGroup; + children = ( + 7E926FCE118888E800FC2037 /* MKStoreManager.h */, + 7E926FCF118888E800FC2037 /* MKStoreManager.m */, + 7E926FD0118888E800FC2037 /* MKStoreObserver.h */, + 7E926FD1118888E800FC2037 /* MKStoreObserver.m */, + 7E926FD2118888E800FC2037 /* readme.txt */, + 7E926FD3118888E800FC2037 /* Server Code */, + ); + path = MKStoreKit; + sourceTree = ""; + }; + 7E926FD3118888E800FC2037 /* Server Code */ = { + isa = PBXGroup; + children = ( + 7E926FD4118888E900FC2037 /* AddDevice.html */, + 7E926FD5118888E900FC2037 /* DBSetup.sql */, + 7E926FD6118888E900FC2037 /* featureCheck.php */, + 7E926FD7118888E900FC2037 /* requestAccess.php */, + ); + path = "Server Code"; + sourceTree = ""; + }; + 7E93E846119A6CA90022A45A /* JSON */ = { + isa = PBXGroup; + children = ( + 7E93E847119A6CA90022A45A /* JSON.h */, + 7E93E848119A6CA90022A45A /* NSObject+SBJSON.h */, + 7E93E849119A6CA90022A45A /* NSObject+SBJSON.m */, + 7E93E84A119A6CA90022A45A /* NSString+SBJSON.h */, + 7E93E84B119A6CA90022A45A /* NSString+SBJSON.m */, + 7E93E84C119A6CA90022A45A /* SBJSON.h */, + 7E93E84D119A6CA90022A45A /* SBJSON.m */, + 7E93E84E119A6CA90022A45A /* SBJsonBase.h */, + 7E93E84F119A6CA90022A45A /* SBJsonBase.m */, + 7E93E850119A6CA90022A45A /* SBJsonParser.h */, + 7E93E851119A6CA90022A45A /* SBJsonParser.m */, + 7E93E852119A6CA90022A45A /* SBJsonWriter.h */, + 7E93E853119A6CA90022A45A /* SBJsonWriter.m */, + ); + path = JSON; + sourceTree = ""; + }; + 7EF9772411ADCDF900B5D81F /* Reachability */ = { + isa = PBXGroup; + children = ( + 7EDF4D3E118D45D4005A7435 /* Reachability.m */, + 7EDF4D3F118D45D4005A7435 /* Reachability.h */, + ); + name = Reachability; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 1D6058900D05DD3D006BFB54 /* AlienBlue */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "AlienBlue" */; + buildPhases = ( + 1D60588D0D05DD3D006BFB54 /* Resources */, + 1D60588E0D05DD3D006BFB54 /* Sources */, + 1D60588F0D05DD3D006BFB54 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AlienBlue; + productName = NeuReddit; + productReference = 1D6058910D05DD3D006BFB54 /* AlienBlue.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 29B97313FDCFA39411CA2CEA /* Project object */ = { + isa = PBXProject; + buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "AlienBlue" */; + compatibilityVersion = "Xcode 3.1"; + hasScannedForEncodings = 1; + mainGroup = 29B97314FDCFA39411CA2CEA /* NeuReddit */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 1D6058900D05DD3D006BFB54 /* AlienBlue */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 1D60588D0D05DD3D006BFB54 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 28AD73880D9D96C1002E5188 /* MainWindow.xib in Resources */, + 4731935D115F7DEC00FDA23A /* black-gradient.png in Resources */, + 47DEF6DB11606A8F000976EA /* CommentsView.xib in Resources */, + 4760A9CC1165875C00E9BB16 /* BrowserView.xib in Resources */, + 4760ADAC1166F64600E9BB16 /* vote-up.png in Resources */, + 4760ADAD1166F64600E9BB16 /* vote-down-selected.png in Resources */, + 4760ADAE1166F64600E9BB16 /* vote-down.png in Resources */, + 4760ADAF1166F64600E9BB16 /* vote-up-selected.png in Resources */, + 4760ADB01166F64600E9BB16 /* reply.png in Resources */, + 4760ADB11166F64600E9BB16 /* reply-selected.png in Resources */, + 474F4FFB11698A78005F1BAD /* PostsView.xib in Resources */, + 4731DC3211720923006806D0 /* button-background.png in Resources */, + 477ADA8E11759A4000342ECD /* level-arrows.png in Resources */, + 472B19E311788D41000204D7 /* mail-gray.png in Resources */, + 472B1A1F11789729000204D7 /* mail-red-thin.png in Resources */, + 472B1A221178985C000204D7 /* gear-icon.png in Resources */, + 472B1A2411789895000204D7 /* mail-red.png in Resources */, + 47294ED5117C9092001A8EAF /* SettingsView.xib in Resources */, + 47BFA8B5117CA2B900A2FD56 /* top-scroll.png in Resources */, + 47BFA8B6117CA2B900A2FD56 /* bottom-scroll.png in Resources */, + 47BFAA5F117DC41700A2FD56 /* IconBig.png in Resources */, + 47BFAC90117E837400A2FD56 /* comment-parent-icon.png in Resources */, + 47BFAE49117EC19600A2FD56 /* readability-icon.png in Resources */, + 47BFAE4A117EC19600A2FD56 /* readability-back-icon.png in Resources */, + 47BFAE53117EC2B800A2FD56 /* vote-up-small.png in Resources */, + 47BFAE54117EC2B800A2FD56 /* vote-down-small.png in Resources */, + 47BFAE55117EC2B800A2FD56 /* vote-down-selected-small.png in Resources */, + 47BFAE56117EC2B800A2FD56 /* load-all-images.png in Resources */, + 47BFAE57117EC2B800A2FD56 /* vote-up-selected-small.png in Resources */, + 47BFAE70117EC69C00A2FD56 /* edit-icon.png in Resources */, + 4701FCA31180136900FE9A76 /* Default.png in Resources */, + 47C4937C118669200095DB82 /* hide-icon.png in Resources */, + 47C4937D118669200095DB82 /* star-selected-icon.png in Resources */, + 47C4937E118669200095DB82 /* star-icon.png in Resources */, + 7E9E49BB1187F1FE00A3C308 /* DefaultBG.png in Resources */, + 7ECFAC2111883F5C00ACDB60 /* posts-icon.png in Resources */, + 7E926FDA118888E900FC2037 /* readme.txt in Resources */, + 7E926FDB118888E900FC2037 /* AddDevice.html in Resources */, + 7E926FDC118888E900FC2037 /* DBSetup.sql in Resources */, + 7E926FDD118888E900FC2037 /* featureCheck.php in Resources */, + 7E926FDE118888E900FC2037 /* requestAccess.php in Resources */, + 47E1C5A51189520E00B65F7E /* video-icon.png in Resources */, + 47E1C5A61189520E00B65F7E /* image-icon.png in Resources */, + 47E1C5A71189520E00B65F7E /* article-icon.png in Resources */, + 47C86401118A9B2E00DF5F4E /* pro-feature-label.png in Resources */, + 47C86403118A9B3900DF5F4E /* hide-selected-icon.png in Resources */, + 4740AF25118C031200A7FA2A /* upgrade-to-pro-label.png in Resources */, + 7EDC74D9118C30E900C79F25 /* why-go-pro-splash.png in Resources */, + 7EDC74FC118C5B0700C79F25 /* orange-back-button.png in Resources */, + 7EDF4D27118D3DBD005A7435 /* no-internet-connection.png in Resources */, + 7EDF4E26118D7D62005A7435 /* more-options-icon.png in Resources */, + 7EE1C9F5118D8AC500B4B7B2 /* green-tick-icon.png in Resources */, + 7E382AFF1190F718000F94DD /* DefaultBGBlack.png in Resources */, + 7E130E631191A78800FB7009 /* button-background-dark.png in Resources */, + 7E03F0361193D028008D3917 /* Icon.png in Resources */, + 7E60872411944684008AA06E /* tips-sheet.png in Resources */, + 7E6087761194505C008AA06E /* info-icon.png in Resources */, + 7ECD0DDC11A0E66C00B7CFC6 /* loading-icon.png in Resources */, + 7E61C94211A79C9C00B77ADB /* MessagesView.xib in Resources */, + 7E61C94411A79CAA00B77ADB /* CustomMessageCell.xib in Resources */, + 7ECD6F4411A9460B001081A5 /* delete-icon.png in Resources */, + 7ECD709011A9778C001081A5 /* fullscreen-icon.png in Resources */, + 7ECD709111A9778C001081A5 /* show-toolbars-button.png in Resources */, + 7EE5370C11AC6DFD006FFF12 /* fullscreen-back-button.png in Resources */, + 7EE5372111AC720E006FFF12 /* right-arrow-dim.png in Resources */, + 7EEDFE4E11ADDA5E008C039B /* README in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 1D60588E0D05DD3D006BFB54 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D60589B0D05DD56006BFB54 /* main.m in Sources */, + 1D3623260D0F684500981E51 /* AlienBlueAppDelegate.m in Sources */, + 28216C970DB411BC00E5133A /* BrowserViewController.m in Sources */, + 47BC8560115EEC1700A4362E /* PostsTableControllerView.m in Sources */, + 4731919D115F2F0D00FDA23A /* PostCell.m in Sources */, + 47DEF6D711606A55000976EA /* CommentsTableViewController.m in Sources */, + 47DEF79311607D68000976EA /* NavigationController.m in Sources */, + 47DEF9281160BB8A000976EA /* CommentCell.m in Sources */, + 474F4C0711680F4B005F1BAD /* RedditAPI.m in Sources */, + 474F4DA91168A8A1005F1BAD /* SettingsTableViewController.m in Sources */, + 472B189B11784586000204D7 /* MessagesTableViewController.m in Sources */, + 472B193C11787DEA000204D7 /* MessageCell.m in Sources */, + 47DFAF9311816BBB0036AC43 /* CommentCellView.m in Sources */, + 47DFAFA911816D3F0036AC43 /* CommentWrapper.m in Sources */, + 7E926FD8118888E900FC2037 /* MKStoreManager.m in Sources */, + 7E926FD9118888E900FC2037 /* MKStoreObserver.m in Sources */, + 7EDF4D40118D45D4005A7435 /* Reachability.m in Sources */, + 7E130DAD11919CCB00FB7009 /* Resources.m in Sources */, + 7E93E854119A6CA90022A45A /* NSObject+SBJSON.m in Sources */, + 7E93E855119A6CA90022A45A /* NSString+SBJSON.m in Sources */, + 7E93E856119A6CA90022A45A /* SBJSON.m in Sources */, + 7E93E857119A6CA90022A45A /* SBJsonBase.m in Sources */, + 7E93E858119A6CA90022A45A /* SBJsonParser.m in Sources */, + 7E93E859119A6CA90022A45A /* SBJsonWriter.m in Sources */, + 7ECD0CFF11A0CA1000B7CFC6 /* ImageCache.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1D6058940D05DD3E006BFB54 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)\"", + ); + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = AlienBlue_Prefix.pch; + INFOPLIST_FILE = "AlienBlue-Info.plist"; + PRODUCT_NAME = AlienBlue; + }; + name = Debug; + }; + 1D6058950D05DD3E006BFB54 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)\"", + ); + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = AlienBlue_Prefix.pch; + INFOPLIST_FILE = "AlienBlue-Info.plist"; + PRODUCT_NAME = AlienBlue; + }; + name = Release; + }; + 7E03F0051193CBDC008D3917 /* Distribution */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution: Morrissey Exchange Pty Ltd"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_PROFILING_CODE = YES; + PREBINDING = NO; + PRODUCT_NAME = "Alien Blue"; + "PROVISIONING_PROFILE[sdk=iphoneos*]" = "BFFC9700-4F30-4501-9C1A-E496EC986429"; + SDKROOT = iphoneos3.0; + }; + name = Distribution; + }; + 7E03F0061193CBDC008D3917 /* Distribution */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)\"", + ); + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = AlienBlue_Prefix.pch; + INFOPLIST_FILE = "AlienBlue-Info.plist"; + PRODUCT_NAME = AlienBlue; + }; + name = Distribution; + }; + C01FCF4F08A954540054247B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer: Jason Morrissey (833HJWY655)"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_DEBUGGING_SYMBOLS = full; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_PROFILING_CODE = YES; + PREBINDING = NO; + PRODUCT_NAME = "Alien Blue"; + "PROVISIONING_PROFILE[sdk=iphoneos*]" = "2451BC07-134E-4C68-B98B-79DBF3C7737B"; + SDKROOT = iphoneos3.0; + }; + name = Debug; + }; + C01FCF5008A954540054247B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer: Jason Morrissey (833HJWY655)"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_PROFILING_CODE = YES; + PREBINDING = NO; + PRODUCT_NAME = "Alien Blue"; + "PROVISIONING_PROFILE[sdk=iphoneos*]" = "2451BC07-134E-4C68-B98B-79DBF3C7737B"; + SDKROOT = iphoneos3.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "AlienBlue" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1D6058940D05DD3E006BFB54 /* Debug */, + 1D6058950D05DD3E006BFB54 /* Release */, + 7E03F0061193CBDC008D3917 /* Distribution */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C01FCF4E08A954540054247B /* Build configuration list for PBXProject "AlienBlue" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C01FCF4F08A954540054247B /* Debug */, + C01FCF5008A954540054247B /* Release */, + 7E03F0051193CBDC008D3917 /* Distribution */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; +} diff --git a/AlienBlue_Prefix.pch b/AlienBlue_Prefix.pch new file mode 100755 index 0000000..1f0c651 --- /dev/null +++ b/AlienBlue_Prefix.pch @@ -0,0 +1,8 @@ +// +// Prefix header for all source files of the 'NeuReddit' target in the 'NeuReddit' project +// + +#ifdef __OBJC__ + #import + #import +#endif diff --git a/Classes/AlienBlueAppDelegate.h b/Classes/AlienBlueAppDelegate.h new file mode 100755 index 0000000..a409bba --- /dev/null +++ b/Classes/AlienBlueAppDelegate.h @@ -0,0 +1,35 @@ +// +// AlienBlueAppDelegate.h +// Alien Blue +// +// Created by Jason Morrissey on 28/03/10. +// Copyright The Design Shed 2010. All rights reserved. +// + +#import +#import "RedditAPI.h" +#import "NavigationController.h" + +@interface AlienBlueAppDelegate : NSObject { + UIWindow *window; + IBOutlet NavigationController *tabBarController; + RedditAPI * redditAPI; + NSTimer * inboxCheckTimer; + NSUserDefaults * prefs; + UIImageView * errorImage; + IBOutlet UIImageView * blackBG; +} + +@property (nonatomic, retain) IBOutlet UIWindow *window; +@property (nonatomic, retain) IBOutlet NavigationController *tabBarController; +@property (nonatomic, retain) IBOutlet UIImageView *blackBG; +@property (readwrite, retain) RedditAPI *redditAPI; +- (void) refreshUnreadMailBadge; +-(IBAction) checkForNewMessages: (id) sender; +- (void) proVersionUpgraded; + +- (void) showConnectionErrorImage; +- (void) hideConnectionErrorImage; +- (void) refreshBackground; +- (void) stopPurchaseIndicator; +@end diff --git a/Classes/AlienBlueAppDelegate.m b/Classes/AlienBlueAppDelegate.m new file mode 100755 index 0000000..f97a5df --- /dev/null +++ b/Classes/AlienBlueAppDelegate.m @@ -0,0 +1,232 @@ +// +// AlienBlueAppDelegate.m +// Alien Blue +// +// Created by Jason Morrissey on 28/03/10. +// Copyright The Design Shed 2010. All rights reserved. +// + +#import "AlienBlueAppDelegate.h" +#import "MessagesTableViewController.h" +#import "SettingsTableViewController.h" + +@implementation AlienBlueAppDelegate + +@synthesize window; +@synthesize tabBarController; +@synthesize redditAPI; +@synthesize blackBG; + +- (void) authtest1:(id) sender +{ + NSLog(@"authtest1 in()"); + if ([redditAPI authenticated]) + { + NSLog(@"-- user is authenticated --"); + [self refreshUnreadMailBadge]; + } + else + NSLog(@"-- not logged in --"); +} + +-(IBAction) checkForNewMessages: (id) sender +{ + if ([redditAPI authenticated]) + [redditAPI fetchUnreadMessageCount:self]; +} + +-(IBAction) apiUnreadMessageCountResponse: (id) sender +{ + NSLog(@"-- new results for unread message count : %d", [redditAPI unreadMessageCount]); + [self refreshUnreadMailBadge]; +} + +- (void) refreshUnreadMailBadge +{ + UITabBarItem * messagesTab = [[[tabBarController tabBar] items] objectAtIndex:1]; + if([redditAPI unreadMessageCount] > 0) + { + [messagesTab setBadgeValue:[NSString stringWithFormat:@"%d",[redditAPI unreadMessageCount]]]; + } + else + { + [messagesTab setBadgeValue:nil]; + } +} + + +// this is to handle users clicking "Upgrade Now" in UIAlert dialogs +- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex +{ + if(alertView.tag == 99 && buttonIndex == 1) + { + NSLog(@"Upgrade Now Pressed"); + [tabBarController setSelectedIndex:2]; + + NSIndexPath * ind = [NSIndexPath indexPathForRow:0 inSection:7]; + [[(SettingsTableViewController *) [tabBarController selectedViewController] tableView] scrollToRowAtIndexPath:ind atScrollPosition:UITableViewScrollPositionTop animated:NO]; + } +} + +- (void) refreshBackground +{ + if ([prefs boolForKey:@"night_mode"]) + [blackBG setHidden:NO]; + else + [blackBG setHidden:YES]; +} + +- (void) stopPurchaseIndicator +{ + if ([tabBarController selectedIndex] == 2) + { + [(SettingsTableViewController *) [tabBarController selectedViewController] stopPurchaseActivityIndicator]; + } +} + +- (void) proVersionUpgraded +{ + NSLog(@"proVersionUpgraded"); + + if ([tabBarController selectedIndex] == 2) + { + // refresh settings - so that the "Thank you message" now displays + [[(SettingsTableViewController *) [tabBarController selectedViewController] tableView] reloadData]; + [(SettingsTableViewController *) [tabBarController selectedViewController] stopPurchaseActivityIndicator]; + } +} + +- (void) showConnectionErrorImage +{ + if (![errorImage superview]) + [window insertSubview:errorImage atIndex:99]; +} + +- (void) hideConnectionErrorImage +{ + if ([errorImage superview]) + [errorImage removeFromSuperview]; +} + +- (void)applicationDidFinishLaunching:(UIApplication *)application { + + errorImage = [[UIImageView alloc] initWithImage:[[UIImage imageNamed:@"no-internet-connection.png"] retain]]; + + prefs = [NSUserDefaults standardUserDefaults]; + + // setup the default user settings on first launch + if(![prefs boolForKey:@"already_ran"] ) + { + [prefs setBool:YES forKey:@"already_ran"]; + + [prefs setBool:YES forKey:@"use_lowres_imgur"]; + [prefs setBool:YES forKey:@"use_direct_imgur_link"]; + + [prefs setBool:YES forKey:@"show_hide_queue"]; + [prefs setBool:YES forKey:@"show_thumbs"]; + [prefs setBool:YES forKey:@"show_help_icon"]; + [prefs setBool:NO forKey:@"night_mode"]; + [prefs setBool:NO forKey:@"auto_mark_as_read"]; + [prefs setBool:YES forKey:@"allow_rotation"]; + [prefs setBool:YES forKey:@"allow_status_bar_scroll"]; + [prefs setBool:NO forKey:@"show_quick_scroll"]; + [prefs setBool:NO forKey:@"allow_tilt_scroll"]; + [prefs setInteger:1 forKey:@"textsize"]; + [prefs setInteger:0 forKey:@"fetch_message_frequency"]; + [prefs synchronize]; + } + + if(![prefs objectForKey:@"filterList"]) + { + NSLog(@"initialising post filter"); + NSMutableArray * filterList = [NSMutableArray arrayWithCapacity:1]; + [prefs setObject:filterList forKey:@"filterList"]; + [prefs synchronize]; + } + else + { + NSMutableArray * filterList = (NSMutableArray *) [prefs objectForKey:@"filterList"]; + for (NSString * filterItem in filterList) + { + NSLog(@"Filter Item : %@", filterItem); + } + } + + + // Always disable tilt-scroll on launch. Otherwise, the user is going to get + // unexpected scrolling if they forgot that tilt-scroll was activated previously. + [prefs setBool:NO forKey:@"allow_tilt_scroll"]; + [prefs synchronize]; + + [MKStoreManager sharedManager]; + + if([MKStoreManager isProUpgraded]) + { + NSLog(@"-- pro version in use --"); + } + else + { + NSLog(@"-- free version in use --"); + } + + // Add the tab bar controller's current view as a subview of the window + [window addSubview:tabBarController.view]; + [tabBarController setDelegate:tabBarController]; + + redditAPI = [[RedditAPI alloc] init]; + [tabBarController loadNibs]; + + [redditAPI testReachability]; + + [redditAPI authenticateWithCallbackTarget:self andCallBackAction:@"authtest1:"]; + int freq_selection = [[prefs valueForKey:@"fetch_message_frequency"] intValue]; + int checkTime = -1; + if (freq_selection == 1) + checkTime = 2 * 60; + else if (freq_selection == 2) + checkTime = 5 * 60; + else if (freq_selection == 3) + checkTime = 10 * 60; + + + if (checkTime > 0) + { + NSLog(@"-- will check for messages every %d minutes", checkTime / 60); + inboxCheckTimer = [NSTimer scheduledTimerWithTimeInterval:checkTime + target:self + selector:@selector(checkForNewMessages:) + userInfo:nil + repeats:YES]; + } + else + NSLog(@"-- manual message checking --"); + + [self refreshBackground]; + +// [self showConnectionErrorImage]; +} + + +/* +// Optional UITabBarControllerDelegate method +- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController { +} +*/ + +/* +// Optional UITabBarControllerDelegate method +- (void)tabBarController:(UITabBarController *)tabBarController didEndCustomizingViewControllers:(NSArray *)viewControllers changed:(BOOL)changed { +} +*/ + + +- (void)dealloc { + [tabBarController release]; + [window release]; + [super dealloc]; +} + + + +@end + diff --git a/Classes/BrowserViewController.h b/Classes/BrowserViewController.h new file mode 100755 index 0000000..52858ac --- /dev/null +++ b/Classes/BrowserViewController.h @@ -0,0 +1,39 @@ +// +// FirstViewController.h +// Alien Blue +// +// Created by Jason Morrissey on 28/03/10. +// Copyright The Design Shed 2010. All rights reserved. +// + +#import + + +@interface BrowserViewController : UIViewController { + BOOL readability_refresh; + + IBOutlet UIWebView *webView; + IBOutlet UINavigationItem *navTitle; + IBOutlet UINavigationBar *navbar; + IBOutlet UIBarButtonItem *backButton; + IBOutlet UIActivityIndicatorView *loadingIndicator; + UIBarButtonItem * readabilityButton; + NSString * backTo; + NSUserDefaults * prefs; + UIImage * readabilityIcon; + UIImage * readabilityBackIcon; +} + +@property (nonatomic, retain) IBOutlet UIWebView *webView; +@property (nonatomic, retain) IBOutlet UINavigationBar *navbar; +@property (nonatomic, retain) IBOutlet UINavigationItem *navTitle; +@property (nonatomic, retain) IBOutlet UIBarButtonItem *readabilityButton; +@property (nonatomic, retain) IBOutlet UIBarButtonItem *backButton; +@property (nonatomic, retain) IBOutlet UIActivityIndicatorView *loadingIndicator; +@property (retain) NSString * backTo; + +- (IBAction)goBack:(id)sender; +- (IBAction)readability:(id)sender; +-(void) browseToLink:(NSString *) link fromMessages:(BOOL) fromMessages; + +@end diff --git a/Classes/BrowserViewController.m b/Classes/BrowserViewController.m new file mode 100755 index 0000000..be1b7bb --- /dev/null +++ b/Classes/BrowserViewController.m @@ -0,0 +1,220 @@ +// +// FirstViewController.m +// Alien Blue +// +// Created by Jason Morrissey on 28/03/10. +// Copyright The Design Shed 2010. All rights reserved. +// + +#import "BrowserViewController.h" +#import "NavigationController.h" + +@implementation BrowserViewController + +@synthesize webView, navTitle, readabilityButton, loadingIndicator, navbar, backButton, backTo; + +-(void) browseImage:(NSString *) link +{ + NSString *htmlString = @""; + NSString *imageHTML = [[NSString alloc] initWithFormat:htmlString, link]; + webView.scalesPageToFit = YES; + [webView loadHTMLString:imageHTML baseURL:nil]; + +} + +-(void) browseArticle:(NSString *) address +{ + NSLog(@"browseTo : %@", address); + NSURL *url = [NSURL URLWithString:address]; + NSURLRequest *requestObj = [NSURLRequest requestWithURL:url]; + [webView setBackgroundColor:[UIColor blackColor]]; + [webView loadRequest:requestObj]; + readability_refresh = FALSE; +} + +- (BOOL) isImageLink:(NSString *) link +{ + if( + [link rangeOfString:@".png" options:NSCaseInsensitiveSearch].location != NSNotFound || + [link rangeOfString:@".jpg" options:NSCaseInsensitiveSearch].location != NSNotFound || + [link rangeOfString:@".jpeg" options:NSCaseInsensitiveSearch].location != NSNotFound + ) + return YES; + else + return NO; +} + + +- (IBAction)gotoMessages:(id)sender +{ + NSLog(@"gotoMessages in()"); + // [self.navigationController popViewControllerAnimated:YES]; + NavigationController * nc = (NavigationController *) [[self parentViewController] parentViewController]; + [[nc postsNavigation] popViewControllerAnimated:NO]; + [nc setSelectedIndex:1]; +} + + + +-(void) browseToLink:(NSString *) link fromMessages:(BOOL) fromMessages +{ + if (fromMessages) + { +// NavigationController * nc = (NavigationController *) [[self parentViewController] parentViewController]; + self.navigationItem.titleView = nil; +// +// UIBarButtonItem * back = self.navigationItem.leftBarButtonItem; +// [back setTitle:@"Messages"]; +// [back setTarget:self]; +// [back setAction:@selector(gotoMessages:)]; + } + else + { +// self.navigationItem.leftBarButtonItem = nil; +// [[self navigationItem] setHidesBackButton:NO]; + } + + if ([self isImageLink:link]) + { + [self browseImage:link]; + } + else + [self browseArticle:link]; +} + +- (IBAction)goBack:(id)sender +{ + NavigationController * nc = (NavigationController *) [self parentViewController]; + if ([backTo isEqualToString:@"Comments"]) + [nc setSelectedViewController:[[nc viewControllers] objectAtIndex:1]]; + else + [nc setSelectedViewController:[[nc viewControllers] objectAtIndex:0]]; +} + +- (IBAction)readability:(id)sender +{ + if ([readabilityButton image] == readabilityIcon) + { + NSLog(@"readability mode"); + NSString * rdb = @"javascript:(function(){readStyle='style-apertura';readSize='size-x-large';readMargin='margin-wide';_readability_script=document.createElement('SCRIPT');_readability_script.type='text/javascript';_readability_script.src='http://lab.arc90.com/experiments/readability/js/readability.js?x='+(Math.random());document.getElementsByTagName('head')[0].appendChild(_readability_script);_readability_css=document.createElement('LINK');_readability_css.rel='stylesheet';_readability_css.href='http://lab.arc90.com/experiments/readability/css/readability.css';_readability_css.type='text/css';_readability_css.media='all';document.getElementsByTagName('head')[0].appendChild(_readability_css);_readability_print_css=document.createElement('LINK');_readability_print_css.rel='stylesheet';_readability_print_css.href='http://lab.arc90.com/experiments/readability/css/readability-print.css';_readability_print_css.media='print';_readability_print_css.type='text/css';document.getElementsByTagName('head')[0].appendChild(_readability_print_css);})();"; + [self.webView stringByEvaluatingJavaScriptFromString:rdb]; + [readabilityButton setImage:readabilityBackIcon]; +// [readabilityButton setTitle:@"Original"]; + readability_refresh = FALSE; + } + else + { + NSLog(@"original view"); + [loadingIndicator startAnimating]; + NSString * rdb = @"javascript:window.location.reload();"; + [self.webView stringByEvaluatingJavaScriptFromString:rdb]; + [readabilityButton setImage:readabilityIcon]; +// [readabilityButton setTitle:@"Readability"]; +// readability_refresh = TRUE; + } +} + +- (IBAction)normal:(id)sender +{ + NSLog(@"normal mode"); + [self.webView goBack]; +} + + +- (void)webViewDidFinishLoad:(UIWebView *)webView +{ + [loadingIndicator stopAnimating]; + [readabilityButton setEnabled:TRUE]; + +} + +- (void)webViewDidStartLoad:(UIWebView *)webView +{ + NSLog(@"loading web page ..."); + [loadingIndicator startAnimating]; + if (!readability_refresh) + { + [readabilityButton setImage:readabilityIcon]; + } + else + { + [readabilityButton setEnabled:FALSE]; + readability_refresh = FALSE; + } +} + +/* +// The designated initializer. Override to perform setup that is required before the view is loaded. +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { + // Custom initialization + } + return self; +} +*/ + +/* +// Implement loadView to create a view hierarchy programmatically, without using a nib. +- (void)loadView { +} +*/ + + +// Implement viewDidLoad to do additional setup after loading the view, typically from a nib. +- (void)viewDidLoad { + prefs = [NSUserDefaults standardUserDefaults]; + [loadingIndicator stopAnimating]; + +// readabilityIcon = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"readability-icon" ofType:@"png"]]; +// readabilityBackIcon = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"readability-back-icon" ofType:@"png"]]; + + readabilityIcon = [[UIImage imageNamed:@"readability-icon.png"] retain]; + readabilityBackIcon = [[UIImage imageNamed:@"readability-back-icon.png"] retain]; + + readabilityButton = [[UIBarButtonItem alloc] initWithImage:readabilityIcon style:UIBarButtonItemStyleBordered target:self action:@selector(readability:)]; + [readabilityButton setStyle:UIBarButtonItemStyleBordered]; +// readabilityButton = [[UIBarButtonItem alloc] initWithTitle:@"Readability" style:UIBarButtonItemStyleBordered target:self action:@selector(readability:)]; + [readabilityButton setEnabled:FALSE]; + self.navigationItem.rightBarButtonItem = readabilityButton; + + // create a very dark blue background for the Webview, otherwise we risk exploding + // the user's retina if they're browsing at night. + NSString *htmlString = @""; + [webView loadHTMLString:htmlString baseURL:nil]; +} + + +/* +// Override to allow orientations other than the default portrait orientation. +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + // Return YES for supported orientations + return (interfaceOrientation == UIInterfaceOrientationPortrait); +} +*/ + +- (void)didReceiveMemoryWarning { + // Releases the view if it doesn't have a superview. + [super didReceiveMemoryWarning]; + + // Release any cached data, images, etc that aren't in use. +} + +- (void)viewDidUnload { + // Release any retained subviews of the main view. + // e.g. self.myOutlet = nil; +} + +- (void)viewWillDisappear:(BOOL)animated +{ + // if the user hits back to comments/posts on the top nav, there's no point using + // up their bandwidth unnecessarily. + [webView stopLoading]; +} + + +- (void)dealloc { + + [super dealloc]; +} + +@end diff --git a/Classes/CommentCell.h b/Classes/CommentCell.h new file mode 100755 index 0000000..029a42f --- /dev/null +++ b/Classes/CommentCell.h @@ -0,0 +1,72 @@ +// +// CommentCell.h +// Alien Blue +// +// Created by Jason Morrissey on 29/03/10. +// Copyright 2010 The Design Shed. All rights reserved. +// + +#import +#import "CommentCellView.h" + +@interface CommentCell : UITableViewCell { + + CommentCellView *commentCellView; + +// IBOutlet UITextView *body; +// IBOutlet UILabel *score; +// IBOutlet UILabel *author; +// IBOutlet UILabel *numReplies; +// IBOutlet UILabel *datetime; +// IBOutlet UIView *viewForBackground; +// IBOutlet UIView *viewForTopBar; +// IBOutlet UIView *viewForCommentOptions; +// IBOutlet UIView *viewForCommentReply; +// IBOutlet UIImageView *levelArrows; +// +// IBOutlet UIButton *topBarButton; +// IBOutlet UIButton *linkButtonTemplate; +// IBOutlet UIToolbar *buttonToolbar; +// +// IBOutlet UIButton *editButton; +// IBOutlet UIButton *parentButton; +// IBOutlet UIButton *voteUpButton; +// IBOutlet UIButton *voteDownButton; +// IBOutlet UIButton *showReplyAreaButton; +// IBOutlet UIButton *submitReply; +// IBOutlet UIButton *cancelReply; +// IBOutlet UITextView *replyTextView; +// +// BOOL collapsed; +} + +//@property (nonatomic, retain) IBOutlet UITextView *body; +//@property (nonatomic, retain) IBOutlet UILabel *numReplies; +//@property (nonatomic, retain) IBOutlet UILabel *score; +//@property (nonatomic, retain) IBOutlet UILabel *author; +//@property (nonatomic, retain) IBOutlet UILabel *datetime; +//@property (nonatomic, retain) IBOutlet UIView *viewForBackground; +//@property (nonatomic, retain) IBOutlet UIView *viewForTopBar; +//@property (nonatomic, retain) IBOutlet UIView *viewForCommentOptions; +//@property (nonatomic, retain) IBOutlet UIView *viewForCommentReply; +//@property (nonatomic, retain) IBOutlet UIImageView *levelArrows; +// +//@property (nonatomic, retain) IBOutlet UIToolbar *buttonToolbar; +//@property (nonatomic, retain) IBOutlet UIButton *topBarButton; +//@property (nonatomic, retain) IBOutlet UIButton *linkButtonTemplate; +// +//@property (nonatomic, retain) IBOutlet UIButton *editButton; +//@property (nonatomic, retain) IBOutlet UIButton *parentButton; +//@property (nonatomic, retain) IBOutlet UIButton *voteUpButton; +//@property (nonatomic, retain) IBOutlet UIButton *voteDownButton; +//@property (nonatomic, retain) IBOutlet UIButton *showReplyAreaButton; +//@property (nonatomic, retain) IBOutlet UIButton *submitReply; +//@property (nonatomic, retain) IBOutlet UIButton *cancelReply; +//@property (nonatomic, retain) IBOutlet UITextView *replyTextView; + +@property (nonatomic, retain) CommentCellView *commentCellView; + +- (void)setCommentWrapper:(CommentWrapper *)commentWrap; +- (void)redisplay; + +@end diff --git a/Classes/CommentCell.m b/Classes/CommentCell.m new file mode 100755 index 0000000..84aad48 --- /dev/null +++ b/Classes/CommentCell.m @@ -0,0 +1,78 @@ +// +// CommentCell.m +// Alien Blue +// +// Created by Jason Morrissey on 29/03/10. +// Copyright 2010 The Design Shed. All rights reserved. +// + +#import "CommentCell.h" + +@implementation CommentCell + +@synthesize commentCellView; + +//@synthesize body; +//@synthesize score; +//@synthesize author; +//@synthesize numReplies; +//@synthesize viewForBackground; +//@synthesize datetime; +//@synthesize buttonToolbar; +//@synthesize viewForTopBar; +//@synthesize topBarButton; +//@synthesize linkButtonTemplate; +//@synthesize viewForCommentOptions; +//@synthesize viewForCommentReply; +//@synthesize parentButton; +//@synthesize voteUpButton; +//@synthesize voteDownButton; +//@synthesize showReplyAreaButton; +//@synthesize submitReply; +//@synthesize cancelReply; +//@synthesize replyTextView; +//@synthesize levelArrows; +//@synthesize editButton; + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + + if (self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier]) { + + CGRect cellViewFrame = CGRectMake(0.0, 0.0, self.contentView.bounds.size.width, self.contentView.bounds.size.height); + commentCellView = [[CommentCellView alloc] initWithFrame:cellViewFrame]; + commentCellView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [self.contentView addSubview:commentCellView]; + [self setSelectionStyle:UITableViewCellSelectionStyleNone]; + [self setClipsToBounds:YES]; + } + return self; +} + +//- (id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier { +// if (self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier]) { +// // Initialization code +// } +// return self; +//} + +- (void)setCommentWrapper:(CommentWrapper *)commentWrap +{ + [commentCellView setCommentWrapper:commentWrap]; +} + +- (void)setSelected:(BOOL)selected animated:(BOOL)animated { + [super setSelected:selected animated:animated]; + + // Configure the view for the selected state +} + + +- (void)dealloc { + [super dealloc]; +} + +- (void)redisplay { + [commentCellView setNeedsDisplay]; +} + +@end diff --git a/Classes/CommentCellView.h b/Classes/CommentCellView.h new file mode 100755 index 0000000..e510c20 --- /dev/null +++ b/Classes/CommentCellView.h @@ -0,0 +1,24 @@ +// +// CommentCellView.h +// Alien Blue +// +// Created by Jason Morrissey on 23/04/10. +// Copyright 2010 The Design Shed. All rights reserved. +// + +#import +#import "CommentWrapper.h" + +@interface CommentCellView : UIView { + + CommentWrapper *commentWrapper; + UITextView * replyTextView; + CGPoint gestureStartPoint; +} + +@property (nonatomic, retain) CommentWrapper *commentWrapper; +@property (nonatomic, retain) UITextView *replyTextView; + +- (void)drawLinkButtons; + +@end diff --git a/Classes/CommentCellView.m b/Classes/CommentCellView.m new file mode 100755 index 0000000..6ef2350 --- /dev/null +++ b/Classes/CommentCellView.m @@ -0,0 +1,524 @@ +// +// CommentCellView.m +// Alien Blue +// +// Created by Jason Morrissey on 23/04/10. +// Copyright 2010 The Design Shed. All rights reserved. +// + +#import "CommentCellView.h" +#import "CommentsTableViewController.h" +#import "Resources.h" +static UIImage *levelArrowImage; + +static UIImage *saveIcon; +static UIImage *saveSelectedIcon; +static UIImage *hideIcon; +static UIImage *hideSelectedIcon; + +static UIImage *voteUpIcon; +static UIImage *voteUpSelectedIcon; +static UIImage *voteDownIcon; +static UIImage *voteDownSelectedIcon; +static UIImage *contextIcon; +static UIImage *replyIcon; +static UIImage *editIcon; + +@implementation CommentCellView + +@synthesize commentWrapper; +@synthesize replyTextView; + ++ (void)initialize { + // Unlikely to have any subclasses, but check class nevertheless. + if (self == [CommentCellView class]) { + NSLog(@"CCV :: CommentCellView initialise in()"); + levelArrowImage = [[UIImage imageNamed:@"level-arrows.png"] retain]; + + voteUpIcon = [[UIImage imageNamed:@"vote-up.png"] retain]; + voteUpSelectedIcon = [[UIImage imageNamed:@"vote-up-selected.png"] retain]; + voteDownIcon = [[UIImage imageNamed:@"vote-down.png"] retain]; + voteDownSelectedIcon = [[UIImage imageNamed:@"vote-down-selected.png"] retain]; + contextIcon = [[UIImage imageNamed:@"comment-parent-icon.png"] retain]; + replyIcon = [[UIImage imageNamed:@"reply.png"] retain]; + editIcon = [[UIImage imageNamed:@"edit-icon.png"] retain]; + + saveIcon = [[UIImage imageNamed:@"star-icon.png"] retain]; + saveSelectedIcon = [[UIImage imageNamed:@"star-selected-icon.png"] retain]; + hideIcon = [[UIImage imageNamed:@"hide-icon.png"] retain]; + hideSelectedIcon = [[UIImage imageNamed:@"hide-selected-icon.png"] retain]; + + } +} + +- (id)initWithFrame:(CGRect)frame { +// NSLog(@"CCV :: initWithFrame in()"); + if (self = [super initWithFrame:frame]) { +// self.opaque = YES; +// self.backgroundColor = [UIColor colorWithRed:0.05 green:0.05 blue:0.2 alpha:1]; + self.backgroundColor = [UIColor clearColor]; + } + return self; +} + +- (void)setCommentWrapper:(CommentWrapper *)newCommentWrapper { + + if (commentWrapper != newCommentWrapper) { + [commentWrapper release]; + commentWrapper = [newCommentWrapper retain]; + } + // May be the same wrapper, but the date may have changed, so mark for redisplay. + [self setNeedsDisplay]; +} + +- (void) handleOptionsTouchesForPoint:(CGPoint) touchPoint +{ + bool isPostInfo = [[[commentWrapper comment] valueForKey:@"comment_index"] intValue] == 0; + + if (isPostInfo) + { + if (touchPoint.x < 50) + [(CommentsTableViewController *) [commentWrapper commentsController] toggleSavePost:[commentWrapper comment]]; + else if (touchPoint.x > 50 && touchPoint.x < 115) + [(CommentsTableViewController *) [commentWrapper commentsController] toggleHidePost:[commentWrapper comment]]; + } + + if (touchPoint.x < 60 && !isPostInfo) + [(CommentsTableViewController *) [commentWrapper commentsController] contextForComment:[commentWrapper comment]]; + else if (touchPoint.x > (self.bounds.size.width / 2 - 20) && + touchPoint.x < (self.bounds.size.width / 2 + 20)) + { + // if the authenticated user clicks this area - activate edit mode, otherwise + // activate standard comment reply. + if ([[[commentWrapper comment] valueForKey:@"comment_type"] isEqualToString:@"me"]) + [(CommentsTableViewController *) [commentWrapper commentsController] editModeForComment:[commentWrapper comment]]; + else + [(CommentsTableViewController *) [commentWrapper commentsController] showReplyAreaForComment:[commentWrapper comment]]; + } + else if (touchPoint.x > (self.bounds.size.width - 100) && + touchPoint.x < (self.bounds.size.width - 40)) + [(CommentsTableViewController *) [commentWrapper commentsController] voteUpComment:[commentWrapper comment]]; + else if (touchPoint.x > (self.bounds.size.width - 40)) + [(CommentsTableViewController *) [commentWrapper commentsController] voteDownComment:[commentWrapper comment]]; + +} + +- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event +{ + gestureStartPoint = [[touches anyObject] locationInView:self]; +} + + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event +{ + NSLog(@"hitTest in"); + CGPoint touchPoint = [[touches anyObject] locationInView:self]; + + // check swipe from on top bar + if (touchPoint.y < 70 && fabs(gestureStartPoint.x - touchPoint.x) > 50) + { + NSLog(@"top bar swiped"); + [(CommentsTableViewController *) [commentWrapper commentsController] collapseToRootForComment:[commentWrapper comment]]; + return; + } + + // top bar button pressed + if (touchPoint.y < 50) + { + [(CommentsTableViewController *) [commentWrapper commentsController] toggleComment:[commentWrapper comment]]; + return; + } + else if (touchPoint.y > (self.bounds.size.height - 50) && + [[commentWrapper comment] objectForKey:@"showOptions"] && + [[[commentWrapper comment] valueForKey:@"showOptions"] boolValue]) + { + NSLog(@"options area clicked"); + [self handleOptionsTouchesForPoint:touchPoint]; + return; + } + else if (touchPoint.y > 50) + { + NSLog(@"body touched at row %d", [[[commentWrapper comment] valueForKey:@"comment_index"] intValue]); + [(CommentsTableViewController *) [commentWrapper commentsController] selectComment:[commentWrapper comment]]; +// [super touchesEnded:touches withEvent:event]; + } +// NSLog(@"touch position : %f", touchPoint.y); + +} + +- (void)drawReplyArea +{ + float bound_height = self.bounds.size.height; + float bound_width = self.bounds.size.width; + + replyTextView = [[[UITextView alloc] initWithFrame:CGRectMake(20, bound_height - 130, bound_width - 40, 70)] retain]; + [replyTextView setText:[[commentWrapper comment] valueForKey:@"replyText"]]; + [replyTextView setDelegate:(CommentsTableViewController *) [commentWrapper commentsController]]; + [replyTextView setTag:[[[commentWrapper comment] valueForKey:@"comment_index"] intValue]]; + [replyTextView setReturnKeyType:UIReturnKeyDone]; + [replyTextView setScrollsToTop:YES]; + [replyTextView setEnablesReturnKeyAutomatically:NO]; + [replyTextView setFont:[Resources secondaryFont]]; + [self addSubview:replyTextView]; + + UIButton * cancelButton = [[UIButton buttonWithType:UIButtonTypeRoundedRect] retain]; + [cancelButton setFrame:CGRectMake(20, bound_height - 45, 90, 30)]; + [cancelButton setTitle:@"Cancel" forState:UIControlStateNormal]; + [cancelButton setTag:[[[commentWrapper comment] valueForKey:@"comment_index"] intValue]]; + [cancelButton addTarget:[commentWrapper commentsController] action:@selector(cancelReplyPressed:) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:cancelButton]; + + UIButton * submitButton = [[UIButton buttonWithType:UIButtonTypeRoundedRect] retain]; + [submitButton setFrame:CGRectMake(bound_width - 110, bound_height - 45, 90, 30)]; + [submitButton setTitle:@"Submit" forState:UIControlStateNormal]; + [submitButton setTag:[[[commentWrapper comment] valueForKey:@"comment_index"] intValue]]; + [submitButton addTarget:[commentWrapper commentsController] action:@selector(submitReplyPressed:) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:submitButton]; + +} + +- (float) drawPostInfo +{ + float vertical_offset = 60; + + NSDictionary * comment = [commentWrapper comment]; + float bound_width = self.bounds.size.width; + +// CGContextRef context = UIGraphicsGetCurrentContext(); +// CGContextSetRGBFillColor(context, 0.0, 0.0, 0.0, 1); +// CGRect rect = CGRectMake(0, 0, self.bounds.size.width, bound_height); +// CGContextFillRect(context, rect); + + [[Resources cNormal] set]; + NSDate * d = [NSDate dateWithTimeIntervalSince1970:[[comment valueForKey:@"created"] doubleValue]]; + NSDateFormatter *df = [[NSDateFormatter alloc] init]; + df.dateStyle = NSDateFormatterMediumStyle; + [[df stringFromDate:d] drawInRect:CGRectMake(15, vertical_offset, bound_width - 30, 20) withFont:[Resources tertiaryFont]]; + [df release]; + + NSString * commentsString = [NSString stringWithFormat:@"%d comments", [[comment valueForKey:@"num_comments"] intValue]]; + [commentsString drawInRect:CGRectMake(bound_width - 35 - ([commentsString length] * 5), vertical_offset, 300, 20) withFont:[Resources tertiaryFont]]; + + vertical_offset += 20; + + [[comment valueForKey:@"url"] drawInRect:CGRectMake(15, vertical_offset, bound_width - 20, 20) withFont:[Resources tertiaryFont]]; + + vertical_offset += 30; + + [[Resources cTitleColor] set]; + CGRect titleFrame = CGRectMake(15, vertical_offset, bound_width - 30, 300 - 20); + + CGSize constraintSize = CGSizeMake(bound_width - 50.0, MAXFLOAT); + CGSize labelSize = [[comment valueForKey:@"title"] sizeWithFont:[Resources mainFont] constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap]; + vertical_offset += labelSize.height + 15; + + [[comment valueForKey:@"title"] drawInRect:titleFrame withFont:[Resources mainFont]]; + + + + vertical_offset -= 60; + + return vertical_offset; + + // [[self superview] setOpaque:FALSE]; +// [self setBackgroundColor:[UIColor clearColor]]; + +// [self setOpaque:FALSE]; +} + +- (void)drawOptions +{ + float bound_height = self.bounds.size.height; + float bound_width = self.bounds.size.width; + float vertical_offset = bound_height - 50; + CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextSetRGBFillColor(context, 0.05, 0.05, 0.05, 0.4); + CGRect rect = CGRectMake(0, vertical_offset, self.bounds.size.width, 50); + CGContextFillRect(context, rect); + NSMutableDictionary * comment = [commentWrapper comment]; + + if ([[comment valueForKey:@"level"] intValue] > 0) + [contextIcon drawAtPoint:CGPointMake(5, vertical_offset + 13)]; + + if ([[comment valueForKey:@"comment_type"] isEqualToString:@"me"]) + [editIcon drawAtPoint:CGPointMake(bound_width / 2 - 15, vertical_offset + 11)]; + else + [replyIcon drawAtPoint:CGPointMake(bound_width / 2 - 15, vertical_offset + 11)]; + + [voteUpIcon drawAtPoint:CGPointMake(bound_width - 100, vertical_offset + 9)]; + [voteDownIcon drawAtPoint:CGPointMake(bound_width - 40, vertical_offset + 11)]; + + if ([[comment valueForKey:@"voteDirection"] intValue] > 0) + { + [voteUpSelectedIcon drawAtPoint:CGPointMake(bound_width - 100, vertical_offset + 9)]; + } else if ([[[commentWrapper comment] valueForKey:@"voteDirection"] intValue] < 0) + { + [voteDownSelectedIcon drawAtPoint:CGPointMake(bound_width - 40, vertical_offset + 11)]; + } + + // draw options for the Post Information cell (like Save and gn) + if ([[comment valueForKey:@"comment_index"] intValue] == 0) + { + if ([[comment valueForKey:@"saved"] boolValue]) + [saveSelectedIcon drawAtPoint:CGPointMake(10, vertical_offset + 9)]; + else + [saveIcon drawAtPoint:CGPointMake(10, vertical_offset + 9)]; + + if ([[comment valueForKey:@"hidden"] boolValue]) + [hideSelectedIcon drawAtPoint:CGPointMake(75, vertical_offset + 10)]; + else + [hideIcon drawAtPoint:CGPointMake(75, vertical_offset + 10)]; + } +} + +- (void)drawLinkButtons +{ + NSMutableDictionary * comment = [commentWrapper comment]; + NSArray * links = [comment objectForKey:@"links"]; + +// NSLog(@"drawLinkButtons in for (%d)", [[comment valueForKey:@"comment_index"] intValue]); + + int link_counter = 1; + + // need to calculate starting point to draw from... (so that the first link appears above + //the others. This is complicated, because the links are bottom-flexy spaced. + + float distance_from_bottom = 0; + for (NSDictionary * link in links) + { + float frame_height; + // overwrite frame_height variable if an image has been loaded (to avoid overlapping + // other links in the same comment. + if ([link objectForKey:@"linkHeight"] && [link objectForKey:@"image"]) + frame_height = [[link valueForKey:@"linkHeight"] floatValue]; + else + frame_height = 33; + distance_from_bottom += frame_height + 10; + } + + if ([comment objectForKey:@"showOptions"] && [[comment valueForKey:@"showOptions"] boolValue]) + { + distance_from_bottom += 50; + } + + if ([comment objectForKey:@"showReplyArea"] && [[comment valueForKey:@"showReplyArea"] boolValue]) + { + distance_from_bottom += 135; + } + + float upto = 0; + for (NSMutableDictionary * link in links) + { + UIButton * linkButton = [[UIButton buttonWithType:UIButtonTypeRoundedRect] retain]; + + CGRect frame = CGRectMake(20, self.bounds.size.height - 65, self.bounds.size.width - 40, 36); + + float frame_height; + if ([link objectForKey:@"linkHeight"] && [link objectForKey:@"image"]) + frame_height = [[link valueForKey:@"linkHeight"] floatValue]; + else + frame_height = 33; + + upto += frame_height + 10; + + frame.origin.y = frame.origin.y - distance_from_bottom + upto - 5; + // frame.origin.y = frame.origin.y - ((frame_height + 8) * ([links count] - link_counter)); + NSString * labelText = [NSString stringWithFormat:@" %d : %@ ", + [[link valueForKey:@"linkTag"] intValue], [link valueForKey:@"description"]]; + + + [linkButton setContentHorizontalAlignment:UIControlContentHorizontalAlignmentLeft]; + [linkButton setFrame:frame]; + [linkButton setHidden:FALSE]; + [linkButton setTitle:labelText forState:UIControlStateNormal]; + [linkButton setAutoresizesSubviews:YES]; + [linkButton setBackgroundImage:[Resources barImage] forState:UIControlStateNormal]; + [linkButton.titleLabel setFont:[Resources secondaryFont]]; + [linkButton setTitleColor:[Resources cTitleColor] forState:UIControlStateNormal]; + [linkButton setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; + + [self addSubview:linkButton]; +// [self sendSubviewToBack:linkButton]; +// [imageIcon drawAtPoint:CGPointMake(15,frame.origin.y - 5)]; +// [imageIcon drawInRect:CGRectMake(15,frame.origin.y - 5,50,50)]; + +// [imageIcon drawAtPoint:CGPointMake(15,frame.origin.y - 5)]; +// [imageIcon drawAtPoint:CGPointMake(15,frame.origin.y - 5) blendMode:kCGBlendModeDestinationOver alpha:1]; + UIImageView * icon = [[UIImageView alloc] init]; + + if ([[link valueForKey:@"type"] isEqualToString:@"video"]) + [icon setImage:[Resources videoIcon]]; + else if ([[link valueForKey:@"type"] isEqualToString:@"image"]) + [icon setImage:[Resources imageIcon]]; + else + [icon setImage:[Resources articleIcon]]; + + + [icon setFrame:CGRectMake(15,frame.origin.y - 5,50,50)]; +// UILabel * linkIdentifier = [[UILabel alloc] initWithFrame:CGRectMake(15,frame.origin.y - 7,45,45)]; +// [linkIdentifier setTextAlignment:UITextAlignmentCenter]; +// [linkIdentifier setText:[[link valueForKey:@"linkTag"] stringValue]]; +// [linkIdentifier setBackgroundColor:[UIColor clearColor]]; +// [linkIdentifier setTextColor:[UIColor whiteColor]]; +// [linkIdentifier setFont:secondaryFont]; + + // this is to make sure that open images don't have an unnecessary image + // icon + if (![link objectForKey:@"image"]) + { + [self addSubview:icon]; +// [self addSubview:linkIdentifier]; + } + + UIButton * moreButton = [[UIButton buttonWithType:UIButtonTypeDetailDisclosure] retain]; + CGRect moreframe = [moreButton frame]; + moreframe.origin.x = [linkButton frame].size.width - moreframe.size.width + 12; + moreframe.origin.y = frame.origin.y + 2; + [moreButton setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin]; + [moreButton setFrame:moreframe]; + [moreButton setTransform:CGAffineTransformIdentity]; + // [linkButton addSubview:moreButton]; + [self addSubview:moreButton]; + [linkButton setTag:[[link valueForKey:@"link_id"] integerValue]]; + [moreButton setTag:[[link valueForKey:@"link_id"] integerValue]]; + [link setValue:[NSString stringWithFormat:@"%f",frame.size.height] forKey:@"linkHeight"]; + [linkButton addTarget:[commentWrapper commentsController] action:@selector(linkClicked:) forControlEvents:UIControlEventTouchUpInside]; + [moreButton addTarget:[commentWrapper commentsController] action:@selector(moreButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; + if ([link objectForKey:@"image"]) + { + UIImage * image = (UIImage *) [link objectForKey:@"image"]; + UIImageView * imageView = [[UIImageView alloc] initWithImage:image]; + [imageView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin]; + [imageView setContentMode:UIViewContentModeScaleAspectFit]; + CGRect imageFrame = [imageView frame]; + CGRect linkFrame = [linkButton frame]; + CGFloat aspect_ratio = [image size].width / [image size].height; + // CGFloat aspect_ratio = imageFrame.size.width / imageFrame.size.height; + imageFrame.origin.y += linkFrame.size.height; + imageFrame.origin.x = 5; + imageFrame.size.width = roundf(linkFrame.size.width - 10); + imageFrame.size.height = roundf(imageFrame.size.width / aspect_ratio); + [imageView setFrame:imageFrame]; + // + // [linkButton setContentVerticalAlignment:UIControlContentVerticalAlignmentTop]; + [linkButton addSubview:imageView]; + [linkButton setBackgroundImage:nil forState:UIControlStateNormal]; + + // We need the roundf() here, otherwise we may end up sizing the picture to something + // like 1000.1234 pixels. This would cause aliasing in other elements that are below + // the image. + linkFrame.size.height = roundf(imageFrame.size.width / aspect_ratio + linkFrame.size.height + 5); + + linkFrame.origin.y -= [imageView frame].size.height; + moreframe.origin.y = linkFrame.origin.y; + [moreButton setFrame:moreframe]; + [link setValue:[NSString stringWithFormat:@"%f",linkFrame + .size.height] forKey:@"linkHeight"]; + [linkButton setTitle:@"" forState:UIControlStateNormal]; + [linkButton setFrame:linkFrame]; + [linkButton setEnabled:FALSE]; + [linkButton setClipsToBounds:TRUE]; + } + link_counter++; + } +} + +- (void)drawRect:(CGRect)rect { + + NSDictionary * comment = [commentWrapper comment]; +// NSLog(@"CommentCellView :: drawRect :: Row [%d]",[[[self superview] superview] tag]); + + // remove cached subviews + for (UIView * subview in [self subviews]) + { + [subview removeFromSuperview]; + [subview release]; + } + + CGRect contentRect = self.bounds; + + float vertical_offset = 65; + // top comment is always the post itself + if ([[comment valueForKey:@"comment_index"] intValue] == 0) + { + vertical_offset += [self drawPostInfo]; + } + + [[Resources barImage] drawInRect:CGRectMake(-20, 0, contentRect.size.width + 40, 50)]; + + // don't extend beyond 3 levels, otherwise we can indent indefinitely + int level = [[comment valueForKey:@"level"] intValue]; + if (level > 0) + { + if (level > 3) level = 3; + CGImageRef imageRef = CGImageCreateWithImageInRect([levelArrowImage CGImage], CGRectMake(0, 0, level * 25, 30)); + UIImage * arrImage = [UIImage imageWithCGImage:imageRef]; + [arrImage drawAtPoint:CGPointMake(0, 12)]; + CGImageRelease(imageRef); + + } + else level = 0; + + if ([[comment valueForKey:@"comment_type"] isEqualToString:@"op"]) + [[Resources cOrange] set]; + else if ([[comment valueForKey:@"comment_type"] isEqualToString:@"me"]) + [[Resources cGreen] set]; + else + [[Resources cTitleColor] set]; + + float author_horizontal_offset = level * 25 + 10; + + [[comment valueForKey:@"author"] drawInRect:CGRectMake(author_horizontal_offset, 15, 180 - author_horizontal_offset, 30) withFont:[Resources secondaryFont] lineBreakMode:UILineBreakModeClip]; + +// drawAtPoint:CGPointMake(,15) withFont:secondaryFont]; + +// [[comment valueForKey:@"author"] drawAtPoint:CGPointMake(,15) withFont:secondaryFont]; + + [[Resources cGreen] set]; + [[[comment valueForKey:@"score"] stringValue] drawAtPoint:CGPointMake(contentRect.size.width - 125,15) withFont:[Resources secondaryFont]]; + + [[Resources cTitleColor] set]; + NSString * replies; + int numReplies = [[comment valueForKey:@"numReplies"] intValue]; + if ([[comment valueForKey:@"comment_index"] intValue] == 0) + replies = @"Post"; + else if (numReplies == 0) + replies = @"No replies"; + else if (numReplies == 1) + replies = @"1 reply"; + else + replies = [NSString stringWithFormat:@"%d replies",numReplies]; + + [replies drawInRect:CGRectMake(contentRect.size.width - 100, 15, 90, 30) withFont:[Resources secondaryFont] lineBreakMode:UILineBreakModeTailTruncation alignment:UITextAlignmentRight]; + + if ([[comment valueForKey:@"visibility"] isEqualToString:@"collapsed"] || + [[comment valueForKey:@"visibility"] isEqualToString:@"hidden"]) + return; + + [[Resources cNormal] set]; + + if([comment objectForKey:@"links"]) + [self drawLinkButtons]; + + if ([comment objectForKey:@"showOptions"] && [[comment valueForKey:@"showOptions"] boolValue]) + [self drawOptions]; + + if ([comment objectForKey:@"showReplyArea"] && [[comment valueForKey:@"showReplyArea"] boolValue]) + [self drawReplyArea]; + + + [[Resources cNormal] set]; + [[comment valueForKey:@"body"] drawInRect:CGRectMake(15, vertical_offset, contentRect.size.width - 30, contentRect.size.height - 20) withFont:[Resources mainFont]]; + + +} + + +- (void)dealloc { +// [linkButtons release]; + [commentWrapper release]; + [super dealloc]; +} + + +@end diff --git a/Classes/CommentWrapper.h b/Classes/CommentWrapper.h new file mode 100755 index 0000000..30408f7 --- /dev/null +++ b/Classes/CommentWrapper.h @@ -0,0 +1,17 @@ +// +// CommentWrapper.h +// Alien Blue +// +// Created by Jason Morrissey on 23/04/10. +// Copyright 2010 The Design Shed. All rights reserved. +// +@class CommentsTableViewController; + +@interface CommentWrapper : NSObject { + NSMutableDictionary * comment; + CommentsTableViewController * commentsController; +} +- initWithComment:(NSMutableDictionary *)cDictionary forController:(UIViewController *) viewController; +@property (nonatomic, retain) NSMutableDictionary *comment; +@property (nonatomic, retain) CommentsTableViewController *commentsController; +@end diff --git a/Classes/CommentWrapper.m b/Classes/CommentWrapper.m new file mode 100755 index 0000000..6963b1a --- /dev/null +++ b/Classes/CommentWrapper.m @@ -0,0 +1,46 @@ +// +// CommentWrapper.m +// Alien Blue +// +// Created by Jason Morrissey on 23/04/10. +// Copyright 2010 The Design Shed. All rights reserved. +// + +#import "CommentWrapper.h" + + +@implementation CommentWrapper +@synthesize comment; +@synthesize commentsController; + ++ (void)initialize { + // Unlikely to have any subclasses, but check class nevertheless. + if (self == [CommentWrapper class]) { +// today = [NSLocalizedString(@"Today", "Today") retain]; +// tomorrow = [NSLocalizedString(@"Tomorrow", "Tomorrow") retain]; +// yesterday = [NSLocalizedString(@"Yesterday", "Yesterday") retain]; +// +// q1Image = [[UIImage imageNamed:@"12-6AM.png"] retain]; +// q2Image = [[UIImage imageNamed:@"6-12AM.png"] retain]; +// q3Image = [[UIImage imageNamed:@"12-6PM.png"] retain]; +// q4Image = [[UIImage imageNamed:@"6-12PM.png"] retain]; + } +} + + +- initWithComment:(NSMutableDictionary *)cDictionary forController:(UIViewController *) viewController { + + if (self = [super init]) { + comment = [cDictionary retain]; + commentsController = (CommentsTableViewController* ) viewController; + } + return self; +} + + +- (void)dealloc { + [super dealloc]; +} + + +@end diff --git a/Classes/CommentsTableViewController.h b/Classes/CommentsTableViewController.h new file mode 100755 index 0000000..5ce7b57 --- /dev/null +++ b/Classes/CommentsTableViewController.h @@ -0,0 +1,71 @@ +// +// CommentsTableViewController.h +// Alien Blue +// +// Created by Jason Morrissey on 29/03/10. +// Copyright 2010 The Design Shed. All rights reserved. +// + +#import +#import "JSON.h" +#import "CommentCell.h" +#import "RedditAPI.h" + +@interface CommentsTableViewController : UITableViewController { + + RedditAPI * redAPI; + NSMutableArray * comments; + NSMutableArray * bodyHeightCache; + NSTimer * progressTimer; + NSMutableArray *allLinks; +// int link_counter; + + NSMutableArray *flatComments; // Search friendly list +// NSMutableArray *filteredListContent; // The content filtered as a result of a search. + + NSMutableDictionary *linkImageDownloadQueue; + NSMutableDictionary * post; + + int selected_row; + int editing_row; + + // The saved state of the search UI if a memory warning removed the view. + NSString *savedSearchTerm; + NSInteger savedScopeButtonIndex; + BOOL searchWasActive; + UIImage * button_background; + UIImage * voteUpIcon; + UIImage * voteDownIcon; + UIImage * voteUpSelectedIcon; + UIImage * voteDownSelectedIcon; + + NSUserDefaults * prefs; + CFTimeInterval stime; + CFTimeInterval etime; +} + +@property (nonatomic, retain) NSMutableArray *flatComments; +//@property (nonatomic, retain) NSMutableArray *filteredListContent; +@property (nonatomic, copy) NSString *savedSearchTerm; +@property (nonatomic) NSInteger savedScopeButtonIndex; +@property (nonatomic) BOOL searchWasActive; + +- (NSMutableDictionary *) reformat:(NSMutableDictionary *) comment; +- (void) loadComments; +- (void) loadTestComments; +- (void) afterCommentReply:(NSString *) commentID; +- (void) toggleComment:(NSMutableDictionary *) comment; +- (void) contextForComment:(NSMutableDictionary *) comment; +- (void) voteUpComment:(NSMutableDictionary *) comment; +- (void) voteDownComment:(NSMutableDictionary *) comment; +- (void) showReplyAreaForComment:(NSMutableDictionary *) comment; +- (void) editModeForComment:(NSMutableDictionary *) comment; + +- (void) toggleSavePost:(NSMutableDictionary *) ps; +- (void) toggleHidePost:(NSMutableDictionary *) ps; +- (void) selectComment:(NSMutableDictionary *) comment; +- (void) collapseToRootForComment:(NSMutableDictionary *) comment; +- (int) getCommentRowByName:(NSString *) cname; +- (IBAction)moreButtonClicked:(id)sender; +- (NSMutableDictionary *) getCommentById:(NSString *) cId; +@end diff --git a/Classes/CommentsTableViewController.m b/Classes/CommentsTableViewController.m new file mode 100755 index 0000000..c0af0b6 --- /dev/null +++ b/Classes/CommentsTableViewController.m @@ -0,0 +1,1777 @@ +// +// CommentsTableViewController.m +// Alien Blue +// +// Created by Jason Morrissey on 29/03/10. +// Copyright 2010 The Design Shed. All rights reserved. +// + +#import "CommentsTableViewController.h" +#import "NavigationController.h" +#import "AlienBlueAppDelegate.h" + +@implementation CommentsTableViewController + +@synthesize flatComments, savedSearchTerm, savedScopeButtonIndex, searchWasActive; + +float CommentsLoadProgressValue = 0; + +- (void) scrollToRow:(int) row +{ + NSIndexPath * ind = [NSIndexPath indexPathForRow:row inSection:0]; + [[self tableView] scrollToRowAtIndexPath:ind atScrollPosition:UITableViewScrollPositionTop animated:YES]; +} + +- (void) clearHeightCacheForRow:(int) row +{ + [bodyHeightCache replaceObjectAtIndex:row withObject:[NSNumber numberWithFloat:-1]]; +} + +- (void) refreshRow:(int) row +{ + NSMutableArray * affectedRows = [[NSMutableArray alloc] init]; + NSIndexPath * ind = [NSIndexPath indexPathForRow:row inSection:0]; + [affectedRows addObject:ind]; + [[self tableView] reloadRowsAtIndexPaths:affectedRows withRowAnimation:UITableViewRowAnimationNone]; + [affectedRows release]; +} + + +- (void) enableProgressBar +{ + CommentsLoadProgressValue = 0; + progressTimer = [NSTimer scheduledTimerWithTimeInterval:1 + target:self + selector:@selector(updateProgressBar:) + userInfo:nil + repeats:YES]; + +} + +- (void) refreshProgressBar:(float) pr +{ + CommentsLoadProgressValue = pr; + int row = [[self tableView] numberOfRowsInSection:0] - 1; + [self refreshRow:row]; +} + +- (void) completeProgressBar +{ + [self refreshProgressBar:0.0]; + if (progressTimer) + { +// [progressTimer invalidate]; + progressTimer = nil; + } +} + +-(void) updateProgressBar: (NSTimer *) theTimer +{ + if (CommentsLoadProgressValue <= 0.9) + { + [self refreshProgressBar:(CommentsLoadProgressValue + 0.14)]; + } + else + { +// [theTimer invalidate]; + theTimer = nil; + } +} + +- (NSMutableArray *) parseBodyForUndescribedLinks:(NSMutableDictionary *) comment +{ + NSMutableString * body = [[comment valueForKey:@"body"] copy]; +// NSMutableArray * links = [[NSMutableArray alloc] init]; + NSMutableArray * links = [NSMutableArray arrayWithCapacity:0]; + int bodyLength = [body length]; + int pos = 0; + int start_link_pos = 0; + int end_link_pos = 0; + int link_counter = 0; + BOOL linksAvailable = TRUE; +// NSLog(@"Body Text: %@",body); + while (linksAvailable) { + start_link_pos = [body rangeOfString:@"http://" options:NSCaseInsensitiveSearch range:NSMakeRange (pos, bodyLength - pos)].location; + end_link_pos = [body rangeOfString:@" " options:NSCaseInsensitiveSearch range:NSMakeRange (start_link_pos, bodyLength - start_link_pos)].location; + // also try newline character if space is not found + int new_line_pos = [body rangeOfString:@"\n" options:NSCaseInsensitiveSearch range:NSMakeRange (start_link_pos, bodyLength - start_link_pos)].location; + // use the one that is closest to the http + if (new_line_pos != NSNotFound) + { + if (new_line_pos < end_link_pos) + end_link_pos = new_line_pos; + } + + + if (start_link_pos != NSNotFound && end_link_pos != NSNotFound) + { + link_counter++; + NSString * linkURL = [body substringWithRange:NSMakeRange(start_link_pos, end_link_pos - start_link_pos)]; + + // we need to ignore described links here, because they're already processed in another + // method.. we can tell described links, because they will have a ")" when processed here + if ([linkURL rangeOfString:@")" options:NSCaseInsensitiveSearch].location == NSNotFound) + { + NSMutableDictionary * link = [[NSMutableDictionary alloc] init]; + [link setValue:[comment valueForKey:@"comment_index"] forKey:@"comment_index"]; + [link setValue:[NSString stringWithString:linkURL] forKey:@"description"]; + [link setValue:[NSString stringWithString:linkURL] forKey:@"original_url"]; + [link setValue:[RedditAPI getLinkType:linkURL] forKey:@"type"]; + [link setValue:[RedditAPI fixImgurLink:linkURL] forKey:@"url"]; + [link setValue:@"NO" forKey:@"isDescribed"]; + [link setValue:[NSNumber numberWithInt:link_counter] forKey:@"linkTag"]; + [link setValue:[NSString stringWithFormat:@"%d",[allLinks count]] forKey:@"link_id"]; +// [link setValue:[[NSNumber alloc] initWithInt:link_counter] forKey:@"linkTag"]; +// [link setValue:[[NSString alloc] initWithFormat:@"%d",[allLinks count]] forKey:@"link_id"]; + [allLinks addObject:link]; + [links addObject:link]; + } + pos = end_link_pos; + } + else + linksAvailable = FALSE; + } + [body release]; + return links; +} + +- (NSMutableArray *) parseBodyForDescribedLinks:(NSMutableDictionary *) comment +{ + NSMutableString * body = [[comment valueForKey:@"body"] copy]; +// NSMutableArray * links = [[NSMutableArray alloc] init]; + NSMutableArray * links = [NSMutableArray arrayWithCapacity:0]; + int bodyLength = [body length]; + int pos = 0; + // left square bracket position + int lsqb = 0; + int rsqb = 0; + + // left round bracket + int lrb = 0; + int rrb = 0; + int link_counter = 0; + BOOL linksAvailable = TRUE; + + while (linksAvailable) { + lsqb = [body rangeOfString:@"[" options:NSCaseInsensitiveSearch range:NSMakeRange (pos, bodyLength - pos)].location + 1; + rsqb = [body rangeOfString:@"](" options:NSCaseInsensitiveSearch range:NSMakeRange (lsqb, bodyLength - lsqb)].location; + lrb = rsqb + 2; + rrb = [body rangeOfString:@")" options:NSCaseInsensitiveSearch range:NSMakeRange (lrb, bodyLength - lrb)].location; + if (lsqb != NSNotFound && rsqb != NSNotFound && rrb != NSNotFound) + { + link_counter++; + NSString * linkName = [body substringWithRange:NSMakeRange(lsqb, rsqb - lsqb)]; + NSString * linkURL = [body substringWithRange:NSMakeRange(lrb, rrb - lrb)]; + NSMutableDictionary * link = [[NSMutableDictionary alloc] init]; + [link setValue:[comment valueForKey:@"comment_index"] forKey:@"comment_index"]; + [link setValue:[linkName copy] forKey:@"description"]; + [link setValue:[linkURL copy] forKey:@"original_url"]; + [link setValue:[RedditAPI getLinkType:linkURL] forKey:@"type"]; + [link setValue:[RedditAPI fixImgurLink:linkURL] forKey:@"url"]; + [link setValue:[[NSNumber alloc] initWithInt:link_counter] forKey:@"linkTag"]; + + // some links have a description that is the same as the URL + // technically these are not described. + if ([linkName isEqualToString:linkURL]) + [link setValue:@"NO" forKey:@"isDescribed"]; + else + [link setValue:@"YES" forKey:@"isDescribed"]; + + [link setValue:[NSString stringWithFormat:@"%d",[allLinks count]] forKey:@"link_id"]; + [allLinks addObject:link]; + + [links addObject:link]; +// [link release]; + [linkName release]; + [linkURL release]; + pos = rrb; + } + else + linksAvailable = false; + } + [body release]; + return links; +} + +- (NSMutableArray *) processLinks:(NSMutableDictionary *) comment +{ + NSMutableArray * links = [self parseBodyForDescribedLinks:comment]; + + // if the poster doesn't describe links with [...](http...) look for + // undescribed links. +// if ([links count] == 0) + [links addObjectsFromArray:[self parseBodyForUndescribedLinks:comment]]; + + return links; +} + + +- (NSMutableDictionary *) reformat:(NSMutableDictionary *) comment +{ + // add a newline so that comments that contain only links can be identified + // as the end of a url is determined by a space or newline character. + + NSMutableString * body = [NSMutableString stringWithString:[comment valueForKey:@"body"]]; + [body appendString:@" "]; + [comment setValue:body forKey:@"body"]; + NSArray * links = [self processLinks:comment]; + + for (NSDictionary * link in links) + { + NSString * linkstr; + NSString * replacement; + if ([[link valueForKey:@"isDescribed"] boolValue]) + { + linkstr = [NSString stringWithFormat:@"[%@](%@)",[link valueForKey:@"description"], [link valueForKey:@"original_url"]]; + replacement = [NSString stringWithFormat:@"%@ [%d]",[link valueForKey:@"description"], [[link valueForKey:@"linkTag"] intValue]]; + + } else + { + linkstr = [NSString stringWithFormat:@"%@",[link valueForKey:@"original_url"]]; + replacement = [NSString stringWithFormat:@"[%d]",[[link valueForKey:@"linkTag"] intValue]]; + } + + // remove the body entirely if all it contains is a link (as we'll be showing these as buttons): + if (abs([linkstr length] - [body length]) < 5) + { + [body setString:@""]; + } + else + { + [body replaceOccurrencesOfString:linkstr withString:replacement options:NSCaseInsensitiveSearch range:NSMakeRange(0, [body length])]; + } + } + [body replaceOccurrencesOfString:@">" withString:@">" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [body length])]; + [body replaceOccurrencesOfString:@"<" withString:@"<" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [body length])]; + [body replaceOccurrencesOfString:@"&" withString:@"&" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [body length])]; + + [comment setValue:body forKey:@"body"]; + [comment setValue:links forKey:@"links"]; + + return comment; +} + + + + +// iterate through comments, and add them to the comments array +- (void) processCommentThread:(NSMutableDictionary *) comment withLevel: (int) level withParents: (NSString *) parents +{ + // on occassion, the Reddit API sends blank IDs for comments, and we need to ignore such comments + // otherwise they will cause AlienBlue to crash. + if(![comment valueForKey:@"id"]) + return; + + [comment setValue:[NSNumber numberWithInt:[comments count]] forKey:@"comment_index"]; + [comment setValue:[NSString stringWithFormat:@"%d",level] forKey:@"level"]; + + + if ([[comment valueForKey:@"author"] isEqualToString:[post valueForKey:@"author"]]) + [comment setValue:@"op" forKey:@"comment_type"]; + else if ([[comment valueForKey:@"author"] isEqualToString:[redAPI authenticatedUser]]) + [comment setValue:@"me" forKey:@"comment_type"]; + else + [comment setValue:@"normal" forKey:@"comment_type"]; + + // initialise vote direction + int voteDirection = 0; + if(![[comment objectForKey:@"likes"] isKindOfClass:[NSNull class]] && [[comment valueForKey:@"likes"] boolValue]) + voteDirection = 1; + else if(![[comment objectForKey:@"likes"] isKindOfClass:[NSNull class]] && ![[comment valueForKey:@"likes"] boolValue]) + voteDirection = -1; + [comment setValue:[NSNumber numberWithInt:voteDirection] forKey:@"voteDirection"]; + + // calculate score (for some reason, the live reddit.com server doesn't compute the score + // for comments, so we do this manually: + int ups = [[comment valueForKey:@"ups"] intValue]; + int downs = [[comment valueForKey:@"downs"] intValue]; + [comment setValue:[NSNumber numberWithInt:(ups - downs)] forKey:@"score"]; + + + comment = [self reformat:comment]; + + [comments addObject:comment]; + +// [parents appendString:@":"]; +// [parents appendString:[comment valueForKey:@"id"]]; + NSString * nParents = [[parents stringByAppendingString:@":"] stringByAppendingString:[comment valueForKey:@"id"]]; + +// NSLog(nParents); + [comment setValue:nParents forKey:@"parents"]; + int numReplies = 0; + if (![[comment objectForKey:@"replies"] isKindOfClass:[NSString class]] ) + { + NSArray * rawComments = [[[comment objectForKey:@"replies"] objectForKey:@"data"] objectForKey:@"children"]; + // [comment setValue:[NSString stringWithFormat:@"%d",[rawComments count]] forKey:@"numReplies"]; + for (NSDictionary * rawComment in rawComments ) + { + // exclude the "more..." threads + if (![[rawComment valueForKey:@"kind"] isEqualToString:@"more"]) + { + [self processCommentThread:[rawComment objectForKey:@"data"] withLevel:level+1 withParents:nParents]; + numReplies ++; + } + } + } + [comment setValue:[NSString stringWithFormat:@"%d",numReplies] forKey:@"numReplies"]; +} + +- (void) processComments:(NSMutableDictionary *) data +{ + NSArray * rawComments = [[data objectForKey:@"data"] objectForKey:@"children"]; + for (NSMutableDictionary * rawComment in rawComments ) + { + if (![[rawComment valueForKey:@"kind"] isEqualToString:@"more"]) + [self processCommentThread:[rawComment objectForKey:@"data"] withLevel:0 withParents:@""]; + } +} + + + +- (IBAction)apiCommentsResponse:(id)sender +{ + + NSLog(@"-- apiCommentsResponse in()"); + stime = CFAbsoluteTimeGetCurrent(); + + NavigationController * nc = (NavigationController *) [[self parentViewController] parentViewController]; + NSMutableDictionary *comment_data = [sender objectAtIndex:1]; + + post = [NSMutableDictionary dictionaryWithDictionary:[[[[[sender objectAtIndex:0] objectForKey:@"data"] objectForKey:@"children"] objectAtIndex:0] objectForKey:@"data"]]; + + NSMutableString * post_body = [NSMutableString stringWithString:@""]; + + if ([RedditAPI isImageLink:[post valueForKey:@"url"]]) + { + [post_body setString:[post valueForKey:@"url"]]; +// post_body = [[post valueForKey:@"url"] copy]; + } else if ([post objectForKey:@"selftext"] && [[post objectForKey:@"selftext"] length]) + { + [post_body setString:[post valueForKey:@"selftext"]]; +// post_body = [[post valueForKey:@"selftext"] copy]; + } + + [post setValue:post_body forKey:@"body"]; + [self processCommentThread:post withLevel:0 withParents:@""]; + + [self processComments:comment_data]; + + NSLog(@"-- comment list response --"); + + // we go above the [comments count] to take into account the post info + // cell in the first row + for (int i=0; i<([comments count] + 1); i++) + [bodyHeightCache addObject:[NSNumber numberWithFloat:-1]]; + + selected_row = -1; + editing_row = -1; + +// filteredListContent = [[NSMutableArray alloc] init]; + + NSLog(@"Imported Comments : [%d]", [comments count]); + + [[self tableView] reloadData]; + [self completeProgressBar]; + + etime = CFAbsoluteTimeGetCurrent(); + + NSLog(@"time to import comments : %f", etime - stime); + + if ([RedditAPI isImageLink:[post valueForKey:@"url"]]) + { + // the following automatically loads the inline image to give + // context to the comments +// UIButton * button = [[UIButton alloc] init]; +// [button setTag:0]; +// NSLog(@"--image url is : %@", [post valueForKey:@"url"]); +// [self linkClicked:button]; + } + + [Resources processPost:post]; + +// [[nc post] release]; + [nc setPost:post]; + [nc refreshVotingSegment]; + + + + + if ([nc replyCommentID] && [[nc replyCommentID] length] > 0) + { +// NSLog(@"[*] scrolling to comment with id:%@", [nc replyCommentID]); + int replyRow = [self getCommentRowByName:[nc replyCommentID]]; +// NSLog(@"[*] scrolling to row : %d", replyRow); + [self scrollToRow:replyRow]; + [nc setReplyCommentID:nil]; + } + +// [self scrollToRow:52]; +// [self testTableDrawPerformanceStart]; +// [comment_data release]; +} + +- (void) loadTestComments +{ + if (progressTimer) + progressTimer = nil; + + [self enableProgressBar]; + [comments release]; + [allLinks release]; + [bodyHeightCache release]; + comments = [[NSMutableArray alloc] init]; + allLinks = [[NSMutableArray alloc] init]; + bodyHeightCache = [[NSMutableArray alloc] init]; + + +// NavigationController * nc = (NavigationController *) [[self parentViewController] parentViewController]; + + SBJSON *parser = [[SBJSON alloc] init]; + + NSString *jsonfile = [[NSBundle mainBundle] pathForResource:@"mitch" ofType:@"json"]; + + NSString *json_string = [[NSString alloc] initWithContentsOfFile:jsonfile]; + NSMutableDictionary *data = [[parser objectWithString:json_string error:nil] objectAtIndex:1]; + [self apiCommentsResponse:data]; +} + + +- (void) resetMemory +{ +// if (comments) +// { +// for (NSMutableDictionary * comment in comments) +// { +// NSMutableArray * links = (NSMutableArray *) [comment objectForKey:@"links"]; +// if (links) +// { +// for (NSDictionary * link in links) +// [link release]; +// } +// } +// } + for (NSMutableDictionary * link in allLinks) + [link release]; + + [comments release]; + [allLinks release]; + [bodyHeightCache release]; +} + +- (void) loadComments +{ + if (progressTimer) + progressTimer = nil; + + [self enableProgressBar]; + + [self resetMemory]; + + + comments = [[NSMutableArray alloc] init]; + allLinks = [[NSMutableArray alloc] init]; + bodyHeightCache = [[NSMutableArray alloc] init]; + + + NavigationController * nc = (NavigationController *) [[self parentViewController] parentViewController]; +// NSString * post_id = [nc postId]; + NSString * post_id = [[nc post] objectForKey:@"id"]; + + NSLog(@"loading comments for post with id [%@]",post_id); + +// RedditAPI * redAPI = [(NeuRedditAppDelegate *) [[UIApplication sharedApplication] delegate] redditAPI]; + [redAPI fetchCommentsForPostID:post_id callBackTarget:self]; + [[self tableView] reloadData]; +} + +/* +- (id)initWithStyle:(UITableViewStyle)style { + // Override initWithStyle: if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad. + if (self = [super initWithStyle:style]) { + } + return self; +} +*/ + +- (void) loadAllImages +{ + if (![MKStoreManager isProUpgraded]) + { + NSString *message = [[NSString alloc] initWithFormat: @"This feature allows you to view all images in a thread with a single tap. You can upgrade to the PRO version in the \"Settings\" panel."]; + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Show All Images\n(PRO Feature)" + message:message + delegate:nil + cancelButtonTitle:@"Ok" + otherButtonTitles:nil]; + [alert show]; + [alert release]; + return; + } + + NSString *message = [[NSString alloc] initWithFormat: @"Loading of all linked images can use a significant amount of Internet data. Are you sure you want to continue?"]; + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Show all images" + message:message + delegate:self + cancelButtonTitle:@"No" + otherButtonTitles:@"Yes",nil]; + [alert setTag:3]; + [alert show]; + [alert release]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + prefs = [NSUserDefaults standardUserDefaults]; + redAPI = [(AlienBlueAppDelegate *) [[UIApplication sharedApplication] delegate] redditAPI]; + + if (self.savedSearchTerm) + { + [self.searchDisplayController setActive:self.searchWasActive]; + [self.searchDisplayController.searchBar setSelectedScopeButtonIndex:self.savedScopeButtonIndex]; + [self.searchDisplayController.searchBar setText:savedSearchTerm]; + + self.savedSearchTerm = nil; + } + +// voteUpIcon = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"vote-up" ofType:@"png"]]; +// voteDownIcon = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"vote-down" ofType:@"png"]]; +// voteUpSelectedIcon = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"vote-up-selected" ofType:@"png"]]; +// voteDownSelectedIcon = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"vote-down-selected" ofType:@"png"]]; + + UIImage * extraOptionsIcon = [[UIImage imageNamed:@"more-options-icon.png"] retain]; + UIBarButtonItem *extraOptionsBarItem = [[[UIBarButtonItem alloc] initWithImage:extraOptionsIcon style:UIBarButtonItemStyleBordered target:self action:@selector(popupExtraOptionsActionSheet:)] autorelease]; + self.navigationItem.rightBarButtonItem = extraOptionsBarItem; + + +// [self loadTestComments]; + + NSLog(@"loaded"); + // Uncomment the following line to display an Edit button in the navigation bar for this view controller. + // self.navigationItem.rightBarButtonItem = self.editButtonItem; +} + + +/* +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; +} +*/ + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + NSLog(@"appeared"); + + // refresh look of visible rows (e.g. if we come back after change to night-mode) + [[self tableView] reloadRowsAtIndexPaths:[[self tableView] indexPathsForVisibleRows] withRowAnimation:NO]; + + NavigationController * nc = (NavigationController *) [[self parentViewController] parentViewController]; +// self.navigationItem.titleView = [nc createVotingSegmentForView:self]; + if ([nc shouldRefreshComments]) + { + [nc setShouldRefreshComments:FALSE]; + NSIndexPath * ind = [NSIndexPath indexPathForRow:0 inSection:0]; + [[self tableView] scrollToRowAtIndexPath:ind atScrollPosition:UITableViewScrollPositionTop animated:NO]; + [self loadComments]; + } + + [[self tableView] setScrollsToTop:[[prefs valueForKey:@"allow_status_bar_scroll"] boolValue]]; +} + +/* +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; +} +*/ + +- (void)viewDidDisappear:(BOOL)animated { + // save the state of the search UI so that it can be restored if the view is re-created + self.searchWasActive = [self.searchDisplayController isActive]; + self.savedSearchTerm = [self.searchDisplayController.searchBar text]; + self.savedScopeButtonIndex = [self.searchDisplayController.searchBar selectedScopeButtonIndex]; + [super viewDidDisappear:animated]; +} + + +- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation +{ + NSLog(@"comments rotated"); + [[self tableView] reloadRowsAtIndexPaths:[[self tableView] indexPathsForVisibleRows] withRowAnimation:NO]; +} + +// +//// Override to allow orientations other than the default portrait orientation. +//- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { +// // Return YES for supported orientations +// return (interfaceOrientation == UIInterfaceOrientationPortrait); +//} +// + +- (void)didReceiveMemoryWarning { + // Releases the view if it doesn't have a superview. + NSLog(@"memory warning received in CommentsTableController"); + [super didReceiveMemoryWarning]; + + // Release any cached data, images, etc that aren't in use. +} + +- (void)viewDidUnload { + // Release any retained subviews of the main view. + // e.g. self.myOutlet = nil; +} + + + + + +#pragma mark Table view methods + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + + +// Customize the number of rows in the table view. +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + if (!comments || [comments count] == 0) + { + return 1; + } + + return [comments count]; +} + + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response +{ + NSLog(@"connection:didReceiveResponse in()"); + NSString *connectionKey = [NSString stringWithFormat: @"%d", ((intptr_t) connection)]; + NSMutableDictionary * imageDL = [linkImageDownloadQueue objectForKey:connectionKey]; + NSMutableData *imageData = [[NSMutableData alloc] init]; + [imageDL setValue:imageData forKey:@"imageData"]; + +// MovieDownload *md = [downloadQ valueForKey:connectionKey]; +// NSMutableData *fdata = [NSMutableData dataWithData:[md getData]]; +// [fdata setLength:0]; +// [md setData:fdata]; +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data +{ +// NSLog(@"connection:didReceiveData in()"); + NSString *connectionKey = [NSString stringWithFormat: @"%d", ((intptr_t) connection)]; + NSMutableDictionary * imageDL = [linkImageDownloadQueue objectForKey:connectionKey]; + NSMutableData * imageData = (NSMutableData *) [imageDL objectForKey:@"imageData"]; + [imageData appendData:data]; +// NSLog(@"Data Length : %d", [imageData length]); +// [imageDL setValue:imageData forKey:@"imageData"]; + +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error +{ + NSLog(@"connection:DidFailWithError in()"); +// [[NSAlert alertWithError:error] runModal]; +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection +{ + NSLog(@"connection:DidFinishLoading in()"); + NSString *connectionKey = [NSString stringWithFormat: @"%d", ((intptr_t) connection)]; + NSMutableDictionary * imageDL = [linkImageDownloadQueue objectForKey:connectionKey]; + NSMutableDictionary * link = [imageDL objectForKey:@"link"]; + NSData * imageData = [imageDL objectForKey:@"imageData"]; + UIImage * image = [UIImage imageWithData:imageData]; + [link setValue:image forKey:@"image"]; + int row = [[imageDL valueForKey:@"comment_row"] intValue]; + + // this handles an exception that occurs when comment_row is corrupted + // and we get an out of bounds error on the following line + if (row <0 || row > [comments count]) + return; + + NSDictionary * comment = [comments objectAtIndex:row]; +// NSDictionary * comment = [comments objectAtIndex:row - 1]; + + // we set this at the comment level, so that heightforrow() will know to fetch + // the comment cell and render it to determine the row height. + [comment setValue:@"YES" forKey:@"openedImage"]; + + NSLog(@"-- updating row after download: %d --", row); + + NSIndexPath * np = [NSIndexPath indexPathForRow:row inSection:0]; + CommentCell * cell = (CommentCell *) [self tableView:[self tableView] cellForRowAtIndexPath:np]; + // the drawLinkButtons call is required here, because only after drawing the images + // can we determine a suitable row height + [[cell commentCellView] drawLinkButtons]; + + // reset the height cache for this row so that it is forced to recalculate + // taking the image height into consideration. + [self clearHeightCacheForRow:row]; + [self refreshRow:row]; + [linkImageDownloadQueue removeObjectForKey:connectionKey]; + + // loading images inline sometimes corrupts the status bar and tab bar controller + // in SDK 3.0. So we redraw these frames just in case. + NavigationController * nc = (NavigationController *) [[self parentViewController] parentViewController]; + [nc redrawFrames]; + + [imageDL release]; +} + + + +- (NSURLRequest *)connection:(NSURLConnection *)connection + willSendRequest:(NSURLRequest *)request + redirectResponse:(NSURLResponse *)redirectResponse +{ + NSLog(@"connection request"); + return request; +} + + + + +- (void) addLinkImageToDownloadQueue:(NSMutableDictionary *) imageForQueue +{ + NSLog(@"add link to image download queue in()"); + if (!linkImageDownloadQueue) + { + NSLog(@"initialising download queue"); + linkImageDownloadQueue = [[NSMutableDictionary alloc] init]; + } + [linkImageDownloadQueue setValue:imageForQueue forKey:[imageForQueue valueForKey:@"connectionKey"]]; + +} + +- (void) loadImageInline:(NSMutableDictionary *) link +{ + NSString * url = [[link valueForKey:@"url"] copy]; + [link setValue:@"Loading..." forKey:@"description"]; + int row = [[link valueForKey:@"comment_index"] intValue]; + NSURLRequest *request=[NSURLRequest requestWithURL:[NSURL URLWithString:url]]; + NSURLConnection * connection = [NSURLConnection connectionWithRequest:request delegate:self]; + NSString *connectionKey = [NSString stringWithFormat: @"%d", ((intptr_t) connection)]; + NSMutableDictionary *imageDL = [[NSMutableDictionary alloc] init]; + [imageDL setValue:connectionKey forKey:@"connectionKey"]; + [imageDL setValue:link forKey:@"link"]; + [imageDL setValue:[NSString stringWithFormat: @"%d",[link valueForKey:@"link_id"]] forKey:@"link_id"]; + [imageDL setValue:[NSString stringWithFormat: @"%d",row] forKey:@"comment_row"]; + [self addLinkImageToDownloadQueue:imageDL]; + [self refreshRow:row]; +} + +- (IBAction)linkClicked:(id)sender { + NSLog(@"link Clicked in()"); + UIButton * button = (UIButton *) sender; + NSMutableDictionary * link = [allLinks objectAtIndex:[button tag]]; +// int row = [[button superview] tag] + 1; + int row = [[[[button superview] superview] superview] tag]; + NSString * url = [[link valueForKey:@"url"] copy]; + NSLog(@"-- downloading [%@] -- in row [%d]", url, row); + if ([[link valueForKey:@"type"] isEqualToString:@"image"]) + { + [self loadImageInline:link]; + } + else + [self moreButtonClicked:sender]; + + +// if ([[link valueForKey:@"type"] isEqualToString:@"image"]) +// { +// NSData* imageData = [[NSData alloc]initWithContentsOfURL:[NSURL URLWithString:[link valueForKey:@"url"]]]; +// UIImage* image = [[UIImage alloc] initWithData:imageData]; +// [link setValue:image forKey:@"image"]; +// int row = [[button superview] tag]; +// // reset the height cache for this row so that it is forced to recalculate +// // taking the image height into consideration. +// [bodyHeightCache replaceObjectAtIndex:row withObject:@"-1"]; +// +// NSMutableArray * affectedRows = [[NSMutableArray alloc] init]; +// NSIndexPath * ind = [NSIndexPath indexPathForRow:row inSection:0]; +// [affectedRows addObject:ind]; +// [[self tableView] reloadRowsAtIndexPaths:affectedRows withRowAnimation:UITableViewRowAnimationNone]; +// [affectedRows release]; +// } +} + +- (IBAction)moreButtonClicked:(id)sender { + UIButton * button = (UIButton *) sender; + NSDictionary * link = [allLinks objectAtIndex:[button tag]]; + NavigationController * nc = (NavigationController *) [[self parentViewController] parentViewController]; + [nc browseToLinkFromComment:[link valueForKey:@"url"]]; +} + + +- (UITableViewCell *) createProgressCell +{ + UITableViewCell * progressCell = [[[UITableViewCell alloc] init] autorelease]; + [progressCell setSelectionStyle:UITableViewCellSelectionStyleNone]; + UIProgressView * progress = [[[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault] autorelease]; + CGRect progressFrame = CGRectMake(40, 25, 240, 40); + + [progress setFrame:progressFrame]; + + // hide the progress bar when it isn't in use. + if (CommentsLoadProgressValue > 0.0 && CommentsLoadProgressValue < 1.0) + { + [progress setHidden:NO]; + [progress setProgress:CommentsLoadProgressValue]; + } + else + { + [progress setHidden:NO]; + } + [progress setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin]; + [progressCell setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin]; + [progressCell addSubview:progress]; + return progressCell; +} + + +// Customize the appearance of table view cells. +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + +// NSLog(@"cellForRowAtIndexPath requested for cell [%d]", indexPath.row); + + if ([comments count] == 0 && indexPath.row == 0) + return [self createProgressCell]; + + NSMutableDictionary *comment; + + int comment_row = indexPath.row; + comment = [comments objectAtIndex:comment_row]; + + NSString * CellIdentifier = @"FastCommentCell"; + + CommentCell *cell = (CommentCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; + + if (cell == nil) { + cell = [[[CommentCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; + cell.frame = CGRectMake(0.0, 0.0, 320.0, 100); + } + + [cell setTag:comment_row]; + + CommentWrapper * wrapper = [[CommentWrapper alloc] initWithComment:comment forController:self]; + [cell setCommentWrapper:wrapper]; + [wrapper release]; + + return cell; +} + +- (IBAction)cancelReplyPressed:(id)sender { + NSLog(@"cancelReplyPressed in()"); + UIButton *button = (UIButton *)sender; + NSDictionary * comment = [comments objectAtIndex:[button tag]]; + [comment setValue:@"NO" forKey:@"showReplyArea"]; +// [comment setValue:@"NO" forKey:@"showOptions"]; +// [self refreshRow:[button tag] + 1]; + [self refreshRow:[button tag]]; +} + +- (void)editModeForComment:(NSMutableDictionary *) comment { + NSLog(@"editModeForComment in()"); + if (![redAPI authenticated]) + { + [redAPI showAuthorisationRequiredDialog]; + return; + } + [comment setValue:@"YES" forKey:@"showReplyArea"]; + [comment setValue:@"NO" forKey:@"showOptions"]; + [comment setValue:@"YES" forKey:@"editMode"]; + [comment setValue:[comment valueForKey:@"body"] forKey:@"replyText"]; + [self refreshRow:[[comment valueForKey:@"comment_index"] intValue]]; +} + +//- (IBAction)editPressed:(id)sender { +// NSLog(@"editPressed in()"); +// if (![redAPI authenticated]) +// { +// [redAPI showAuthorisationRequiredDialog]; +// return; +// } +// UIButton *button = (UIButton *)sender; +// NSDictionary * comment = [comments objectAtIndex:[button tag]]; +// [comment setValue:@"YES" forKey:@"showReplyArea"]; +// [comment setValue:@"NO" forKey:@"showOptions"]; +// CommentCell * cell = (CommentCell *) [[[button superview] superview] superview]; +// [comment setValue:@"YES" forKey:@"editMode"]; +// [comment setValue:[comment valueForKey:@"body"] forKey:@"replyText"]; +//// [[cell submitReply] setTitle:@"Update" forState:UIControlStateNormal]; +// // [self refreshRow:[button tag] + 1]; +// [self refreshRow:[button tag]]; +//} + + +//- (void)cancelReplyForComment:(NSMutableDictionary *) comment { +// NSLog(@"cancelReplyPressed in()"); +// [comment setValue:@"NO" forKey:@"showReplyArea"]; +// [self refreshRow:[[comment valueForKey:@"comment_index"] intValue]]; +//} + +- (void)showReplyAreaForComment:(NSMutableDictionary *) comment { + NSLog(@"showReplyAreaPressed in()"); + if (![redAPI authenticated]) + { + [redAPI showAuthorisationRequiredDialog]; + return; + } + [comment setValue:@"normal" forKey:@"visibility"]; + [comment setValue:@"YES" forKey:@"showReplyArea"]; + [comment setValue:@"NO" forKey:@"showOptions"]; + [self refreshRow:[[comment valueForKey:@"comment_index"] intValue]]; +} + + + +//- (IBAction)showReplyAreaPressed:(id)sender { +// NSLog(@"showReplyAreaPressed in()"); +// if (![redAPI authenticated]) +// { +// [redAPI showAuthorisationRequiredDialog]; +// return; +// } +// UIButton *button = (UIButton *)sender; +// NSDictionary * comment = [comments objectAtIndex:[button tag]]; +// [comment setValue:@"YES" forKey:@"showReplyArea"]; +// [comment setValue:@"NO" forKey:@"showOptions"]; +//// [self refreshRow:[button tag] + 1]; +// [self refreshRow:[button tag]]; +//} + +- (IBAction)gotoParent:(id)sender { + NSLog(@"goto parent in()"); + UIButton *button = (UIButton *)sender; + NSDictionary * comment = [comments objectAtIndex:[button tag]]; + NSDictionary * parent = [self getCommentById:[comment valueForKey:@"parent_id"]]; + [self scrollToRow:[[parent valueForKey:@"comment_index"] intValue]]; +// if (parent != nil && [[parent valueForKey:@"body"] length] > 5) +// { +// UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"In response to:" message:[parent valueForKey:@"body"] delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:nil]; +// [alert show]; +// [alert release]; +// } + +} + + +- (void)contextForComment:(NSMutableDictionary *) comment { + NSLog(@"comment parent in()"); + NSDictionary * parent = [self getCommentById:[comment valueForKey:@"parent_id"]]; + if (parent != nil && [[parent valueForKey:@"body"] length] > 5) + { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"In response to:" message:[parent valueForKey:@"body"] delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:nil]; + [alert show]; + [alert release]; + } +} + +- (void) toggleSavePost:(NSMutableDictionary *) ps { + if ([[ps valueForKey:@"saved"] boolValue]) + { + [redAPI unsavePostWithID:[ps valueForKey:@"name"]]; + [ps setValue:[NSNumber numberWithBool:NO] forKey:@"saved"]; + } + else + { + [redAPI savePostWithID:[post valueForKey:@"name"]]; + [ps setValue:[NSNumber numberWithBool:YES] forKey:@"saved"]; + } + [self refreshRow:0]; +} + +- (void) toggleHidePost:(NSMutableDictionary *) ps { + if ([[ps valueForKey:@"hidden"] boolValue]) + { + [redAPI unhidePostWithID:[ps valueForKey:@"name"]]; + [ps setValue:[NSNumber numberWithBool:NO] forKey:@"hidden"]; + } + else + { +// NavigationController * nc = (NavigationController *) [[self parentViewController] parentViewController]; +// PostsTableControllerView * ptcv = (PostsTableControllerView *) [[[nc postsNavigation] viewControllers] objectAtIndex:0]; +// [ptcv hideSinglePost:[nc post]]; + [redAPI hidePostWithID:[ps valueForKey:@"name"]]; + [ps setValue:[NSNumber numberWithBool:YES] forKey:@"hidden"]; + } + [self refreshRow:0]; +} + +- (void) voteUpComment:(NSMutableDictionary *) comment { + NSLog(@"voteUpPressed in()"); + NavigationController * nc = (NavigationController *) [[self parentViewController] parentViewController]; + [nc upvoteItem:comment]; + [self refreshRow:[[comment valueForKey:@"comment_index"] intValue]]; +} + +- (void) voteDownComment:(NSMutableDictionary *) comment { + NSLog(@"voteDownPressed in()"); + NavigationController * nc = (NavigationController *) [[self parentViewController] parentViewController]; + [nc downvoteItem:comment]; + [self refreshRow:[[comment valueForKey:@"comment_index"] intValue]]; +} + + +//- (IBAction)voteUpPressed:(id)sender { +// NSLog(@"voteUpPressed in()"); +// UIButton *button = (UIButton *)sender; +// NavigationController * nc = (NavigationController *) [[self parentViewController] parentViewController]; +// NSMutableDictionary * comment = [comments objectAtIndex:[button tag]]; +// [nc upvoteItem:comment]; +// [self refreshRow:[button tag]]; +// +//} + +- (IBAction)loginResponse:(id)sender { + NSLog(@"comments :: loginResponse()"); + NSString * modhash = (NSString *) sender; + NSLog(modhash); +} + +//- (IBAction)voteDownPressed:(id)sender { +// NSLog(@"voteDownPressed in()"); +// UIButton *button = (UIButton *)sender; +// NavigationController * nc = (NavigationController *) [[self parentViewController] parentViewController]; +// NSMutableDictionary * comment = [comments objectAtIndex:[button tag]]; +// [nc downvoteItem:comment]; +// [self refreshRow:[button tag]]; +//// RedditAPI * redAPI = [(NeuRedditAppDelegate *) [[UIApplication sharedApplication] delegate] redditAPI]; +//// [redAPI loginUser:@"apitest9" withPassword:@"test9" callBackTarget:self]; +// +//} + +- (IBAction)submitReplyPressed:(id)sender { + NSLog(@"submitReplyPressed in()"); + UIButton *button = (UIButton *)sender; + NSMutableDictionary * comment = [comments objectAtIndex:[button tag]]; + + CommentCellView * ccv = (CommentCellView *) [button superview]; + [comment setValue:[[ccv replyTextView] text] forKey:@"replyText"]; + NavigationController * nc = (NavigationController *) [[self parentViewController] parentViewController]; + NSLog([comment valueForKey:@"replyText"]); + [nc replyToItem:comment]; + [comment setValue:@"NO" forKey:@"showReplyArea"]; + [self refreshRow:[button tag]]; +} + +- (void) afterCommentReply:(NSString *) commentID +{ + NSLog(@"reload and scroll in"); + NavigationController * nc = (NavigationController *) [[self parentViewController] parentViewController]; + [nc setReplyCommentID:commentID]; + [self loadComments]; +} + +- (int) getCommentRowByName:(NSString *) cname +{ + int row_counter = 0; + for(NSDictionary * comment in comments) + { + if([cname isEqualToString:[comment valueForKey:@"name"]]) + return row_counter; + row_counter++; + } + return 0; +} + + +- (NSMutableDictionary *) getCommentById:(NSString *) cId +{ + for(NSMutableDictionary * comment in comments) + { + NSRange range = [cId rangeOfString : [comment valueForKey:@"id"]]; + if (range.location != NSNotFound) { + return comment; + } + } + return nil; +} + +- (NSMutableArray *) makeArrayUnique:(NSMutableArray *) mutableArray +{ + NSArray *copy = [mutableArray copy]; + NSInteger index = [copy count] - 1; + for (id object in [copy reverseObjectEnumerator]) { + if ([mutableArray indexOfObject:object inRange:NSMakeRange(0, index)] != NSNotFound) { + [mutableArray removeObjectAtIndex:index]; + } + index--; + } + [copy release]; + return mutableArray; +} + + +// this returns the comment as well as sub-comments +- (NSArray *) allChildrenForComment:(NSDictionary *) comment +{ + NSMutableArray * children = [[NSMutableArray alloc] init]; + int comment_level = [[comment valueForKey:@"level"] intValue]; + NSString * comment_id = [comment valueForKey:@"id"]; + NSLog(@"finding children for comment:"); + NSLog(comment_id); + for(NSDictionary * cmt in comments) + { + NSRange range = [[cmt valueForKey:@"parents"] rangeOfString : comment_id]; + if (range.location != NSNotFound) { + NSArray *chain = [[cmt valueForKey:@"parents"] componentsSeparatedByString: @":"]; + for (int i=comment_level + 1; i<[chain count]; i++) + { + [children addObject:[self getCommentById:[chain objectAtIndex:i]]]; + } + } + } + children = [self makeArrayUnique:children]; + return children; +} + +- (void) showHideComments:(NSArray *) thread doHide:(BOOL) hide +{ + NSMutableArray * affectedRows = [[NSMutableArray alloc] init]; + int counter = 0; + for (NSDictionary * comment in thread) + { + NSIndexPath * ind = [NSIndexPath indexPathForRow:([[comment valueForKey:@"comment_index"] intValue]) inSection:0]; + + // TODO :: Must find out why there is a stream of: + // -- mark for update row: 0 (everytime a row is collapsed or opened + + //NSLog(@" -- mark for update row: %d",[[comment valueForKey:@"cellRow"] intValue] ); + // handle the duplicates coming in on row:0 +// if (ind.row != 0) + [affectedRows addObject:ind]; + + if ([comment valueForKey:@"visibility"]) + [[comment valueForKey:@"visibility"] release]; + + if (hide) + { + [comment setValue:@"hidden" forKey:@"visibility"]; + if (counter++ == 0) + [comment setValue:@"collapsed" forKey:@"visibility"]; + } + else + { + [comment setValue:@"normal" forKey:@"visibility"]; + } + } + [[self tableView] reloadRowsAtIndexPaths:[self makeArrayUnique:affectedRows] withRowAnimation:UITableViewRowAnimationFade]; + [affectedRows release]; + +} + +- (void) collapseToRootForComment:(NSMutableDictionary *) comment { + NSLog(@"collapsing comment to root"); + NSLog(@"parents :: %@", [comment valueForKey:@"parents"]); + NSArray *chain = [[comment valueForKey:@"parents"] componentsSeparatedByString: @":"]; + if (chain && [chain count] > 0) + { + NSString * rootId = [chain objectAtIndex:1]; + NSLog(@"root parent :: %@", rootId); + NSMutableDictionary * rootComment = [self getCommentById:rootId]; + + // this check is to make sure that we only toggle for collapsing, not + // expanding here. + if (![[rootComment valueForKey:@"visibility"] isEqualToString:@"collapsed"]) + { + // scroll up to the root comment first so that we don't disorient the + // user. + NSIndexPath * scrRow = [NSIndexPath indexPathForRow:[[rootComment valueForKey:@"comment_index"] intValue] inSection:0]; + CGRect rect = [[self tableView] rectForRowAtIndexPath:scrRow]; + [[self tableView] scrollRectToVisible:rect animated:YES]; + + [self toggleComment:rootComment]; + } + } +} + +- (void) toggleComment:(NSMutableDictionary *) comment { + + NSLog(@"toggling comment : %d", [[comment valueForKey:@"comment_index"] intValue]); + + NSArray * thread = [self allChildrenForComment:comment]; + if ([[comment valueForKey:@"visibility"] isEqualToString:@"collapsed"] || + [[comment valueForKey:@"visibility"] isEqualToString:@"hidden"]) + [self showHideComments:thread doHide:FALSE]; + else + [self showHideComments:thread doHide:TRUE]; + + // if user toggles the last comment, scroll so that the user can see that the message + // has opened. + if ([[comment valueForKey:@"comment_index"] intValue] == ([comments count] - 1)) + { + NSLog(@"last comment toggled"); + [self scrollToRow:[[comment valueForKey:@"comment_index"] intValue]]; + } + [thread release]; +} + + +//- (IBAction)toggleComment:(id)sender { +// NSLog(@"toggleComment in()"); +// UIButton *button = (UIButton *)sender; +// NSDictionary * comment = [comments objectAtIndex:[button tag]]; +// NSArray * thread = [self allChildrenForComment:comment]; +// if ([[comment valueForKey:@"visibility"] isEqualToString:@"collapsed"] || +// [[comment valueForKey:@"visibility"] isEqualToString:@"hidden"]) +// [self showHideComments:thread doHide:FALSE]; +// else +// [self showHideComments:thread doHide:TRUE]; +// +//} + +- (IBAction)showContext:(id)sender { +// UIButton *button = (UIButton *)sender; +// NSDictionary * comment = [comments objectAtIndex:[button tag]]; +// NSDictionary * parent = [self getCommentById:[comment valueForKey:@"parent_id"]]; +// +// if (parent != nil) +// { +// UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"In response to:" message:[parent valueForKey:@"body"] delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:nil]; +// [alert show]; +// [alert release]; +// } +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath +{ +// NSLog(@"-- cell height requested for row [%d]", indexPath.row); + + if (!comments || [comments count] == 0) + return 150; + + float header_height = 50; + float bottom_padding = 90; + float comment_options_height = 50; + float comment_reply_area = 135; + + NSDictionary * comment = [comments objectAtIndex:indexPath.row]; +// NSDictionary * comment = [comments objectAtIndex:indexPath.row - 1]; + + if ([[comment valueForKey:@"visibility"] isEqualToString:@"collapsed"]) + return header_height; + else if ([[comment valueForKey:@"visibility"] isEqualToString:@"hidden"]) + return 0; + + float cs = [[bodyHeightCache objectAtIndex:indexPath.row] floatValue]; + + // there is no valid cached value - so we must recalculate + if (cs < 0) + { + cs = 0; + UIFont *cellFont = [Resources mainFont]; + + // posts row has a variable title-height that we need to estimate + if (indexPath.row == 0) + { + CGSize constraintSize = CGSizeMake([tableView frame].size.width - 30.0, MAXFLOAT); + CGSize labelSize = [[comment valueForKey:@"title"] sizeWithFont:cellFont constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap]; + cs += labelSize.height + 60; + // the 40 takes into account the post creation date displayed + } + + + // guestimate the height of the body text + CGSize constraintSize; + constraintSize = CGSizeMake([tableView frame].size.width - 45.0, MAXFLOAT); + CGSize labelSize = [[comment valueForKey:@"body"] sizeWithFont:cellFont constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap]; + cs += labelSize.height + 20; + + if ([comment objectForKey:@"openedImage"]) + { + NSLog(@"-- need to render height due to opened image -- for row : %d", indexPath.row); + for (NSDictionary * link in [comment objectForKey:@"links"]) + { + if ([link objectForKey:@"linkHeight"]) + { + cs += [[link valueForKey:@"linkHeight"] floatValue] + 10; + } + else + { + cs += 20; + } + } + } + else + { + // otherwise, take a good guess at the height because it doesn't contain open images, + //and is a lot faster than rendering the cell first. + cs += ([[comment objectForKey:@"links"] count] * 50); + } + + [bodyHeightCache replaceObjectAtIndex:indexPath.row withObject:[NSNumber numberWithFloat:cs]]; + } + + if ([comment objectForKey:@"showReplyArea"] && [[comment valueForKey:@"showReplyArea"] boolValue]) + { + cs += comment_reply_area; + } + + if ([comment objectForKey:@"showOptions"] && [[comment valueForKey:@"showOptions"] boolValue]) + { + cs += comment_options_height; + } + + cs += bottom_padding; + + return cs; +} + +- (void) selectComment:(NSMutableDictionary *) comment { + + NSIndexPath * scrollRow = nil; + // user tapped on the loading progress bar + if (!comments || [comments count] == 0) + return; + + NSMutableArray * affectedRows = [[NSMutableArray alloc] init]; + // NSDictionary * comment = [comments objectAtIndex:indexPath.row - 1]; + + // clicking on the same row twice hides the edit view + if ([comment objectForKey:@"showOptions"] && [[comment valueForKey:@"showOptions"] boolValue]) + { + [comment setValue:@"NO" forKey:@"showOptions"]; + NSIndexPath * rowPath = [NSIndexPath indexPathForRow:editing_row inSection:0]; + [affectedRows addObject:rowPath]; + [self clearHeightCacheForRow:rowPath.row]; + editing_row = -1; + } + // if the reply area is showing, don't render the comment options. The user should first + // cancel the reply (or submit it). + else if ([comment objectForKey:@"showReplyArea"] && [[comment valueForKey:@"showReplyArea"] boolValue]) + { + } + else + { + // deselect previously selected rows. + if (editing_row > -1) + { + NSDictionary * previouslySelectedComment = [comments objectAtIndex:editing_row]; + [previouslySelectedComment setValue:@"NO" forKey:@"showOptions"]; + NSIndexPath * previousRowPath = [NSIndexPath indexPathForRow:editing_row inSection:0]; + [affectedRows addObject:previousRowPath]; + [self clearHeightCacheForRow:previousRowPath.row]; + } + [comment setValue:@"YES" forKey:@"showOptions"]; + editing_row = [[comment valueForKey:@"comment_index"] intValue]; + NSIndexPath * rowPath = [NSIndexPath indexPathForRow:editing_row inSection:0]; + scrollRow = rowPath; + + [affectedRows addObject:rowPath]; + [self clearHeightCacheForRow:rowPath.row]; + } + + [[self tableView] reloadRowsAtIndexPaths:[self makeArrayUnique:affectedRows] withRowAnimation:UITableViewRowAnimationNone]; + [affectedRows release]; + + + if (scrollRow) + { + if (scrollRow.row == [comments count] - 1) + { + [[self tableView] scrollToRowAtIndexPath:scrollRow atScrollPosition:UITableViewScrollPositionBottom animated:YES]; + } + else + { + // find the co-ordinates of the row *after* the one we want to scroll to + // and work our way back up about 90 pixels to make sure that the + // comment options are visible. + NSIndexPath * nextRow = [NSIndexPath indexPathForRow:scrollRow.row + 1 inSection:0]; + CGRect rect = [[self tableView] rectForRowAtIndexPath:nextRow]; + + // This used to align the bottom of the comment options with the bottom + // of the view panel. However, I have found that this causes frustration as + // the user may end up clicking on the "Posts / Messages / Settings" options + // by accident. + //rect.origin.y = rect.origin.y - 90; + + rect.origin.y = rect.origin.y - 30; + rect.size.height = 60; + [[self tableView] scrollRectToVisible:rect animated:YES]; + } + } + + NSLog(@"Selected Row (from body click) %d", [[comment valueForKey:@"comment_index"] intValue]); + // [affectedRows release]; +} + +//- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { +// +// NSIndexPath * scrollRow = nil; +// // user tapped on the loading progress bar +// if (!comments || [comments count] == 0) +// return; +// +// // user tapped on the post information +//// if (indexPath.row == 0) +//// return; +// +// NSMutableArray * affectedRows = [[NSMutableArray alloc] init]; +// NSDictionary * comment = [comments objectAtIndex:indexPath.row]; +// // NSDictionary * comment = [comments objectAtIndex:indexPath.row - 1]; +// +// // clicking on the same row twice hides the edit view +// if ([comment objectForKey:@"showOptions"] && [[comment valueForKey:@"showOptions"] boolValue]) +// { +// [comment setValue:@"NO" forKey:@"showOptions"]; +// NSIndexPath * rowPath = [NSIndexPath indexPathForRow:editing_row inSection:0]; +// [affectedRows addObject:rowPath]; +// [self clearHeightCacheForRow:rowPath.row]; +// editing_row = -1; +// } +// // if the reply area is showing, don't render the comment options. The user should first +// // cancel the reply (or submit it). +// else if ([comment objectForKey:@"showReplyArea"] && [[comment valueForKey:@"showReplyArea"] boolValue]) +// { +// } +// else +// { +// // deselect previously selected rows. +// if (editing_row > -1) +// { +// NSDictionary * previouslySelectedComment = [comments objectAtIndex:editing_row]; +// [previouslySelectedComment setValue:@"NO" forKey:@"showOptions"]; +// NSIndexPath * previousRowPath = [NSIndexPath indexPathForRow:editing_row inSection:0]; +// [affectedRows addObject:previousRowPath]; +// [self clearHeightCacheForRow:previousRowPath.row]; +// } +// [comment setValue:@"YES" forKey:@"showOptions"]; +// editing_row = indexPath.row; +// NSIndexPath * rowPath = [NSIndexPath indexPathForRow:editing_row inSection:0]; +// scrollRow = rowPath; +// +// [affectedRows addObject:rowPath]; +// [self clearHeightCacheForRow:rowPath.row]; +// } +// +// [tableView reloadRowsAtIndexPaths:[self makeArrayUnique:affectedRows] withRowAnimation:UITableViewRowAnimationNone]; +// [affectedRows release]; +// +// if (scrollRow) +// { +// if (scrollRow.row == [comments count] - 1) +// { +// [tableView scrollToRowAtIndexPath:scrollRow atScrollPosition:UITableViewScrollPositionBottom animated:YES]; +// } +// else +// { +// // find the co-ordinates of the row *after* the one we want to scroll to +// // and work our way back up about 90 pixels to make sure that the +// // comment options are visible. +// NSIndexPath * nextRow = [NSIndexPath indexPathForRow:scrollRow.row + 1 inSection:0]; +// CGRect rect = [tableView rectForRowAtIndexPath:nextRow]; +// rect.origin.y = rect.origin.y - 90; +// rect.size.height = 90; +// [tableView scrollRectToVisible:rect animated:YES]; +// } +// } +// +// NSLog(@"Selected Row %d", indexPath.row); +//// [affectedRows release]; +//} + +- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath +{ + return UITableViewCellEditingStyleNone; +} + +//- (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath +//{ +// NSLog(@"indentation level in()"); +// return 200.0; +//} +// +- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath +{ +// NSLog(@"should indent in(%d)", indexPath.row); +// CommentCell * cell = (CommentCell * ) [tableView cellForRowAtIndexPath:indexPath]; +// return [cell isEditing]; +// if (indexPath.row == editing_row) +// return YES; +// else +// return NO; + return YES; +} + +/* +// Override to support conditional editing of the table view. +- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { + // Return NO if you do not want the specified item to be editable. + return YES; +} +*/ + + +/* +// Override to support editing the table view. +- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { + + if (editingStyle == UITableViewCellEditingStyleDelete) { + // Delete the row from the data source + [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES]; + } + else if (editingStyle == UITableViewCellEditingStyleInsert) { + // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view + } +} +*/ + + +/* +// Override to support rearranging the table view. +- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath { +} +*/ + + +/* +// Override to support conditional rearranging of the table view. +- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath { + // Return NO if you do not want the item to be re-orderable. + return YES; +} +*/ + + +- (void)dealloc { + [bodyHeightCache release]; + [comments release]; + [flatComments release]; +// [filteredListContent release]; + [super dealloc]; +} + +//#pragma mark - +//#pragma mark Content Filtering +// +//- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope +//{ +// /* +// Update the filtered array based on the search text and scope. +// */ +// +// +// [filteredListContent removeAllObjects]; // First clear the filtered array. +// +// +// for (NSDictionary * comment in comments) +// { +// NSRange rangeBody = [[comment valueForKey:@"body"] rangeOfString:searchText options:NSCaseInsensitiveSearch ]; +// NSRange rangeUser = [[comment valueForKey:@"author"] rangeOfString : searchText options:NSCaseInsensitiveSearch]; +// if (rangeBody.location != NSNotFound || rangeUser.location != NSNotFound) +// [self.filteredListContent addObject:comment]; +// } +// +//} + +#pragma mark - +#pragma mark UISearchDisplayController Delegate Methods + +//- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString +//{ +// [self filterContentForSearchText:searchString scope: +// [[self.searchDisplayController.searchBar scopeButtonTitles] objectAtIndex:[self.searchDisplayController.searchBar selectedScopeButtonIndex]]]; +// +// // Return YES to cause the search result table view to be reloaded. +// return YES; +//} +// +// +//- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption +//{ +// [self filterContentForSearchText:[self.searchDisplayController.searchBar text] scope: +// [[self.searchDisplayController.searchBar scopeButtonTitles] objectAtIndex:searchOption]]; +// +// // Return YES to cause the search result table view to be reloaded. +// return YES; +//} +// + + +- (void)textViewDidBeginEditing:(UITextView *)textView +{ + NSLog(@"textViewDidBeginEditing in"); +// int row = [textView tag]; +// NSIndexPath * ind = [NSIndexPath indexPathForRow:(row + 1) inSection:0]; +// CGRect rect = [[self tableView] rectForRowAtIndexPath:ind]; +// rect.origin.y = rect.origin.y - 150; +// rect.size.height = 150; +// [[self tableView] scrollRectToVisible:rect animated:YES]; +// [[self tableView] scrollToRowAtIndexPath:ind atScrollPosition:UITableViewScrollPositionBottom animated:YES]; +// CommentCell * cell = (CommentCell *) [[[[textView superview] superview] superview] superview]; +// [[cell submitReply] setHidden:FALSE]; +} + +- (void)textViewDidEndEditing:(UITextView *)textView +{ + NSLog(@"textViewDidEndEditing in"); +} + + +- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range + replacementText:(NSString *)text +{ + NSLog(@"shouldChangeTextInRange in"); + + NSMutableDictionary * comment = (NSMutableDictionary *) [comments objectAtIndex:[textView tag]]; + [comment setValue:[textView text] forKey:@"replyText"]; + + // int edit_row = [textView tag]; +// NSLog(@"editing row : %d", edit_row); + + // Any new character added is passed in as the "text" parameter + if ([text isEqualToString:@"\n"]) { + // Be sure to test for equality using the "isEqualToString" message + [textView resignFirstResponder]; + + // Return FALSE so that the final '\n' character doesn't get added + return FALSE; + } + // For any other character return TRUE so that the text gets added to the view + return TRUE; +} + +-(IBAction) popupExtraOptionsActionSheet:(id) sender { + NSLog(@"pop up extra options in()"); + + if (!comments || [comments count] == 0) + return; + + + NSString * loadImageTitle; + if (![MKStoreManager isProUpgraded]) + loadImageTitle = @"Show All Images (PRO)"; + else + loadImageTitle = @"Show All Images"; + + UIActionSheet *popupQuery = [[UIActionSheet alloc] + initWithTitle:nil + delegate:self + cancelButtonTitle:nil + destructiveButtonTitle:nil + otherButtonTitles: + @"Open in Safari", + @"Email Link", + @"Show All Images", + nil]; +// NSMutableDictionary * ps = (NSMutableDictionary *) [comments objectAtIndex:0]; + NavigationController * nc = (NavigationController *) [[self parentViewController] parentViewController]; + NSMutableDictionary * ps = [nc post]; + + // Open in Safari + // Email Link + // Load All Images + // Save Post + // Hide Post + // Add Comment + + if ([[ps valueForKey:@"saved"] boolValue]) + [popupQuery addButtonWithTitle:@"Un-Save Post"]; + else + [popupQuery addButtonWithTitle:@"Save Post"]; + + if ([[ps valueForKey:@"hidden"] boolValue]) + [popupQuery addButtonWithTitle:@"Un-Hide Post"]; + else + [popupQuery addButtonWithTitle:@"Hide Post"]; + + [popupQuery addButtonWithTitle:@"Add a Comment"]; + [popupQuery addButtonWithTitle:@"Cancel"]; + [popupQuery setCancelButtonIndex:6]; + + popupQuery.actionSheetStyle = UIActionSheetStyleBlackOpaque; + [popupQuery showInView:self.tabBarController.view]; + [popupQuery release]; +} + +- (void)actionSheet:(UIActionSheet *)actionSheet +clickedButtonAtIndex:(NSInteger)buttonIndex { + + NSMutableDictionary * ps = (NSMutableDictionary *) [comments objectAtIndex:0]; + + if (buttonIndex == 0) { + NSLog(@"Action Sheet :: Open in Safari"); + NSString * message = @"This will launch Safari and exit Alien Blue. Do you want to continue?"; + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Open in Safari" + message:message + delegate:self + cancelButtonTitle:@"No" + otherButtonTitles:@"Yes",nil]; + [alert setTag:2]; + [alert show]; + [alert release]; + + } else if (buttonIndex == 1) { + NSLog(@"Action Sheet :: Email Link"); + NSString * message = @"This will launch your Mail application and exit Alien Blue. Do you want to continue?"; + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Email Link" + message:message + delegate:self + cancelButtonTitle:@"No" + otherButtonTitles:@"Yes",nil]; + [alert setTag:1]; + [alert show]; + [alert release]; + + } else if (buttonIndex == 2) + { + [self loadAllImages]; + } + else if (buttonIndex == 3) + { + [self toggleSavePost:ps]; + } + else if (buttonIndex == 4) + { + [self toggleHidePost:ps]; + } + else if (buttonIndex == 5) + { + [self showReplyAreaForComment:ps]; + NSIndexPath * ind = [NSIndexPath indexPathForRow:0 inSection:0]; + [[self tableView] scrollToRowAtIndexPath:ind atScrollPosition:UITableViewScrollPositionBottom animated:YES]; + } + + +} + + + +- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex +{ + NavigationController * nc = (NavigationController *) [[self parentViewController] parentViewController]; + if(alertView.tag == 1 && buttonIndex == 1) + { + NSLog(@"launch Mail app in()"); + NSString *url = [NSString stringWithString:@"mailto:?subject=Link%20From%20Reddit&body=http://www.reddit.com"]; + url = [url stringByAppendingString:[[[nc post] valueForKey:@"permalink"] copy]]; + NSLog(url); + [[UIApplication sharedApplication] openURL: [NSURL URLWithString: url]]; + } + + if(alertView.tag == 2 && buttonIndex == 1) + { + NSLog(@"launch safari in()"); + NSString *url = [NSString stringWithFormat:@"http://www.reddit.com%@",[[[nc post] valueForKey:@"permalink"] copy]]; + NSLog(url); + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]]; + } + + if(alertView.tag == 3 && buttonIndex == 1) + { + NSLog(@"loadAllImages in()"); + + for (NSMutableDictionary * link in allLinks) + { + if ([[link valueForKey:@"type"] isEqualToString:@"image"]) + { + NSLog(@"downloading image: %@", [link valueForKey:@"url"]); + [self loadImageInline:link]; + } + } + } +} + +@end + diff --git a/Classes/ImageCache.h b/Classes/ImageCache.h new file mode 100644 index 0000000..45cf761 --- /dev/null +++ b/Classes/ImageCache.h @@ -0,0 +1,18 @@ +// +// ImageCache.h +// AlienBlue +// +// Created by Jason Morrissey on 17/05/10. +// Copyright 2010 The Design Shed. All rights reserved. +// + +#import +#import "Resources.h" + +@interface ImageCache : NSObject { + +} + ++ (UIImage *) imageForURL:(NSString *) urlString withCallBackTarget:(id) target; ++ (void) resetImageCache; +@end diff --git a/Classes/ImageCache.m b/Classes/ImageCache.m new file mode 100644 index 0000000..d9fc32b --- /dev/null +++ b/Classes/ImageCache.m @@ -0,0 +1,149 @@ +// +// ImageCache.m +// AlienBlue +// +// Created by Jason Morrissey on 17/05/10. +// Copyright 2010 The Design Shed. All rights reserved. +// + +#import "ImageCache.h" + +static NSMutableDictionary * cache; + +@implementation ImageCache + ++ (void) resetImageCache +{ + if (cache) + [cache release]; + cache = [[NSMutableDictionary alloc] init]; +} + ++ (void)initialize { + if (self == [ImageCache class]) { + NSLog(@"ImageCache :: initialise in()"); + [self resetImageCache]; + } +} + ++ (NSMutableDictionary *) cacheForURL:(NSString *) urlString +{ + for(NSString * connectionKey in cache){ + NSMutableDictionary * cachedItem = [cache objectForKey:connectionKey]; + NSString * cachedUrl = [cachedItem valueForKey:@"url"]; +// NSLog(@"-- in cache :: %@", cachedUrl); + if (cachedUrl && [cachedUrl isEqualToString:urlString]) + return cachedItem; + } + return nil; +} + ++ (UIImage *) imageForURL:(NSString *) urlString withCallBackTarget:(id) target +{ +// NSLog(@"ImageCache :: Image Requested :: %@", urlString); + + // handle invalid thumbnail links + if (!urlString || [urlString length] == 0 + || [urlString rangeOfString:@"noimage" options:NSCaseInsensitiveSearch].location != NSNotFound) + return nil; + + NSMutableDictionary * cachedItem = [self cacheForURL:urlString]; + if (!cachedItem) + { + NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease]; + [request setURL:[NSURL URLWithString:urlString]]; + NSURLConnection * connection = [NSURLConnection connectionWithRequest:request delegate:self]; + NSString *connectionKey = [NSString stringWithFormat: @"%d", ((intptr_t) connection)]; + NSMutableDictionary *dl = [[NSMutableDictionary alloc] init]; + [dl setValue:connectionKey forKey:@"connectionKey"]; + [dl setValue:[urlString copy] forKey:@"url"]; + [dl setValue:target forKey:@"afterCompleteTarget"]; + [dl setValue:@"loading" forKey:@"progress"]; + [cache setValue:dl forKey:connectionKey]; +// NSLog(@"ImageCache :: Image Requested :: %@", urlString); + } + else + { + // override the callback target... The request for caching may have been + // made by the PostsTableController for pre-fetching. The next time we call this + // method, it may be from a different object, so we need to update it's callback target + [cachedItem setValue:target forKey:@"afterCompleteTarget"]; + + // cached item + if ([[cachedItem valueForKey:@"progress"] isEqualToString:@"finished"]) + return [cachedItem objectForKey:@"image"]; + + } + return [Resources loadingImage]; +} + ++ (NSURLRequest *)connection:(NSURLConnection *)connection + willSendRequest:(NSURLRequest *)request + redirectResponse:(NSURLResponse *)redirectResponse +{ +// NSLog(@"ImageCache :: connection : request"); + return request; +} + ++ (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response +{ +// NSLog(@"ImageCache :: connection : didReceiveResponse"); + NSString *connectionKey = [NSString stringWithFormat: @"%d", ((intptr_t) connection)]; + NSMutableDictionary * dl = [cache objectForKey:connectionKey]; + if (dl) + { + NSMutableData *data = [[NSMutableData alloc] init]; + [dl setObject:data forKey:@"data"]; + } +} + ++ (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data +{ +// NSLog(@"ImageCache :: connection : didReceiveData"); + NSString *connectionKey = [NSString stringWithFormat: @"%d", ((intptr_t) connection)]; + NSMutableDictionary * dl = [cache objectForKey:connectionKey]; + if (dl) + { + NSMutableData * oldData = (NSMutableData *) [dl objectForKey:@"data"]; + [oldData appendData:data]; + } +} + ++ (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error +{ +// NSLog(@"ImageCache :: connection : DidFailWithError"); +} + + ++ (void)connectionDidFinishLoading:(NSURLConnection *)connection +{ +// NSLog(@"ImageCache :: connection : connectionDidFinishLoading in()"); + NSString *connectionKey = [NSString stringWithFormat: @"%d", ((intptr_t) connection)]; + NSMutableDictionary * dl = [cache objectForKey:connectionKey]; + if (dl) + { + [dl setValue:@"finished" forKey:@"progress"]; + UIImage * image = [UIImage imageWithData:[dl objectForKey:@"data"]]; + [dl setValue:image forKey:@"image"]; + + // perform necessary callbacks with the data. + if ([dl objectForKey:@"afterCompleteTarget"]) + { + SEL action = NSSelectorFromString(@"imageReadyCallback:"); + [[dl objectForKey:@"afterCompleteTarget"] performSelector:action withObject:image]; + } + [[dl objectForKey:@"data"] release]; + } +// [cache removeObjectForKey:connectionKey]; +// [connectionKey release]; +// [dl release]; +} + + +- (void)dealloc { + if (cache) + [cache release]; + [super dealloc]; +} + +@end diff --git a/Classes/MessageCell.h b/Classes/MessageCell.h new file mode 100755 index 0000000..476b357 --- /dev/null +++ b/Classes/MessageCell.h @@ -0,0 +1,35 @@ +// +// MessageCell.h +// Alien Blue +// +// Created by Jason Morrissey on 16/04/10. +// Copyright 2010 The Design Shed. All rights reserved. +// + +#import + + +@interface MessageCell : UITableViewCell { + IBOutlet UITextView *body; + IBOutlet UILabel *author; + IBOutlet UILabel *datetime; + IBOutlet UIView *viewForMessageReply; + IBOutlet UIButton *showReplyAreaButton; + IBOutlet UIButton *showContextButton; + IBOutlet UIButton *submitReply; + IBOutlet UIButton *cancelReply; + IBOutlet UITextView *replyTextView; +} + + +@property (nonatomic, retain) IBOutlet UITextView *body; +@property (nonatomic, retain) IBOutlet UILabel *author; +@property (nonatomic, retain) IBOutlet UILabel *datetime; +@property (nonatomic, retain) IBOutlet UIView *viewForMessageReply; +@property (nonatomic, retain) IBOutlet UIButton *showReplyAreaButton; +@property (nonatomic, retain) IBOutlet UIButton *submitReply; +@property (nonatomic, retain) IBOutlet UIButton *showContextButton; +@property (nonatomic, retain) IBOutlet UIButton *cancelReply; +@property (nonatomic, retain) IBOutlet UITextView *replyTextView; + +@end diff --git a/Classes/MessageCell.m b/Classes/MessageCell.m new file mode 100755 index 0000000..6799470 --- /dev/null +++ b/Classes/MessageCell.m @@ -0,0 +1,47 @@ +// +// MessageCell.m +// Alien Blue +// +// Created by Jason Morrissey on 16/04/10. +// Copyright 2010 The Design Shed. All rights reserved. +// + +#import "MessageCell.h" + + +@implementation MessageCell + +@synthesize body; +@synthesize author; +@synthesize datetime; +@synthesize viewForMessageReply; +@synthesize showReplyAreaButton; +@synthesize submitReply; +@synthesize cancelReply; +@synthesize replyTextView; +@synthesize showContextButton; + + + +- (id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier { + if (self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier]) { + // Initialization code + } + return self; +} + + +- (void)setSelected:(BOOL)selected animated:(BOOL)animated { + + [super setSelected:selected animated:animated]; + + // Configure the view for the selected state +} + + +- (void)dealloc { + [super dealloc]; +} + + +@end diff --git a/Classes/MessagesTableViewController.h b/Classes/MessagesTableViewController.h new file mode 100755 index 0000000..901da39 --- /dev/null +++ b/Classes/MessagesTableViewController.h @@ -0,0 +1,23 @@ +// +// MessagesTableViewController.h +// Alien Blue +// +// Created by Jason Morrissey on 16/04/10. +// Copyright 2010 The Design Shed. All rights reserved. +// + +#import +#import "RedditAPI.h" +#import "MessageCell.h" + +@interface MessagesTableViewController : UITableViewController { + NSMutableArray * messages; + RedditAPI * redAPI; + NSTimer * checkInboxTimer; + NSTimer * progressTimer; + float ProgressValue; + NSUserDefaults * prefs; + BOOL resultsFetched; +} +- (IBAction) fetchMessages:(id)sender; +@end diff --git a/Classes/MessagesTableViewController.m b/Classes/MessagesTableViewController.m new file mode 100755 index 0000000..76c4cde --- /dev/null +++ b/Classes/MessagesTableViewController.m @@ -0,0 +1,663 @@ +// +// MessagesTableViewController.m +// Alien Blue +// +// Created by Jason Morrissey on 16/04/10. +// Copyright 2010 The Design Shed. All rights reserved. +// + +#import "MessagesTableViewController.h" +#import "NavigationController.h" +#import "AlienBlueAppDelegate.h" + + +@implementation MessagesTableViewController + +/* +- (id)initWithStyle:(UITableViewStyle)style { + // Override initWithStyle: if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad. + if (self = [super initWithStyle:style]) { + } + return self; +} +*/ + +- (void) enableProgressBar +{ + progressTimer = [NSTimer scheduledTimerWithTimeInterval:1 + target:self + selector:@selector(updateProgressBar:) + userInfo:nil + repeats:YES]; + +} + +- (IBAction)linkClicked:(id)sender { + UIButton * button = (UIButton *) sender; + NSDictionary * message = [messages objectAtIndex:[[button superview] tag]]; + NSMutableDictionary * link = [[message valueForKey:@"links"] objectAtIndex:[button tag] - 1]; + NSLog(@"-- message row: %d", [[button superview] tag]); + NSLog(@"-- link #: %d", [button tag]); + NSLog(@"-- URL : %@", [link valueForKey:@"url"]); + NavigationController * nc = (NavigationController *) [self parentViewController]; + [nc browseToLinkFromMessage:link]; +} + + +- (void) refreshRow:(int) row +{ + NSMutableArray * affectedRows = [[NSMutableArray alloc] init]; + NSIndexPath * ind = [NSIndexPath indexPathForRow:row inSection:0]; + [affectedRows addObject:ind]; + [[self tableView] reloadRowsAtIndexPaths:affectedRows withRowAnimation:UITableViewRowAnimationNone]; + [affectedRows release]; +} + +- (void) refreshProgressBar:(float) pr +{ + ProgressValue = pr; + int row = [[self tableView] numberOfRowsInSection:0] - 1; + [self refreshRow:row]; +} + +- (void) completeProgressBar +{ + [self refreshProgressBar:0.0]; + if(progressTimer) + { +// [progressTimer invalidate]; + progressTimer = nil; + } +} + +-(void) updateProgressBar: (NSTimer *) theTimer +{ + if (ProgressValue <= 0.9) + { + [self refreshProgressBar:(ProgressValue + 0.05)]; + } + else + { +// [theTimer invalidate]; + theTimer = nil; + } +} + + +- (void)viewDidLoad { + NSLog(@"-- messages view loaded --"); + [super viewDidLoad]; + prefs = [NSUserDefaults standardUserDefaults]; + redAPI = [(AlienBlueAppDelegate *) [[UIApplication sharedApplication] delegate] redditAPI]; +// [messagesTab setBadgeValue:@"None"]; + resultsFetched = NO; + + + // Uncomment the following line to display an Edit button in the navigation bar for this view controller. +// [self.navigationController setNavigationBarHidden:NO animated:NO]; +// self.navigationItem.rightBarButtonItem = self.editButtonItem; + +} + + + +- (void)viewWillAppear:(BOOL)animated { + NSLog(@"-- messages view will appear --"); + [super viewWillAppear:animated]; +} + + +- (void)viewDidAppear:(BOOL)animated { + NSLog(@"-- messages view appeared --"); + [super viewDidAppear:animated]; + + NavigationController * nc = (NavigationController *) [self parentViewController]; + [[self tableView] setScrollsToTop:[[prefs valueForKey:@"allow_status_bar_scroll"] boolValue]]; + + if (![redAPI authenticated]) + { + [redAPI showAuthorisationRequiredDialog]; + + // direct user to settings + + [nc setSelectedIndex:2]; + return; + } + + if ([nc shouldRefreshMessages]) + { + // [messages release]; + // [[self tableView] reloadData]; + [messages removeAllObjects]; + [self fetchMessages:nil]; + } else + { + // This is used when we want to come back from the BrowserView to our messages. Otherwise + // we will lose our place. + // don't do anything this time, but refresh the next time the user taps the Messages + [nc setShouldRefreshMessages:YES]; + } +} + +/* +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; +} +*/ +/* +- (void)viewDidDisappear:(BOOL)animated { + [super viewDidDisappear:animated]; +} +*/ + +/* +// Override to allow orientations other than the default portrait orientation. +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + // Return YES for supported orientations + return (interfaceOrientation == UIInterfaceOrientationPortrait); +} +*/ + +- (void)didReceiveMemoryWarning { + // Releases the view if it doesn't have a superview. + [super didReceiveMemoryWarning]; + + // Release any cached data, images, etc that aren't in use. +} + +- (void)viewDidUnload { + // Release any retained subviews of the main view. + // e.g. self.myOutlet = nil; +} + + +#pragma mark Table view methods + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + + +// Customize the number of rows in the table view. +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return [messages count] + 1; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (!messages) + return 150; + + if (indexPath.row == [messages count]) + return 150; + + float cs = 0; + + UIFont *cellFont = [UIFont fontWithName:@"Helvetica" size:17.0]; +// CGSize constraintSize; + NSDictionary * message = [messages objectAtIndex:indexPath.row]; +// if ([message objectForKey:@"showOptions"] && [[message valueForKey:@"showOptions"] boolValue]) +// constraintSize = CGSizeMake([tableView frame].size.width - 90.0, MAXFLOAT); +// else +// constraintSize = CGSizeMake([tableView frame].size.width - 50.0, MAXFLOAT); + CGSize constraintSize = CGSizeMake([tableView frame].size.width - 50.0, MAXFLOAT); + CGSize labelSize = [[message valueForKey:@"body"] sizeWithFont:cellFont constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap]; + cs = labelSize.height + 20; + + + if ([message objectForKey:@"showReplyArea"] && [[message valueForKey:@"showReplyArea"] boolValue]) + { + cs += 140; + } + + for (NSDictionary * link in [message objectForKey:@"links"]) + { + cs += 50; + } + + cs += 50; + return cs; +} + +- (UITableViewCell *) createNothingHereCell +{ + UITableViewCell * nCell = [[UITableViewCell alloc] init]; + UILabel * nothingHereLabel = [[UILabel alloc] initWithFrame:CGRectMake(48, 25, 230, 40)]; + [nothingHereLabel setTextAlignment:UITextAlignmentCenter]; + [nothingHereLabel setText:@"You have no messages."]; + [nothingHereLabel setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin]; + [nothingHereLabel setBackgroundColor:[UIColor clearColor]]; + [nothingHereLabel setTextColor:[UIColor whiteColor]]; + [nothingHereLabel setFont:[UIFont systemFontOfSize:19]]; + [nCell setSelectionStyle:UITableViewCellSelectionStyleNone]; + [nCell addSubview:nothingHereLabel]; + [nCell setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin]; + return nCell; +} + +- (UITableViewCell *) createViewMoreCell +{ + UITableViewCell * moreCell = [[UITableViewCell alloc] init]; + [moreCell setSelectionStyle:UITableViewCellSelectionStyleNone]; + UIButton * showMoreButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; + CGRect buttonFrame = CGRectMake(60, 25, 200, 40); + [showMoreButton setFrame:buttonFrame]; + UIProgressView * progress = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault]; + CGRect progressFrame = CGRectMake(60, 75, 200, 40); + [progress setFrame:progressFrame]; + + if ([redAPI loadingMessages]) + [showMoreButton setTitle:@"Loading..." forState:UIControlStateNormal]; + else + [showMoreButton setTitle:@"Show more..." forState:UIControlStateNormal]; + [showMoreButton setTitleColor:[Resources cNormal] forState:UIControlStateNormal]; + [showMoreButton setTitleColor:[Resources cTitleColor] forState:UIControlStateDisabled]; + [showMoreButton setBackgroundImage:[Resources barImage] forState:UIControlStateNormal]; + + [showMoreButton addTarget:self action:@selector(fetchMessages:) forControlEvents:UIControlEventTouchUpInside]; + [moreCell addSubview:showMoreButton]; + + // hide the progress bar when it isn't in use. + if ([redAPI loadingMessages]) + { + [progress setHidden:NO]; + [progress setProgress:ProgressValue]; + [showMoreButton setEnabled:FALSE]; + } + else + { + [showMoreButton setEnabled:TRUE]; + [progress setHidden:YES]; + } + + [progress setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin]; + [showMoreButton setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin]; + [moreCell setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin]; + [moreCell addSubview:progress]; + return moreCell; +} + +- (void) deleteOldLinkButtonsForCell:(MessageCell *) cell +{ + NSArray * subviews = [cell subviews]; + // NSLog(@"-----------"); + for (id subview in subviews) + { +// NSLog(@"-- found object : %@", [subview class]); + if ([subview isKindOfClass:[UIButton class]]) + { + UIButton * uib = (UIButton *) subview; + // only link buttons have a background, so it is safe to remove them + if ([uib backgroundImageForState:UIControlStateNormal]) + [uib removeFromSuperview]; + } + } +} + +- (void) createLinkButtonsForCell:(MessageCell *) cell withMessage:(NSDictionary *) comment +{ + NSArray * links = [comment objectForKey:@"links"]; + + // need to calculate starting point to draw from... (so that the first link appears above + //the others. This is complicated, because the links are bottom-flexy spaced. + + float distance_from_bottom = 0; + for (NSDictionary * link in links) + { + NSLog(@"link found: %@", [link valueForKey:@"url"]); + float frame_height = 33; + distance_from_bottom += frame_height + 10; + } + + if ([comment objectForKey:@"showReplyArea"] && [[comment valueForKey:@"showReplyArea"] boolValue]) + { + distance_from_bottom += 135; + } + + float upto = 0; + float cell_height = [cell frame].size.height; + float cell_width = [cell frame].size.width; + int link_counter = 1; + for (NSMutableDictionary * link in links) + { + UIButton * linkButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; + + CGRect frame = CGRectMake(25, cell_height - 55, cell_width - 45, 35); + float frame_height; + if ([link objectForKey:@"linkHeight"] && [link objectForKey:@"image"]) + frame_height = [[link valueForKey:@"linkHeight"] floatValue]; + else + frame_height = 33; + + upto += frame_height + 10; + + frame.origin.y = frame.origin.y - distance_from_bottom + upto - 5; + NSString * labelText = [[NSString alloc] initWithFormat:@" %d :: %@ :: %@ ", + [[link valueForKey:@"linkTag"] intValue], [[link valueForKey:@"type"] capitalizedString], + [link valueForKey:@"description"]]; + [linkButton setFrame:frame]; + [linkButton setHidden:FALSE]; + [linkButton.titleLabel setFont:[UIFont fontWithName:@"Helvetica" size:13]]; + [linkButton setTitle:labelText forState:UIControlStateNormal]; + [linkButton setAutoresizesSubviews:YES]; + [linkButton setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth]; + + [linkButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; + [linkButton setBackgroundImage:[Resources barImage] forState:UIControlStateNormal]; + [cell addSubview:linkButton]; + [linkButton setTag:link_counter]; + [link setValue:[[NSString alloc] initWithFormat:@"%f",frame.size.height] forKey:@"linkHeight"]; + [linkButton addTarget:self action:@selector(linkClicked:) forControlEvents:UIControlEventTouchUpInside]; + link_counter++; + } +} + + +// Customize the appearance of table view cells. +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + // empty set + if (indexPath.row == 0 && [messages count] == 0 && resultsFetched) + { + return [self createNothingHereCell]; + } + + if (indexPath.row == [messages count]) + return [self createViewMoreCell]; + + static NSString *CellIdentifier = @"MessageCell"; + + MessageCell *cell = (MessageCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; + if (cell == nil) { + NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"CustomMessageCell" owner:nil options:nil]; + for(id currentObject in topLevelObjects) + { + if([currentObject isKindOfClass:[MessageCell class]]) + { + cell = (MessageCell *)currentObject; + break; + } + } + } + // Set up the cell... + int message_row = indexPath.row; + + [self deleteOldLinkButtonsForCell:cell]; + + NSDictionary * message = [messages objectAtIndex:indexPath.row]; + [cell setTag:message_row]; + [[cell body] setText:[message valueForKey:@"body"]]; +// [[cell body] setEditable:NO]; + [[cell body] setDataDetectorTypes:UIDataDetectorTypeNone]; +// [[cell body] setUserInteractionEnabled:YES]; + [[cell author] setText:[message valueForKey:@"author"]]; + + [[cell showReplyAreaButton] setTag:message_row]; + [[cell submitReply] setTag:message_row]; + [[cell cancelReply] setTag:message_row]; + [[cell showContextButton] setTag:message_row]; + + [[cell showReplyAreaButton] addTarget:self action:@selector(showReplyAreaPressed:) forControlEvents:UIControlEventTouchUpInside]; + [[cell submitReply] addTarget:self action:@selector(submitReplyPressed:) forControlEvents:UIControlEventTouchUpInside]; + [[cell cancelReply] addTarget:self action:@selector(cancelReplyPressed:) forControlEvents:UIControlEventTouchUpInside]; + [[cell showContextButton] addTarget:self action:@selector(messageContextPressed:) forControlEvents:UIControlEventTouchUpInside]; + [[cell replyTextView] setDelegate:self]; + [[cell replyTextView] setText:@""]; + + if ([[message valueForKey:@"was_comment"] boolValue]) + [[cell showContextButton] setHidden:NO]; + else + [[cell showContextButton] setHidden:YES]; + + if ([message objectForKey:@"showReplyArea"] && [[message valueForKey:@"showReplyArea"] boolValue]) + { + [[cell viewForMessageReply] setHidden:FALSE]; + } + else + [[cell viewForMessageReply] setHidden:TRUE]; + [[cell replyTextView] setTag:message_row]; + + + // parse links and add related buttons + if ([[message objectForKey:@"links"] count] > 0) + { + [self createLinkButtonsForCell:cell withMessage:message]; + } + + + +// UITextView * messageBody = [[UITextView alloc] initWithFrame:CGRectMake(20, 20, 290, 50)]; +// [messageBody setText:[message valueForKey:@"body"]]; +// [messageBody setBackgroundColor:[UIColor clearColor]]; +// [messageBody setFont:[UIFont fontWithName:@"Helvetica" size:14]]; +// [messageBody setScrollEnabled:FALSE]; +// [messageBody setEditable:FALSE]; +// [messageBody setTextColor:[UIColor whiteColor]]; +// [messageBody setAutoresizesSubviews:FALSE]; +// [messageBody setClipsToBounds:FALSE]; +// +// [cell setClipsToBounds:TRUE]; +// [cell addSubview:messageBody]; + return cell; +} + +- (void)textViewDidBeginEditing:(UITextView *)textView +{ + int row = [textView tag]; + NSIndexPath * ind = [NSIndexPath indexPathForRow:row inSection:0]; + [[self tableView] scrollToRowAtIndexPath:ind atScrollPosition:UITableViewScrollPositionBottom animated:YES]; +} + +- (void)textViewDidEndEditing:(UITextView *)textView +{ +} + +- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range + replacementText:(NSString *)text +{ + // Any new character added is passed in as the "text" parameter + if ([text isEqualToString:@"\n"]) { + // Be sure to test for equality using the "isEqualToString" message + [textView resignFirstResponder]; + + // Return FALSE so that the final '\n' character doesn't get added + return FALSE; + } + // For any other character return TRUE so that the text gets added to the view + return TRUE; +} + +- (IBAction)messageContextPressed:(id)sender { + UIButton *button = (UIButton *)sender; + NSDictionary * message = [messages objectAtIndex:[button tag]]; + + + if (![[message valueForKey:@"was_comment"] boolValue]) + return; + + NSString * context = [message valueForKey:@"context"]; + if (!context || [context length] == 0) + return; + + NSString * post_id = nil; + int post_id_left = [context rangeOfString:@"/comments/" options:NSCaseInsensitiveSearch].location; + if (post_id_left != NSNotFound) + { + post_id_left += 10; + int post_id_right = [[context substringFromIndex:post_id_left] rangeOfString:@"/" options:NSCaseInsensitiveSearch].location; + if (post_id_right != NSNotFound) + { + post_id = [context substringWithRange:NSMakeRange(post_id_left, post_id_right)]; + NSLog(@"Context Post ID : %@", post_id); + } + } + + if (!post_id) + return; + + NavigationController * nc = (NavigationController *) [self parentViewController]; + [nc setReplyCommentID:[message valueForKey:@"name"]]; + NSMutableDictionary * post = [[NSMutableDictionary alloc] init]; + [post setValue:post_id forKey:@"id"]; + [post setValue:@"" forKey:@"type"]; + [nc browseToPostThreadFromMessage:post]; +} + +- (IBAction)cancelReplyPressed:(id)sender { + NSLog(@"cancelReplyPressed in()"); + UIButton *button = (UIButton *)sender; + NSDictionary * message = [messages objectAtIndex:[button tag]]; + [message setValue:@"NO" forKey:@"showReplyArea"]; + [self refreshRow:[button tag]]; +} + +- (IBAction)showReplyAreaPressed:(id)sender { + NSLog(@"showReplyAreaPressed in()"); + UIButton *button = (UIButton *)sender; + NSDictionary * message = [messages objectAtIndex:[button tag]]; + [message setValue:@"YES" forKey:@"showReplyArea"]; + int row = [button tag]; + + [self refreshRow:[button tag]]; + NSIndexPath * ind = [NSIndexPath indexPathForRow:row inSection:0]; + [[self tableView] scrollToRowAtIndexPath:ind atScrollPosition:UITableViewScrollPositionBottom animated:YES]; + +} + +- (IBAction)submitReplyPressed:(id)sender { + NSLog(@"submitReplyPressed in()"); + UIButton *button = (UIButton *)sender; + NSMutableDictionary * message = [messages objectAtIndex:[button tag]]; + MessageCell * cell = (MessageCell *) [[[[button superview] superview] superview] superview]; + [message setValue:[[cell replyTextView] text] forKey:@"replyText"]; + NavigationController * nc = (NavigationController *) [self parentViewController]; + NSLog([message valueForKey:@"replyText"]); + [nc replyToItem:message]; + [message setValue:@"NO" forKey:@"showReplyArea"]; + [self refreshRow:[button tag]]; +} + + + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + // Navigation logic may go here. Create and push another view controller. + // AnotherViewController *anotherViewController = [[AnotherViewController alloc] initWithNibName:@"AnotherView" bundle:nil]; + // [self.navigationController pushViewController:anotherViewController]; + // [anotherViewController release]; +} + + +/* +// Override to support conditional editing of the table view. +- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { + // Return NO if you do not want the specified item to be editable. + return YES; +} +*/ + + +/* +// Override to support editing the table view. +- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { + + if (editingStyle == UITableViewCellEditingStyleDelete) { + // Delete the row from the data source + [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES]; + } + else if (editingStyle == UITableViewCellEditingStyleInsert) { + // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view + } +} +*/ + + +/* +// Override to support rearranging the table view. +- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath { +} +*/ + + +/* +// Override to support conditional rearranging of the table view. +- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath { + // Return NO if you do not want the item to be re-orderable. + return YES; +} +*/ + + +- (void)dealloc { + [super dealloc]; +} + +- (NSString *) reformatBody:(NSString *) body +{ + body = [body stringByReplacingOccurrencesOfString:@">" withString:@">"]; + body = [body stringByReplacingOccurrencesOfString:@"<" withString:@"<"]; + body = [body stringByReplacingOccurrencesOfString:@"&" withString:@"&"]; + return body; +} + +- (IBAction)apiInboxResponse:(id)sender +{ + NSLog(@"-- apiInboxResponse"); + NSMutableDictionary *data = (NSMutableDictionary *) sender; + + for (NSMutableDictionary * message_data in [[data objectForKey:@"data"] objectForKey:@"children"]) + { + NSMutableDictionary * message = [message_data objectForKey:@"data"]; + CommentsTableViewController *ctvc = [(NavigationController *) [self parentViewController] commentsView]; + [ctvc reformat:message]; +// NSString * body = [self reformatBody:[message valueForKey:@"body"]]; +// [message setValue:body forKey:@"body"]; + + [messages addObject:message]; + } + +// if (unreadMailCount > 0) +// { +// UITabBarController * tb = [(NeuRedditAppDelegate *) [[UIApplication sharedApplication] delegate] tabBarController]; +// UITabBarItem * messagesTab = [[[tb tabBar] items] objectAtIndex:1]; +// [messagesTab setBadgeValue:[NSString stringWithFormat:@"%d",unreadMailCount]]; +// } + + // [posts addObjectsFromArray:[[data objectForKey:@"data"] objectForKey:@"children"]]; + + NSLog(@"Imported %d", [messages count]); + + [[self tableView] reloadData]; + [self completeProgressBar]; + resultsFetched = YES; + +} + + +- (IBAction) fetchMessages:(id)sender +{ + UIButton * button = (UIButton *) sender; + if (button) + [button setEnabled:FALSE]; + [self enableProgressBar]; + NSLog(@"Show more messages..."); + NSString * afterMessageID = @""; + if (!messages) + { + messages = [[NSMutableArray alloc] init]; + } + + if ([messages count] > 0) + { + NSDictionary * lastMessage = [messages objectAtIndex:([messages count] - 1)]; + afterMessageID = [lastMessage valueForKey:@"name"]; + NSLog(@"asking for messages after id [%@]", afterMessageID); + } + [redAPI fetchMessageInboxAfterMessageID:afterMessageID withCallBackTarget:self]; + + [[self tableView] reloadData]; +} + + + +@end + diff --git a/Classes/NavigationController.h b/Classes/NavigationController.h new file mode 100755 index 0000000..751987f --- /dev/null +++ b/Classes/NavigationController.h @@ -0,0 +1,63 @@ +#import +#import +#import "RedditAPI.h" +#import "BrowserViewController.h" +#import "CommentsTableViewController.h" +#import "PostsTableControllerView.h" +#import +#import "MKStoreManager.h" + +@interface NavigationController : UITabBarController +{ +// NSString * postId; +// NSString * browserURL; + BOOL forcePortrait; + BOOL shouldRefreshComments; + BOOL shouldRefreshMessages; + IBOutlet UINavigationController * postsNavigation; + CommentsTableViewController * commentsView; + BrowserViewController * browserView; + NSMutableDictionary * post; + NSTimer * statusNotifyTimer; + RedditAPI * redAPI; + NSUserDefaults * prefs; + float tiltCalibration; + BOOL tiltCalibrationModeActive; + UISegmentedControl * votingSegment; + IBOutlet UIView *proFeaturesSplash; + NSString * replyCommentID; + BOOL isFullscreen; + UIButton * exitFullscreenButton; + UIButton * backFullscreenButton; +} + +//@property (nonatomic, retain) NSString * postId; +@property (nonatomic) BOOL forcePortrait; +@property (assign) NSMutableDictionary * post; +@property BOOL shouldRefreshComments; +@property BOOL shouldRefreshMessages; +@property BOOL isFullscreen; +@property float tiltCalibration; +@property (assign) CommentsTableViewController * commentsView; +@property (assign) BrowserViewController * browserView; +@property (nonatomic, retain) IBOutlet UINavigationController * postsNavigation; +@property (nonatomic, copy) NSString *replyCommentID; +//@property (nonatomic, retain) NSString * browserURL; + +-(void) loadNibs; +-(void) loadCommentsForPost:(NSMutableDictionary *) post; +-(void) browseToLinkFromComment:(NSString *) link; +-(void) browseToLinkFromPost:(NSMutableDictionary *) post; +-(void) browseToLinkFromMessage:(NSMutableDictionary *) link; +-(void) browse; +- (IBAction)voteSegmentChanged:(id)sender; +- (void) upvoteItem:(NSMutableDictionary *) item; +- (void) downvoteItem:(NSMutableDictionary *) item; +- (void) replyToItem:(NSMutableDictionary *) item; +- (void) clearAndRefreshPosts; +- (void) redrawFrames; +- (void) activateTiltCalibrationMode; +- (void) browseToPostThreadFromMessage:(NSMutableDictionary *) npost; +- (void) refreshVotingSegment; +- (void) drawFullScreenBackButton; +@end diff --git a/Classes/NavigationController.m b/Classes/NavigationController.m new file mode 100755 index 0000000..054bb6b --- /dev/null +++ b/Classes/NavigationController.m @@ -0,0 +1,533 @@ +#import "NavigationController.h" +#import "AlienBlueAppDelegate.h" +#import "SettingsTableViewController.h" + +@implementation NavigationController + +@synthesize post; +@synthesize forcePortrait; +@synthesize shouldRefreshComments; +@synthesize shouldRefreshMessages; +@synthesize postsNavigation; +@synthesize browserView; +@synthesize commentsView; +@synthesize tiltCalibration; +@synthesize replyCommentID; +@synthesize isFullscreen; + +static UIImage * up_icon; +static UIImage * down_icon; +static UIImage * up_icon_selected; +static UIImage * down_icon_selected; + + + +- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController +{ + NSLog(@"tab did change"); +// NSLog(@"tab did change to %@", [viewController class]); + [(AlienBlueAppDelegate *) [[UIApplication sharedApplication] delegate] hideConnectionErrorImage]; +} + +-(void) loadNibs +{ + up_icon = [[UIImage imageNamed:@"vote-up-small.png"] retain]; + down_icon = [[UIImage imageNamed:@"vote-down-small.png"] retain]; + up_icon_selected = [[UIImage imageNamed:@"vote-up-selected-small.png"] retain]; + down_icon_selected = [[UIImage imageNamed:@"vote-down-selected-small.png"] retain]; + + prefs = [NSUserDefaults standardUserDefaults]; + commentsView = [[CommentsTableViewController alloc] initWithNibName:@"CommentsView" bundle:nil]; + browserView = [[BrowserViewController alloc] initWithNibName:@"BrowserView" bundle:nil]; + [[self postsNavigation] setDelegate:self]; + redAPI = (RedditAPI *) [(AlienBlueAppDelegate *) [[UIApplication sharedApplication] delegate] redditAPI]; + shouldRefreshMessages = YES; + +// NSMutableDictionary * testPost = [[NSMutableDictionary alloc] init]; +// [self loadCommentsForPost:testPost]; + exitFullscreenButton = [[UIButton buttonWithType:UIButtonTypeCustom] retain]; + [exitFullscreenButton setImage:[UIImage imageNamed:@"show-toolbars-button.png"] forState:UIControlStateNormal]; + [exitFullscreenButton addTarget:self action:@selector(exitFullscreenMode:) forControlEvents:UIControlEventTouchUpInside]; + + backFullscreenButton = [[UIButton buttonWithType:UIButtonTypeCustom] retain]; + [backFullscreenButton setImage:[UIImage imageNamed:@"fullscreen-back-button.png"] forState:UIControlStateNormal]; + [backFullscreenButton addTarget:self action:@selector(goBackInFullscreen:) forControlEvents:UIControlEventTouchUpInside]; + + +} + + +- (void)viewDidLoad +{ + tiltCalibration = 0; + tiltCalibrationModeActive = NO; + UIAccelerometer *accel = [UIAccelerometer sharedAccelerometer]; + [accel setDelegate:self]; + [accel setUpdateInterval:1.0f / 60.0f]; +} + + +- (void) upvoteItem:(NSMutableDictionary *) item +{ + NSLog(@"-- upvotePost"); + if (![redAPI authenticated]) + { + NSLog(@"cannot upvote :: unauthenticated (username %@)", [redAPI authenticatedUser]); + [redAPI showAuthorisationRequiredDialog]; + return; + } + + int previousVoteDirection = [[item valueForKey:@"voteDirection"] intValue]; + if (previousVoteDirection > 0) + { + [item setObject:[NSNumber numberWithInt:0] forKey:@"voteDirection"]; + [item setValue:[NSNumber numberWithInt:[[item valueForKey:@"score"] intValue] - 1] forKey:@"score"]; + } + else + { + [item setObject:[NSNumber numberWithInt:1] forKey:@"voteDirection"]; + [item setValue:[NSNumber numberWithInt:[[item valueForKey:@"score"] intValue] + 1] forKey:@"score"]; + } + [redAPI submitVote:item]; +} + +// In SDK 3.0, larger images loaded inline in comments cause corruption in the status bar +// and tab bar controller. So we call this just in case. +- (void) redrawFrames +{ + NSLog(@"NavController :: Asked For Screen Refresh"); + [[self view] setNeedsDisplay]; +} + +- (void) downvoteItem:(NSMutableDictionary *) item +{ + NSLog(@"-- downvotePost"); + if (![redAPI authenticated]) + { + [redAPI showAuthorisationRequiredDialog]; + return; + } + + int previousVoteDirection = [[item valueForKey:@"voteDirection"] intValue]; + if (previousVoteDirection < 0) + { + [item setObject:[NSNumber numberWithInt:0] forKey:@"voteDirection"]; + [item setValue:[NSNumber numberWithInt:[[item valueForKey:@"score"] intValue] + 1] forKey:@"score"]; + } + else + { + [item setObject:[NSNumber numberWithInt:-1] forKey:@"voteDirection"]; + [item setValue:[NSNumber numberWithInt:[[item valueForKey:@"score"] intValue] - 1] forKey:@"score"]; + } + [redAPI submitVote:item]; + +} + +- (IBAction)voteSegmentChanged:(id)sender +{ + NSLog(@"segment changed"); + UISegmentedControl *segmentedControl = (UISegmentedControl *)sender; + NSString * segment = [segmentedControl titleForSegmentAtIndex: [segmentedControl selectedSegmentIndex]]; + + // upvote + if ([segmentedControl selectedSegmentIndex] == 0) + { + [self upvoteItem:post]; + [self refreshVotingSegment]; + return; + } + else if ([segmentedControl selectedSegmentIndex] == 2) + { + [self downvoteItem:post]; + [self refreshVotingSegment]; + return; + } + + + UIViewController * previousViewController = [[postsNavigation viewControllers] objectAtIndex:[[postsNavigation viewControllers] count] - 2]; + + if ([segment isEqualToString:@"Comments"]) + { + if ([previousViewController isKindOfClass:[PostsTableControllerView class]]) + { + // if we initially went directly to the browser before loading comments, we need to load + // them now. + [self loadCommentsForPost:post]; + } + else + { + // if we're coming back to the comments from the browser view (eg. from an embedded comment + // link, we just pop back so that we don't lose our place in the comments. + [postsNavigation popViewControllerAnimated:YES]; + } + } + else + { + // Handle the case when user navigaties "View Article -> Comments -> View Article" + if ([previousViewController isKindOfClass:[BrowserViewController class]]) + { + [postsNavigation popViewControllerAnimated:YES]; + } + else + [self browseToLinkFromPost:post]; + } + + + + +} + +- (void) clearAndRefreshPosts +{ + PostsTableControllerView * ptcv = (PostsTableControllerView *) [[postsNavigation viewControllers] objectAtIndex:0]; + [ptcv clearAndRefreshFromSettingsLogin]; +} + +- (void) replyToItem:(NSMutableDictionary *) item +{ + NSLog(@"-- reply to item in..."); + if (![item objectForKey:@"replyText"] || [[item valueForKey:@"replyText"] length] == 0) + { + NSLog(@"-- blank reply entered.. ignoring"); + } + else + { + if ([item objectForKey:@"editMode"]) + [redAPI submitChangeReply:item withCallBackTarget:self]; + else + [redAPI submitReply:item withCallBackTarget:self]; + } +} + +- (void)didReceiveMemoryWarning { + // Releases the view if it doesn't have a superview. + NSLog(@"memory warning received in NavigationController"); + [super didReceiveMemoryWarning]; + + // Release any cached data, images, etc that aren't in use. +} + +- (IBAction)apiReplyResponse:(id)sender +{ + NSLog(@"apiReplyResponse in"); + NSString * newCommentID = (NSString *) sender; + NSString * promptString = [NSString stringWithFormat:@"Your reply was submitted."]; + if ([self selectedViewController] == postsNavigation) + { + commentsView.navigationItem.prompt = promptString; + [commentsView afterCommentReply:newCommentID]; + + statusNotifyTimer = [NSTimer scheduledTimerWithTimeInterval:3 + target:self + selector:@selector(clearStatus:) + userInfo:nil + repeats:NO]; + } + else + { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:promptString delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:nil]; + [alert show]; + [alert release]; + } + +} + +- (IBAction)clearStatus:(id)sender +{ + [postsNavigation visibleViewController].navigationItem.prompt = nil; + + if(statusNotifyTimer) + { + [statusNotifyTimer invalidate]; + statusNotifyTimer = nil; + } +} + +- (void) refreshVotingSegment +{ + +// if ([viewController isKindOfClass:[BrowserViewController class]]) +// NSLog(@"-- browser view voting segment"); + + NSArray *segmentTextContent = [NSArray arrayWithObjects: + NSLocalizedString(@"Up", @""), + NSLocalizedString(@"", @""), + NSLocalizedString(@"Down", @""), + nil]; + + votingSegment = [[UISegmentedControl alloc] initWithItems:segmentTextContent]; + [votingSegment addTarget:self + action:@selector(voteSegmentChanged:) + forControlEvents:UIControlEventValueChanged]; + [votingSegment setMomentary:NO]; + + NSString * centerItem; + if ([[postsNavigation visibleViewController] isKindOfClass:[CommentsTableViewController class]]) + centerItem = [NSString stringWithFormat:@"View %@",[[post valueForKey:@"type"] capitalizedString]]; + else if ([[postsNavigation visibleViewController] isKindOfClass:[BrowserViewController class]]) + centerItem = @"Comments"; + else + { +// [postsNavigation visibleViewController].navigationItem.titleView = nil; + return; + } + + +// UISegmentedControl *segmentedControl = [[UISegmentedControl alloc] initWithItems:segmentTextContent]; + votingSegment.selectedSegmentIndex = -1; + + votingSegment.autoresizingMask = UIViewAutoresizingFlexibleWidth; + votingSegment.segmentedControlStyle = UISegmentedControlStyleBar; + // UIBarButtonItem *segmentBarItem = [[[UIBarButtonItem alloc] initWithCustomView:segmentedControl] autorelease]; + + UIImage * upIcon = up_icon; + UIImage * downIcon = down_icon; + + if ([[post valueForKey:@"voteDirection"] intValue] > 0) + { + upIcon = up_icon_selected; + } else if ([[post valueForKey:@"voteDirection"] intValue] < 0) + { + downIcon = down_icon_selected; + } + + + // We don't need the central item for self posts + if ([centerItem isEqualToString:@"View Self"]) + { + centerItem = @""; + [votingSegment setWidth:1 forSegmentAtIndex:1]; + } + else + [votingSegment setWidth:80 forSegmentAtIndex:1]; + + + [votingSegment setWidth:52 forSegmentAtIndex:0]; + [votingSegment setWidth:52 forSegmentAtIndex:2]; + [votingSegment setImage:upIcon forSegmentAtIndex:0]; + [votingSegment setImage:downIcon forSegmentAtIndex:2]; + [votingSegment setTitle:centerItem forSegmentAtIndex:1]; + [postsNavigation visibleViewController].navigationItem.titleView = votingSegment; + [votingSegment release]; +} + +- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated +{ + NSLog(@"*** refreshing voting segment for : %@", [viewController class]); + if (isFullscreen) + [self drawFullScreenBackButton]; + + [self refreshVotingSegment]; +} + + +-(void) loadCommentsForPost:(NSMutableDictionary *) npost +{ + NSLog(@"NavigationController::loadCommentsForPostId in()"); + + // don't refresh comments if we're loading the same comment thread + // incase the user accidentally clicked back (we don't want them to + // lose their place in the comment thread +// if (post && [[post valueForKey:@"name"] isEqualToString:[npost valueForKey:@"name"]]) +// shouldRefreshComments = NO; +// else +// shouldRefreshComments = YES; + + post = npost; + [postsNavigation popToRootViewControllerAnimated:NO]; + [postsNavigation pushViewController:commentsView animated:YES]; + shouldRefreshComments = YES; +} + +-(void) browse +{ + NSLog(@"browse in()"); +} + +-(void) browseToLinkFromPost:(NSMutableDictionary *) npost +{ + post = npost; + if ([postsNavigation visibleViewController] != browserView) + [postsNavigation pushViewController:browserView animated:YES]; + [browserView browseToLink:[post valueForKey:@"url"] fromMessages:NO]; +} + +-(void) browseToPostThreadFromMessage:(NSMutableDictionary *) npost +{ + [self setShouldRefreshMessages:NO]; + [self loadCommentsForPost:npost]; + [self setSelectedIndex:0]; +} + + +-(void) browseToLinkFromMessage:(NSMutableDictionary *) link +{ + [self setShouldRefreshMessages:NO]; + if ([postsNavigation visibleViewController] != browserView) + [postsNavigation pushViewController:browserView animated:YES]; + [browserView browseToLink:[link valueForKey:@"url"] fromMessages:YES]; + // browserView.navigationItem.titleView = nil; + [self setSelectedIndex:0]; +} + +-(void) browseToLinkFromComment:(NSString *) link +{ + UIViewController * previousViewController = [[postsNavigation viewControllers] objectAtIndex:[[postsNavigation viewControllers] count] - 2]; + if (previousViewController == browserView) + [postsNavigation popViewControllerAnimated:YES]; + else if ([postsNavigation visibleViewController] != browserView) + [postsNavigation pushViewController:browserView animated:YES]; + [browserView browseToLink:link fromMessages:NO]; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation +{ + +// // don't allow rotation for settings +// if ([self selectedIndex] == 2) +// return NO; + + return [[prefs valueForKey:@"allow_rotation"] boolValue]; +// return (interfaceOrientation == UIInterfaceOrientationPortrait); + +} + +- (void)activateTiltCalibrationMode +{ + tiltCalibrationModeActive = YES; +} + +- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration +{ + if (![[prefs valueForKey:@"allow_tilt_scroll"] boolValue]) + return; + + if (tiltCalibrationModeActive) + { + NSLog(@"Tilt Motion :: Calibrating"); + tiltCalibration = acceleration.z; + tiltCalibrationModeActive = NO; + return; + } + + float tilt_threshold = 0.1f; //0.08f; + float tilt_scroll_ratio = 9.0f; + float max_accel = 0.6f; + + float acceleration_z; + if (acceleration.z > 0) + { + acceleration_z = acceleration.z - tiltCalibration;// + tilt_threshold; + if (acceleration_z > max_accel) acceleration_z = max_accel; + } + else + { + acceleration_z = acceleration.z - tiltCalibration;// - tilt_threshold; + if (acceleration_z < (-1 * max_accel)) acceleration_z = -1 * max_accel; + } + + +// NSLog(@"accelerometer in : %f", acceleration_z); + + if (fabs(acceleration.z - tiltCalibration) > tilt_threshold) + { + float direction = -1; + if ([[prefs valueForKey:@"reverse_tilt_axis"] boolValue]) + direction = 1; + + float speed = direction * tilt_scroll_ratio * (acceleration_z); +// NSLog(@"speed : %f", speed); + UIScrollView * scrollview; + + // try the posts/comments view first + if ([self selectedIndex] == 0) + { + if ([postsNavigation visibleViewController] != browserView) + { + scrollview = (UIScrollView *) [[postsNavigation visibleViewController] view]; + + CGPoint contentOffset = [scrollview contentOffset]; + contentOffset.y = contentOffset.y + speed; + + if ((contentOffset.y < -30 && speed < 0) || (contentOffset.y > [scrollview contentSize].height - 410 && speed > 0)) + return; + else + [scrollview setContentOffset:contentOffset animated:NO]; + } + } +// else if ([self selectedIndex] == 1 || [self selectedIndex] == 2) +// { +// scrollview = (UIScrollView *) [[self selectedViewController] view]; +// } + } +} + +- (IBAction)showHelp:(id)sender +{ + NSLog(@"showHelp in()"); + [self setSelectedIndex:2]; + SettingsTableViewController * settingsView = (SettingsTableViewController *) [[self viewControllers] objectAtIndex:2]; + [settingsView showTipsSplash]; +} + +- (void) drawFullScreenBackButton +{ + [backFullscreenButton removeFromSuperview]; + if ([[postsNavigation viewControllers] count] > 1) + { + CGRect bounds = [[self view] bounds]; + [backFullscreenButton setFrame:CGRectMake(0, bounds.size.height - 31, 43, 31)]; + [[self view] addSubview:backFullscreenButton]; + } +} + +- (IBAction)goBackInFullscreen:(id)sender +{ + if ([[postsNavigation viewControllers] count] > 1) + [postsNavigation popViewControllerAnimated:YES]; +} + +- (IBAction)exitFullscreenMode:(id)sender +{ + [exitFullscreenButton removeFromSuperview]; + [backFullscreenButton removeFromSuperview]; +// [self removeHideFullscreenButton]; + [[self tabBar] setHidden:NO]; + [postsNavigation setNavigationBarHidden:NO animated:YES]; + + isFullscreen = NO; + + if ([[postsNavigation visibleViewController] isMemberOfClass:[PostsTableControllerView class]]) + [(PostsTableControllerView *) [postsNavigation visibleViewController] refreshSegmentControl]; + +} + + +- (IBAction)enterFullscreenMode:(id)sender +{ + + [exitFullscreenButton removeFromSuperview]; + [[self tabBar] setHidden:YES]; + [postsNavigation setNavigationBarHidden:YES animated:YES]; + + isFullscreen = YES; + + if ([[postsNavigation visibleViewController] isMemberOfClass:[PostsTableControllerView class]]) + [(PostsTableControllerView *) [postsNavigation visibleViewController] refreshSegmentControl]; + CGRect bounds = [[self view] bounds]; + [exitFullscreenButton setFrame:CGRectMake(bounds.size.width / 2 - 83, bounds.size.height - 31, 166, 31)]; + [[self view] addSubview:exitFullscreenButton]; + + [self drawFullScreenBackButton]; +} + +- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation +{ + NSLog(@"Post View rotated"); + if (isFullscreen) + [self enterFullscreenMode:nil]; + [super didRotateFromInterfaceOrientation:fromInterfaceOrientation]; +} + + +@end diff --git a/Classes/PostCell.h b/Classes/PostCell.h new file mode 100755 index 0000000..d29275f --- /dev/null +++ b/Classes/PostCell.h @@ -0,0 +1,24 @@ +// +// PostCell.h +// Alien Blue +// +// Created by Jason Morrissey on 28/03/10. +// Copyright 2010 The Design Shed. All rights reserved. +// + +#import +#import "ImageCache.h" + +@interface PostCell : UITableViewCell { + NSMutableDictionary * post; + UIViewController * postController; + CGRect buttonFrame; + BOOL isEditing; +} + +@property (nonatomic, retain) NSMutableDictionary *post; +@property (nonatomic, retain) UIViewController * postController; + +- (IBAction) imageReadyCallback: (id)sender; + +@end diff --git a/Classes/PostCell.m b/Classes/PostCell.m new file mode 100755 index 0000000..96dc4a1 --- /dev/null +++ b/Classes/PostCell.m @@ -0,0 +1,188 @@ +// +// PostCell.m +// Alien Blue +// +// Created by Jason Morrissey on 28/03/10. +// Copyright 2010 The Design Shed. All rights reserved. +// + +#import "PostCell.h" +#import "PostsTableControllerView.h" + +@implementation PostCell + +@synthesize post; +@synthesize postController; + + +static NSUserDefaults * prefs; + ++ (void)initialize { + // Unlikely to have any subclasses, but check class nevertheless. + if (self == [PostCell class]) { + NSLog(@"PC :: PostCell initialise in()"); + prefs = [NSUserDefaults standardUserDefaults]; + + } +} + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + + if (self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier]) { + +// CGRect cellViewFrame = CGRectMake(0.0, 0.0, self.contentView.bounds.size.width, self.contentView.bounds.size.height); +// commentCellView = [[CommentCellView alloc] initWithFrame:cellViewFrame]; +// commentCellView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; +// [self.contentView addSubview:commentCellView]; +// [self setAccessoryType:UITableViewCellAccessoryDisclosureIndicator]; + [self setAccessoryType:UITableViewCellAccessoryNone]; + [self setSelectionStyle:UITableViewCellSelectionStyleNone]; + [self setClipsToBounds:YES]; + } + return self; +} + +//- (id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier { +// if (self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier]) { +// // Initialization code +// } +// return self; +//} + + +- (void)setSelected:(BOOL)selected animated:(BOOL)animated { + + [super setSelected:selected animated:animated]; + + // Configure the view for the selected state +} + +- (void)setPost:(NSMutableDictionary *) newPost { +// NSLog(@"PostCell :: setPost in()"); + post = newPost; + [self setNeedsDisplay]; +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event +{ +// NSLog(@"hitTest in"); + CGPoint touchPoint = [[touches anyObject] locationInView:self]; + if (CGRectContainsPoint(buttonFrame, touchPoint)) + { + NSLog(@"comments button pressed"); + [(PostsTableControllerView *) postController showCommentsForPost:post]; + } + else + { + [(PostsTableControllerView *) postController openLinkForPost:post]; + } + +} + +- (IBAction) imageReadyCallback: (id)sender +{ + NSLog(@"** Post Cell : Image Ready Callback"); + [self setNeedsDisplay]; + +} + +- (void) drawThumbnail:(float) vertical_offset +{ +// NSLog(@"* PostCell :: draw thumbnail in"); + UIImage * thumb = [ImageCache imageForURL:[post valueForKey:@"thumbnail"] withCallBackTarget:self]; + if (thumb) + { + CGRect thumbFrame = CGRectMake(5, vertical_offset, 65, 47); + [thumb drawInRect:thumbFrame]; + } + +} + +- (void) drawTransparentOverlay +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextSetRGBFillColor(context, 0.05, 0.05, 0.15, 0.7); + CGRect rect = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height); + CGContextFillRect(context, rect); +} + +- (void)drawRect:(CGRect)rect { + +// NSLog(@"Post Cell :: drawRect in()"); + CGRect contentRect = self.bounds; + + [[Resources cNormal] set]; + float vertical_offset = 15; + [[post valueForKey:@"title"] drawInRect:CGRectMake(15, vertical_offset, contentRect.size.width - 45, contentRect.size.height - 20) withFont:[Resources mainFont]]; + + CGSize constraintSize = CGSizeMake(contentRect.size.width - 45, MAXFLOAT); + CGSize labelSize = [[post valueForKey:@"title"] sizeWithFont:[Resources mainFont] constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap]; + vertical_offset += labelSize.height + 15; + + if ([[post valueForKey:@"type"] isEqualToString:@"video"]) + [[Resources videoIcon] drawAtPoint:CGPointMake(15, vertical_offset)]; + else if ([[post valueForKey:@"type"] isEqualToString:@"image"]) + [[Resources imageIcon] drawAtPoint:CGPointMake(15, vertical_offset)]; + else + [[Resources articleIcon] drawAtPoint:CGPointMake(15, vertical_offset)]; + + if ([prefs boolForKey:@"show_thumbs"] && + [[post valueForKey:@"thumbnail"] length] > 0) + [self drawThumbnail:vertical_offset]; + + + NSString * strComments = [NSString stringWithFormat: @"%d comment(s)", [[post objectForKey:@"num_comments"] intValue]]; + buttonFrame = CGRectMake(80, vertical_offset, 205, 40); + + CGRect labelFrame = CGRectMake(80, vertical_offset + 10, 205, 40); + [[Resources barImage] drawInRect:buttonFrame]; + [strComments drawInRect:labelFrame withFont:[Resources secondaryFont] lineBreakMode:UILineBreakModeTailTruncation alignment:UITextAlignmentCenter]; + + vertical_offset += 50; + + [[[post valueForKey:@"score"] stringValue] drawInRect:CGRectMake(15, vertical_offset, 45, 30) withFont:[Resources tertiaryFont] lineBreakMode:UILineBreakModeTailTruncation alignment:UITextAlignmentCenter]; + +// [[post valueForKey:@"domain"] drawAtPoint:CGPointMake(84, vertical_offset) withFont:tertiaryFont]; + [[post valueForKey:@"domain"] drawInRect:CGRectMake(84, vertical_offset, 100, 15) withFont:[Resources tertiaryFont] lineBreakMode:UILineBreakModeClip alignment:UITextAlignmentLeft]; + [[post valueForKey:@"subreddit"] drawInRect:CGRectMake(196, vertical_offset, self.bounds.size.width - 235, 15) withFont:[Resources tertiaryFont] lineBreakMode:UILineBreakModeClip alignment:UITextAlignmentRight]; + + vertical_offset += 20; + + CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextSetRGBFillColor(context, 0.05, 0.05, 0.15, 0.2); + rect = CGRectMake(0, self.bounds.size.height - 4, self.bounds.size.width, 4); + CGContextFillRect(context, rect); + + [[Resources rightArrowDimImage] drawInRect:CGRectMake(self.bounds.size.width - 22, (vertical_offset / 2) - 5, 17, 26)]; + +// if (isEditing) +// [self drawTransparentOverlay]; + +} + +//- (void)setEditing:(BOOL)editing animated:(BOOL)animate +//{ +//// isEditing = editing; +//// if ([self contentView]) +//// [self setNeedsDisplay]; +//// +// +//// if (post == [NSMutableDictionary class]) +//// { +//// if (editing) +//// [post setValue:[NSNumber numberWithBool:YES] forKey:@"editing"]; +//// else +//// [post setValue:[NSNumber numberWithBool:NO] forKey:@"editing"]; +//// [self setNeedsDisplay]; +//// } +// +// [super setEditing:editing animated:animate]; +//} + + +- (void)dealloc { + [super dealloc]; +} + + +@end diff --git a/Classes/PostsTableControllerView.h b/Classes/PostsTableControllerView.h new file mode 100755 index 0000000..b34fd7a --- /dev/null +++ b/Classes/PostsTableControllerView.h @@ -0,0 +1,63 @@ +// +// PostsTableControllerView.h +// Alien Blue +// +// Created by Jason Morrissey on 28/03/10. +// Copyright 2010 The Design Shed. All rights reserved. +// + +#import +#import "JSON.h" +#import "PostCell.h" +#import "RedditAPI.h" + +@interface PostsTableControllerView : UITableViewController { + NSMutableArray * posts; + NSMutableArray * subreddits; + NSString * subreddit; + NSTimer * progressTimer; + NSTimer * timer; + IBOutlet UISegmentedControl *segmentControl; + UISegmentedControl * scrollSegmentControl; + IBOutlet UIView * subredditPickView; + IBOutlet UIView * subredditManualEnterView; + IBOutlet UIPickerView * subredditScrollingPickerView; + IBOutlet UITextField * subredditManualText; + IBOutlet UIButton * subredditDoneButton; + UIBarButtonItem * introButton; + UIBarButtonItem * fullscreenButton; + RedditAPI * redAPI; + float ProgressValue; + NSUserDefaults * prefs; + BOOL resultsFetched; + +// // we use this when switching to/from fullscreen mode +// CGRect oldTableViewFrame; + + // We maintain two hide queues, one that the API calls to Reddit, and this one. + // The reason for this localHideQueue is that a "hide" call may not be finished + // before a fetchPosts call is made. This would result in one or two posts + // being shown even though the user wanted to hide them. + NSMutableArray * localHideQueue; +} + +@property (readwrite, copy) NSString * subreddit; +@property (nonatomic, retain) IBOutlet UISegmentedControl *segmentControl; +@property (nonatomic, retain) IBOutlet UIView *subredditPickView; +@property (nonatomic, retain) IBOutlet UIPickerView *subredditScrollingPickerView; +@property (nonatomic, retain) IBOutlet UIView *subredditManualEnterView; +@property (nonatomic, retain) IBOutlet UITextField *subredditManualText; +@property (nonatomic, retain) IBOutlet UIButton *subredditDoneButton; + +- (IBAction) fetchPosts:(id)sender; +- (IBAction)loadPosts:(id)sender; +- (IBAction)chooseSubredditButton:(id)sender; +- (IBAction)cancelSubredditSelect:(id)sender; +- (void) hideSinglePost:(NSMutableDictionary *) post; +- (void) showCommentsForPost:(NSMutableDictionary *) post; +- (void) openLinkForPost:(NSMutableDictionary *) post; +- (void) clearAndRefreshFromSettingsLogin; +- (void) refreshSegmentControl; +- (void) removeSegmentControl; +@end + diff --git a/Classes/PostsTableControllerView.m b/Classes/PostsTableControllerView.m new file mode 100755 index 0000000..ee52cb4 --- /dev/null +++ b/Classes/PostsTableControllerView.m @@ -0,0 +1,1214 @@ +// +// PostsTableControllerView.m +// Alien Blue +// +// Created by Jason Morrissey on 28/03/10. +// Copyright 2010 The Design Shed. All rights reserved. +// + +#import "PostsTableControllerView.h" +#import "NavigationController.h" +#import "AlienBlueAppDelegate.h" + +@implementation PostsTableControllerView + +@synthesize subreddit; +@synthesize segmentControl; +@synthesize subredditPickView; +@synthesize subredditManualEnterView; +@synthesize subredditScrollingPickerView; +@synthesize subredditManualText; +@synthesize subredditDoneButton; + +static UIImage * proFeatureLabelImage; + +/* +- (id)initWithStyle:(UITableViewStyle)style { + // Override initWithStyle: if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad. + if (self = [super initWithStyle:style]) { + } + return self; +} +*/ + +- (void) enableProgressBar +{ + progressTimer = [NSTimer scheduledTimerWithTimeInterval:1 + target:self + selector:@selector(updateProgressBar:) + userInfo:nil + repeats:YES]; + +} + + + +- (void) refreshRow:(int) row +{ + NSMutableArray * affectedRows = [[NSMutableArray alloc] init]; + NSIndexPath * ind = [NSIndexPath indexPathForRow:row inSection:0]; + [affectedRows addObject:ind]; + [[self tableView] reloadRowsAtIndexPaths:affectedRows withRowAnimation:UITableViewRowAnimationNone]; + [affectedRows release]; +} + +- (void) refreshProgressBar:(float) pr +{ + ProgressValue = pr; + int row = [[self tableView] numberOfRowsInSection:0] - 1; + [self refreshRow:row]; +} + +- (void) completeProgressBar +{ + [self refreshProgressBar:0.0]; + if(progressTimer) + { +// [progressTimer invalidate]; + progressTimer = nil; + } +} + +-(void) updateProgressBar: (NSTimer *) theTimer +{ + if (ProgressValue <= 0.9) + { + [self refreshProgressBar:(ProgressValue + 0.05)]; + } + else + { +// [theTimer invalidate]; + theTimer = nil; + } +} + +- (void) processHideQueue: (NSTimer *) theTimer +{ + int queue_length = [redAPI hideQueueLength]; + if (queue_length > 0) + { + if([[prefs valueForKey:@"show_hide_queue"] boolValue]) + { + NSString * promptString = [NSString stringWithFormat:@"Hiding Posts (%d in queue)", queue_length]; + self.navigationItem.prompt = promptString; + } + [redAPI processFirstInPostQueue]; + } + else + { + self.navigationItem.prompt = nil; + [localHideQueue removeAllObjects]; + } + + // re-shift the floating front/subreddit/new segment control along + // with the increased top navigation size + [self refreshSegmentControl]; +} + +- (void) shiftSubredditManualFieldUp +{ + [UIView beginAnimations:nil context:NULL]; + [UIView setAnimationDuration:0.3]; + [UIView setAnimationCurve:UIViewAnimationCurveLinear]; + [UIView setAnimationBeginsFromCurrentState:YES]; + + CGRect manual_view = [subredditManualEnterView frame]; + + // The transform matrix + CGAffineTransform transform = CGAffineTransformMakeTranslation(0, -1 * manual_view.origin.y + 30); + [subredditPickView setTransform:transform]; + [UIView commitAnimations]; +} + +- (void) shiftSubredditManualFieldDown +{ + [UIView beginAnimations:nil context:NULL]; + [UIView setAnimationDuration:0.3]; + [UIView setAnimationCurve:UIViewAnimationCurveLinear]; + [UIView setAnimationBeginsFromCurrentState:YES]; + + + // The transform matrix + CGAffineTransform transform = CGAffineTransformMakeTranslation(0, 20); + [subredditPickView setTransform:transform]; + [UIView commitAnimations]; + +} + +- (void) subredditPickerShouldShow:(BOOL) show +{ + CGRect redditPickFrame = [subredditPickView frame]; + [UIView beginAnimations:nil context:NULL]; + [UIView setAnimationDuration:0.3]; + [UIView setAnimationCurve:UIViewAnimationCurveLinear]; + [UIView setAnimationBeginsFromCurrentState:YES]; + + // this hack is required to fix the overlay of the pick view background + // when switching to portrait view while choosing a subreddit. + if (redditPickFrame.size.height == 504.0) + { + redditPickFrame.size.height = 664.0; +// [subredditPickView setFrame:redditPickFrame]; + } + + +// CGFloat shift = [[self view] frame].size.height + 40; + CGFloat shift = redditPickFrame.size.height; + if (!show) + shift = shift * -1; + else + shift = 20; + + + NSLog(@"shifting : %f", shift); + + // The transform matrix + CGAffineTransform transform = CGAffineTransformMakeTranslation(0, shift); + [subredditPickView setTransform:transform]; + [UIView commitAnimations]; +} + +- (void) loadSubscribedSubreddits +{ + // load default subreddits for unauthenticated users + if (![redAPI authenticated]) + { + [subreddits release]; + subreddits = [[NSMutableArray alloc] init]; + [subreddits addObject:@"/r/announcements/"]; + [subreddits addObject:@"/r/AskReddit/"]; + [subreddits addObject:@"/r/blog/"]; + [subreddits addObject:@"/r/funny/"]; + [subreddits addObject:@"/r/gaming/"]; + [subreddits addObject:@"/r/pics/"]; + [subreddits addObject:@"/r/politics/"]; + [subreddits addObject:@"/r/programming/"]; + [subreddits addObject:@"/r/reddit.com/"]; + [subreddits addObject:@"/r/science/"]; + [subreddits addObject:@"/r/worldnews/"]; + [subreddits addObject:@"/r/WTF/"]; + [subredditScrollingPickerView reloadAllComponents]; + [subredditPickView setHidden:FALSE]; + [self subredditPickerShouldShow:YES]; + [[self segmentControl] setTitle:@"Subreddit" forSegmentAtIndex:1]; + } + else + { + [redAPI fetchSubscribedRedditsWithCallBackTarget:self]; + } +} + +- (IBAction) postCategorySegmentSelected:(id)sender +{ + UISegmentedControl *segmentedControl = (UISegmentedControl *)sender; + NSString * segment = [segmentedControl titleForSegmentAtIndex: [segmentedControl selectedSegmentIndex]]; + if ([segment isEqualToString:@"Subreddit"]) + { + [segmentedControl setTitle:@"Loading..." forSegmentAtIndex:1]; + [self loadSubscribedSubreddits]; + return; + } else if ([segment isEqualToString:@"Saved"]) + { + if (![redAPI authenticated]) + { + [redAPI showAuthorisationRequiredDialog]; + return; + } + self.navigationItem.title = @"Saved Posts"; + subreddit = @"/saved/"; + } + else if ([segment isEqualToString:@"Hidden"]) + { + if (![redAPI authenticated]) + { + [redAPI showAuthorisationRequiredDialog]; + return; + } + self.navigationItem.title = @"Hidden Posts"; + if([prefs objectForKey:@"username"]) + { + subreddit = [[NSString stringWithFormat: @"/user/%@/hidden/", [prefs stringForKey:@"username"]] retain]; + } + } + else if ([segment isEqualToString:@"Front"]) + { + self.navigationItem.title = @"Front Page"; + subreddit = @""; + } + else if ([segment isEqualToString:@"New"]) + { + self.navigationItem.title = @"New"; + subreddit = @"/new/"; + } + + [posts removeAllObjects]; + [[self tableView] reloadData]; + [self fetchPosts:nil]; +} + + + + + +- (void)textFieldDidBeginEditing:(UITextField *)textField +{ + if (textField == [self subredditManualText]) + { + [self shiftSubredditManualFieldUp]; + } +} + +- (void)textFieldDidEndEditing:(UITextField *)textField +{ + if (textField == [self subredditManualText]) + { + [self shiftSubredditManualFieldDown]; + } + +} + +// need this for the manual entry into subreddit +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + NSLog(@"textFieldShouldReturn in()"); + [textField resignFirstResponder]; + return NO; +} + +- (void)pickerView:(UIPickerView *)thePickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component { + NSLog(@"picker item selected"); + if (row == [subreddits count]) + { + [subredditManualEnterView setHidden:FALSE]; + } + else + { + [subredditManualEnterView setHidden:TRUE]; + } +} + +- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)thePickerView { +// NSLog(@"numberOfComponentsInPickerView in"); +// return 1; + return 1; +} + +//PickerViewController.m +- (NSInteger)pickerView:(UIPickerView *)thePickerView numberOfRowsInComponent:(NSInteger)component { +// return 5; + return [subreddits count] + 1; +} + +- (NSString *)pickerView:(UIPickerView *)thePickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component { + if (row == [subreddits count]) + return @"Other..."; + else + return [subreddits objectAtIndex:row]; +// return @"hello"; + +// return [arrayColors objectAtIndex:row]; +} + + +- (IBAction)apiSubredditsResponse:(id)sender +{ + NSMutableDictionary *data = (NSMutableDictionary *) sender; + + NSArray * subreddit_response = + [[data objectForKey:@"data"] objectForKey:@"children"]; + + if (subreddit_response) { + [subreddits release]; + subreddits = [[NSMutableArray alloc] init]; + NSMutableArray * unsorted = [[NSMutableArray alloc] init]; + for (NSMutableDictionary * sr in subreddit_response) + { + [unsorted addObject:[[sr objectForKey:@"data"] valueForKey:@"url"]]; + } + [subreddits addObjectsFromArray: [unsorted sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]]; + [unsorted release]; + NSLog(@"Imported %d Subreddits", [subreddits count]); + [subredditScrollingPickerView reloadAllComponents]; + [subredditPickView setHidden:FALSE]; + [self subredditPickerShouldShow:YES]; + [[self segmentControl] setTitle:@"Subreddit" forSegmentAtIndex:1]; + } +} + +- (void) loadTestSubreddits +{ + SBJSON *parser = [[SBJSON alloc] init]; + + NSString *jsonfile = [[NSBundle mainBundle] pathForResource:@"test-subreddits" ofType:@"json"]; + + NSString *json_string = [[NSString alloc] initWithContentsOfFile:jsonfile]; + NSMutableDictionary *data = [parser objectWithString:json_string error:nil]; + [self apiSubredditsResponse:data]; +} + + + +- (void) positionSubredditPickFrameFromOrientation:(UIInterfaceOrientation)fromInterfaceOrientation +{ + if (fromInterfaceOrientation == UIInterfaceOrientationPortrait) + { + // the orientation is now landscape, and we need to resize the UIPickerView for the + // limited vertical space. + CGRect pickerFrame = [subredditScrollingPickerView frame]; + pickerFrame.size.width = 480; + pickerFrame.size.height = 120; + [subredditScrollingPickerView setFrame:pickerFrame]; + + CGRect manualFrame = [subredditManualEnterView frame]; + manualFrame.origin.y = 180; + [subredditManualEnterView setFrame:manualFrame]; + + CGRect doneButtonFrame = [subredditDoneButton frame]; + doneButtonFrame.origin.y = 250; + [subredditDoneButton setFrame:doneButtonFrame]; + } + else + { + // orientation has moved from landscape back to portrait + CGRect subredditFrame = [subredditPickView frame]; + NSLog(@"frame height: %f", subredditFrame.size.height); + + CGRect pickerFrame = [subredditScrollingPickerView frame]; + pickerFrame.size.width = 320; + pickerFrame.size.height = 216; + [subredditScrollingPickerView setFrame:pickerFrame]; + + CGRect manualFrame = [subredditManualEnterView frame]; + manualFrame.origin.y = 300; + [subredditManualEnterView setFrame:manualFrame]; + + CGRect doneButtonFrame = [subredditDoneButton frame]; + doneButtonFrame.origin.y = 380; + [subredditDoneButton setFrame:doneButtonFrame]; + } +} + +- (IBAction)scrollingSegmentChanged:(id)sender +{ + NSLog(@"scrolling segment changed"); + UISegmentedControl *segmentedControl = (UISegmentedControl *)sender; +// NSString * segment = [segmentedControl titleForSegmentAtIndex: [segmentedControl selectedSegmentIndex]]; + if ([segmentedControl selectedSegmentIndex] == 0) + [[self tableView] scrollRectToVisible:CGRectMake(0,0,1,1) animated:YES]; + else if ([segmentedControl selectedSegmentIndex] == 1) + { + NSIndexPath * ind = [NSIndexPath indexPathForRow:[posts count] inSection:0]; + [[self tableView] scrollToRowAtIndexPath:ind atScrollPosition:UITableViewScrollPositionTop animated:YES]; + } + + +// segmentedControl.selectedSegmentIndex = -1; +// [segmentedControl setSelectedSegmentIndex:-1]; +} + + +- (void) createScrollingSegment +{ + + NSArray * segmentTextContent = [NSArray arrayWithObjects: + NSLocalizedString(@"Up", @""), + NSLocalizedString(@"Down", @""), + nil]; + scrollSegmentControl = [[UISegmentedControl alloc] initWithItems:segmentTextContent]; + [scrollSegmentControl addTarget:self + action:@selector(scrollingSegmentChanged:) + forControlEvents:UIControlEventValueChanged]; + + [scrollSegmentControl setMomentary:YES]; +// scrollSegmentControl.selectedSegmentIndex = -1; + scrollSegmentControl.autoresizingMask = UIViewAutoresizingFlexibleWidth; + scrollSegmentControl.segmentedControlStyle = UISegmentedControlStyleBar; + + UIImage * up_icon = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"top-scroll" ofType:@"png"]]; + UIImage * down_icon = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"bottom-scroll" ofType:@"png"]]; + + [scrollSegmentControl setWidth:30 forSegmentAtIndex:0]; + [scrollSegmentControl setWidth:30 forSegmentAtIndex:1]; + [scrollSegmentControl setImage:up_icon forSegmentAtIndex:0]; + [scrollSegmentControl setImage:down_icon forSegmentAtIndex:1]; + UIBarButtonItem *segmentBarItem = [[[UIBarButtonItem alloc] initWithCustomView:scrollSegmentControl] autorelease]; + self.navigationItem.leftBarButtonItem = segmentBarItem; + [scrollSegmentControl release]; +} + +- (void) removeSegmentControl +{ + [segmentControl removeFromSuperview]; +} + +- (void) refreshSegmentControl +{ + [self removeSegmentControl]; + NavigationController * nc = (NavigationController *) [[self parentViewController] parentViewController]; + + // no need to draw the segment control if we're in fullscreen mode. + // or if we're not on the PostsNavigation tab + if ([nc isFullscreen] || [nc selectedIndex] != 0) + return; + + CGRect segmentFrame = [segmentControl frame]; + segmentFrame.origin.y = [[[self navigationController] navigationBar] frame].size.height + 18; + + if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation)) + segmentFrame.size.width = [[[UIApplication sharedApplication] keyWindow] frame].size.height; + else + segmentFrame.size.width = [[[UIApplication sharedApplication] keyWindow] frame].size.width; + [segmentControl setFrame:segmentFrame]; +// [[nc view] addSubview:segmentControl]; + [[nc view] insertSubview:segmentControl belowSubview:subredditPickView]; + [[self tableView] setContentInset:UIEdgeInsetsMake(24,0,0,0)]; +// [[self tableView] setContentOffset:contentOffset]; + // contentOffset.y = -50; + // [scrollview setContentOffset:contentOffset animated:NO]; + +} + + + +- (void)viewDidLoad { + NSLog(@"table view loaded"); + NavigationController * nc = (NavigationController *) [[self parentViewController] parentViewController]; + + proFeatureLabelImage = [[UIImage imageNamed:@"pro-feature-label.png"] retain]; + [super viewDidLoad]; + prefs = [NSUserDefaults standardUserDefaults]; + ProgressValue = 0; + resultsFetched = NO; + redAPI = [(AlienBlueAppDelegate *) [[UIApplication sharedApplication] delegate] redditAPI]; + +// [[self.navigationController navigationItem] setPrompt:@"Hello"]; +// [[self.navigationController +// +// + timer = [NSTimer scheduledTimerWithTimeInterval:2 + target:self + selector:@selector(processHideQueue:) + userInfo:nil + repeats:YES]; + + + // if the subreddit variable has not been set, default to the front page. + if (!subreddit) + { + subreddit = @""; + } + + [segmentControl addTarget:self + action:@selector(postCategorySegmentSelected:) + forControlEvents:UIControlEventValueChanged]; + +// +// UIScrollView * scrollview = (UIScrollView *) [[self tableView] superview]; +// CGRect tableFrame = [scrollview frame]; +// tableFrame.origin.y = 300; +// [scrollview setFrame:tableFrame]; +// CGPoint contentOffset = [scrollview contentOffset]; +// contentOffset.y = -50; +// [scrollview setContentOffset:contentOffset animated:NO]; + + // CGRect tableFrame = [[self tableView] scr +// tableFrame.origin.y = 262; +// tableFrame.size.height -= 62; +// [[self tableView] setFrame:tableFrame]; + + NSLog(@"segment set"); + + [subredditPickView setHidden:TRUE]; + [self subredditPickerShouldShow:NO]; + + [[[[self parentViewController] parentViewController] view] addSubview:subredditPickView]; + + + UIBarButtonItem *newBackButton = [[UIBarButtonItem alloc] initWithTitle: @"Posts" style: UIBarButtonItemStyleBordered target: nil action: nil]; + [[self navigationItem] setBackBarButtonItem: newBackButton]; + [newBackButton release]; + + localHideQueue = [[NSMutableArray alloc] init]; + + + + introButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"info-icon.png"] style:UIBarButtonItemStyleBordered target:nc action:@selector(showHelp:)]; + + fullscreenButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"fullscreen-icon.png"] style:UIBarButtonItemStyleBordered target:nc action:@selector(enterFullscreenMode:)]; + // [[[self parentViewController] view] addSubview:subredditPickView]; + + +// NSString * background_file = [[NSBundle mainBundle] pathForResource:@"background-blue" ofType:@"png"]; +// UIImage * background = [[UIImage alloc] initWithContentsOfFile:background_file]; +// [[self view] setBackgroundImage:background forState:UIControlStateNormal]; + // Uncomment the following line to display an Edit button in the navigation bar for this view controller. +// self.navigationItem.rightBarButtonItem = self.editButtonItem; +// [self.editButtonItem setTitle:@"Hide"]; + +// [(NavigationController *) [[self navigationController] tabBarController] loadCommentsForPostId:@"myID!"]; + +// NavigationController * nc = (NavigationController *) [self parentViewController]; +// [nc loadCommentsForPostId:@"bonv4"]; + + self.navigationItem.title = @"Front Page"; + [self fetchPosts:nil]; + +} + + + +- (void)viewWillAppear:(BOOL)animated { +// [self.navigationController setNavigationBarHidden:YES animated:animated]; + [super viewWillAppear:animated]; +} + + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + [segmentControl setHidden:NO]; + + // refresh look of visible rows (e.g. if we come back after change to night-mode) + [[self tableView] reloadRowsAtIndexPaths:[[self tableView] indexPathsForVisibleRows] withRowAnimation:NO]; + + + +// if ([[prefs valueForKey:@"show_quick_scroll"] boolValue]) +// [self createScrollingSegment]; +// else + self.navigationItem.leftBarButtonItem = fullscreenButton; + + if ([[prefs valueForKey:@"show_help_icon"] boolValue]) + [[self navigationItem] setRightBarButtonItem:introButton]; + else + self.navigationItem.rightBarButtonItem = nil; + + [self refreshSegmentControl]; + + [[self tableView] setScrollsToTop:[[prefs valueForKey:@"allow_status_bar_scroll"] boolValue]]; +} + + +- (void)viewWillDisappear:(BOOL)animated { +// [self.navigationController setNavigationBarHidden:NO animated:animated]; + + [segmentControl setHidden:YES]; + + [super viewWillDisappear:animated]; +} + +/* +- (void)viewDidDisappear:(BOOL)animated { + [super viewDidDisappear:animated]; +} +*/ + + +// Override to allow orientations other than the default portrait orientation. +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + // Return YES for supported orientations +// return (interfaceOrientation == UIInterfaceOrientationPortrait); + return NO; +} + + +- (void)didReceiveMemoryWarning { + // Releases the view if it doesn't have a superview. + NSLog(@"memory warning received in PostsTableController"); + [ImageCache resetImageCache]; + [super didReceiveMemoryWarning]; + + // Release any cached data, images, etc that aren't in use. +} + +- (void)viewDidUnload { + // Release any retained subviews of the main view. + // e.g. self.myOutlet = nil; +} + + +#pragma mark Table view methods + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + + +// Customize the number of rows in the table view. +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return [posts count] + 1; +} + +- (UITableViewCell *) createViewMoreCell +{ + UITableViewCell * moreCell = [[UITableViewCell alloc] init]; + + // this is necessary to allow correct centering of HideAll and ShowMore buttons + // when rotating the device. + if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation)) + [moreCell setFrame:CGRectMake(0, 0, 390, 300)]; + else + [moreCell setFrame:CGRectMake(0, 0, 320, 300)]; + + + [moreCell setSelectionStyle:UITableViewCellSelectionStyleNone]; + UIButton * showMoreButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; + CGRect buttonFrame = CGRectMake(100, 25, 200, 40); + [showMoreButton setFrame:buttonFrame]; + UIProgressView * progress = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault]; + CGRect progressFrame = CGRectMake(60, 75, 200, 40); + [progress setFrame:progressFrame]; + + +// if ([posts count] == 0 && [redAPI hideQueueLength] == 0) + if ([redAPI loadingMessages]) + [showMoreButton setTitle:@"Loading..." forState:UIControlStateNormal]; + else + [showMoreButton setTitle:@"Show more..." forState:UIControlStateNormal]; + [showMoreButton setTitleColor:[Resources cNormal] forState:UIControlStateNormal]; + [showMoreButton setTitleColor:[Resources cTitleColor] forState:UIControlStateDisabled]; + [showMoreButton setBackgroundImage:[Resources barImage] forState:UIControlStateNormal]; + + + + [showMoreButton addTarget:self action:@selector(fetchPosts:) forControlEvents:UIControlEventTouchUpInside]; + [moreCell addSubview:showMoreButton]; + + // hide the progress bar when it isn't in use. +// if (ProgressValue > 0.0 && ProgressValue < 1.0) + if([redAPI loadingPosts]) + { + [progress setHidden:NO]; + [progress setProgress:ProgressValue]; + [showMoreButton setEnabled:FALSE]; + } + else + { + [showMoreButton setEnabled:TRUE]; + [progress setHidden:YES]; + + if (![MKStoreManager isProUpgraded]) + { + UIImageView * proLabelView = [[UIImageView alloc] initWithImage:proFeatureLabelImage]; + [proLabelView setFrame:CGRectMake(38, 76, 67, 10)]; + [proLabelView setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin]; + [moreCell addSubview:proLabelView]; + } + } + + [progress setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin]; + [moreCell setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin]; + [moreCell addSubview:progress]; + + UIButton * hideAllButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; + [hideAllButton addTarget:self action:@selector(hideAll:) forControlEvents:UIControlEventTouchUpInside]; + [hideAllButton setTitle:@"Hide All" forState:UIControlStateNormal]; + + // in night mode, the hide button needs to be modified so that the white background + // doesn't stick out like a sore thumb + if ([prefs boolForKey:@"night_mode"]) + { + [hideAllButton setBackgroundImage:[Resources barImage] forState:UIControlStateNormal]; + [hideAllButton setTitleColor:[Resources cTitleColor] forState:UIControlStateNormal]; + } + + +// [hideAllButton setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin]; +// [showMoreButton setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin]; + + [hideAllButton setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin]; + [showMoreButton setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin]; + + + // disable "Hide All" feature when browsing Hidden or Saved Posts: + if (![subreddit isEqualToString:@""] && + [subreddit rangeOfString:@"/r/" options:NSCaseInsensitiveSearch].location == NSNotFound) + { + [hideAllButton setEnabled:NO]; +// [hideAllButton setBackgroundColor:[UIColor grayColor]]; + } + else + { + [hideAllButton setEnabled:YES]; +// [hideAllButton setBackgroundColor:[UIColor whiteColor]]; + } + + + buttonFrame.origin.x = 20; + buttonFrame.size.width = 100; + [hideAllButton setFrame:buttonFrame]; + [moreCell addSubview:hideAllButton]; + return moreCell; +} + +- (UITableViewCell *) createNothingHereCell +{ + UITableViewCell * nCell = [[UITableViewCell alloc] init]; + UILabel * nothingHereLabel = [[UILabel alloc] initWithFrame:CGRectMake(60, 25, 200, 40)]; + [nothingHereLabel setTextAlignment:UITextAlignmentCenter]; + [nothingHereLabel setText:@"Nothing here."]; + [nothingHereLabel setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin]; + [nothingHereLabel setBackgroundColor:[UIColor clearColor]]; + [nothingHereLabel setTextColor:[UIColor whiteColor]]; + [nothingHereLabel setFont:[UIFont systemFontOfSize:19]]; + [nCell setSelectionStyle:UITableViewCellSelectionStyleNone]; + [nCell addSubview:nothingHereLabel]; + [nCell setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin]; + return nCell; +} + +#define ASYNC_IMAGE_TAG 9999 + +// Customize the appearance of table view cells. +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + // empty set + if (indexPath.row == 0 && [posts count] == 0 && resultsFetched && [redAPI hideQueueLength] == 0) + { + return [self createNothingHereCell]; + } + + if (indexPath.row == [posts count]) + return [self createViewMoreCell]; + + NSString * CellIdentifier = @"FastPostCell"; + + NSMutableDictionary * post = [posts objectAtIndex:indexPath.row]; + + PostCell *cell = (PostCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; + + + if (cell == nil) { + cell = [[[PostCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; + cell.frame = CGRectMake(0.0, 0.0, 320.0, 100); + + } + + [cell setTag:indexPath.row]; + [cell setPostController:self]; + [cell setPost:post]; + + return cell; +} + + +// +//// Customize the appearance of table view cells. +//- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { +// +// // empty set +// if (indexPath.row == 0 && [posts count] == 0 && resultsFetched && [redAPI hideQueueLength] == 0) +// { +// return [self createNothingHereCell]; +// } +// +// if (indexPath.row == [posts count]) +// return [self createViewMoreCell]; +// +// static NSString *CellIdentifier = @"customCell"; +// PostCell * cell = (PostCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; +// if (cell == nil) { +// NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"CustomPostCell" owner:nil options:nil]; +// for(id currentObject in topLevelObjects) +// { +// if([currentObject isKindOfClass:[PostCell class]]) +// { +// cell = (PostCell *)currentObject; +// break; +// } +// } +// } +// NSDictionary * p = [posts objectAtIndex:indexPath.row]; +// [[cell titleText] setText:[p objectForKey:@"title"]]; +// [[cell subreddit] setText:[p objectForKey:@"subreddit"]]; +// [[cell points] setText:[[p objectForKey:@"score"] stringValue]]; +// [[cell domain] setText:[p objectForKey:@"domain"]]; +// +//// [[cell num_comments] setText:[NSString stringWithFormat: @"%d comment(s)", [[p objectForKey:@"num_comments"] intValue]]]; +// +// [[cell viewForBackground] setBackgroundColor:[UIColor clearColor]]; +// +// [[cell commentsButton] setTitle:[NSString stringWithFormat: @"%d comment(s)", [[p objectForKey:@"num_comments"] intValue]] forState:UIControlStateNormal]; +// [[cell commentsButton] setTag:indexPath.row]; +// [[cell commentsButton] addTarget:self action:@selector(showComments:) forControlEvents:UIControlEventTouchUpInside]; +// +// return cell; +//} + +- (void) showCommentsForPost:(NSMutableDictionary *) post +{ + NavigationController * nc = (NavigationController *) [[self parentViewController] parentViewController]; + [nc loadCommentsForPost:post]; +} + +//- (IBAction)showComments:(id)sender +//{ +// NSLog(@"showComments in()"); +// UIButton *button = (UIButton *)sender; +// NavigationController * nc = (NavigationController *) [[self parentViewController] parentViewController]; +// NSDictionary * post = [posts objectAtIndex:[button tag]]; +// +//// [nc loadCommentsForPostId:[post objectForKey:@"id"]]; +// [nc loadCommentsForPost:post]; +//} + + +- (void) openLinkForPost:(NSMutableDictionary *) post +{ + NSLog(@"open link clicked"); + NavigationController * nc = (NavigationController *) [[self parentViewController] parentViewController]; + + if ([RedditAPI isSelfLink:[post valueForKey:@"domain"]]) + { + [nc loadCommentsForPost:post]; + } + else + { + [nc browseToLinkFromPost:post]; + } + +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + // Navigation logic may go here. Create and push another view controller. + // AnotherViewController *anotherViewController = [[AnotherViewController alloc] initWithNibName:@"AnotherView" bundle:nil]; + // [self.navigationController pushViewController:anotherViewController]; + // [anotherViewController release]; + + + // This check is necessary, as the "Show more..." cell is the last cell in the table + // and shouldn't be selected. +// if (indexPath.row < [posts count]) +// { +// NavigationController * nc = (NavigationController *) [[self parentViewController] parentViewController]; +// NSMutableDictionary * post = [posts objectAtIndex:indexPath.row]; +// NSRange range = [[post valueForKey:@"domain"] rangeOfString : @"self."]; +// if (range.location != NSNotFound) { +// [nc loadCommentsForPost:post]; +// } +// else +// { +//// NSString * url = [post objectForKey:@"url"]; +//// [nc setPostId:[[post valueForKey:@"id"] copy]]; +// [nc browseToLinkFromPost:post]; +// } +// +// +//// [tableView reloadData]; +//// PostCell * cell = (PostCell * ) [tableView cellForRowAtIndexPath:indexPath]; +//// [[cell viewForBackground] setBackgroundColor:[[UIColor alloc] initWithRed:20.0 / 255 green:59.0 / 255 blue:102.0 / 255 alpha:0.3]]; +////// [[cell buttonToolbar] setHidden:NO]; +// NSLog(@"Selected Row %d", indexPath.row); +// } +} + + + +// Override to support conditional editing of the table view. +- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { + // Return NO if you do not want the specified item to be editable. + + // don't allow swipe-to-hide if the we're viewing Hidden or Saved Posts: + if (![subreddit isEqualToString:@""] && + [subreddit rangeOfString:@"/r/" options:NSCaseInsensitiveSearch].location == NSNotFound) + return NO; + + if (indexPath.row < [posts count]) + return YES; + else + return NO; +} + + +- (void) hideSinglePost:(NSMutableDictionary *) post +{ + // Delete the row from the data source + NSLog(@"-- hiding post: %@", [post valueForKey:@"name"]); + int row = [posts indexOfObject:post]; + if (row == NSNotFound) + return; + +// NSDictionary * post = [posts objectAtIndex:indexPath.row]; + [redAPI addPostToHideQueue:[post valueForKey:@"name"]]; + NSIndexPath * indexPath = [NSIndexPath indexPathForRow:row inSection:0]; + [posts removeObjectAtIndex:indexPath.row]; + [[self tableView] deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES]; + [[self tableView] reloadData]; +} + + //Override to support editing the table view. +- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { + + if (editingStyle == UITableViewCellEditingStyleDelete) { + [self hideSinglePost:[posts objectAtIndex:indexPath.row]]; +// [self hidePostAtRow:indexPath.row]; + } + else if (editingStyle == UITableViewCellEditingStyleInsert) { + // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view + } +} + + + +/* +// Override to support rearranging the table view. +- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath { +} +*/ + + +/* +// Override to support conditional rearranging of the table view. +- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath { + // Return NO if you do not want the item to be re-orderable. + return YES; +} +*/ + + +- (void)dealloc { + [posts release]; + [super dealloc]; +} + + +- (BOOL) shouldFilterOutPost:(NSMutableDictionary *) post +{ + NSMutableArray * filterList = (NSMutableArray *) [prefs objectForKey:@"filterList"]; + if (!filterList || [filterList count] == 0) + return NO; + + NSString * postTitle = [post valueForKey:@"title"]; + for (NSString * filterItem in filterList) + { + if ([postTitle rangeOfString:filterItem options:NSCaseInsensitiveSearch].location != NSNotFound) + return YES; + } + + return NO; +} + +- (IBAction)apiPostsResponse:(id)sender +{ + + NSMutableDictionary *data = (NSMutableDictionary *) sender; + + for (NSDictionary * post_data in [[data objectForKey:@"data"] objectForKey:@"children"]) + { + NSMutableDictionary * post = [post_data objectForKey:@"data"]; + // we check if the incoming posts are in the hide queue because Reddit may not + // yet have been notified that the user doesn't want to see the particular post. + // due to delays in rate-limiting. + // if (![redAPI isPostInHideQueue:[post valueForKey:@"name"]]) + if (![localHideQueue containsObject:[post valueForKey:@"name"]] + && ![self shouldFilterOutPost:post] + ) + { + [Resources processPost:post]; + + // start caching thumbnail + if([prefs boolForKey:@"show_thumbs"]) + [ImageCache imageForURL:[post valueForKey:@"thumbnail"] withCallBackTarget:nil]; + + [posts addObject:post]; + } + } + +// [posts addObjectsFromArray:[[data objectForKey:@"data"] objectForKey:@"children"]]; + + NSLog(@"Imported %d", [posts count]); + + [[self tableView] reloadData]; + [self completeProgressBar]; + resultsFetched = YES; +} + + +- (void) loadTestPosts +{ + SBJSON *parser = [[SBJSON alloc] init]; + + NSString *jsonfile = [[NSBundle mainBundle] pathForResource:@"home" ofType:@"json"]; + + NSString *json_string = [[NSString alloc] initWithContentsOfFile:jsonfile]; + NSMutableDictionary *data = [parser objectWithString:json_string error:nil]; + [self apiPostsResponse:data]; +} + +- (void) clearAndRefreshFromSettingsLogin +{ + [posts removeAllObjects]; + [[self tableView] reloadData]; + [self fetchPosts:nil]; +} + +- (IBAction) hideAll:(id)sender +{ + if (![MKStoreManager isProUpgraded]) + { + [MKStoreManager needProAlert]; + return; + } + + if (![redAPI authenticated]) + { + [redAPI showAuthorisationRequiredDialog]; + return; + } + + + + for (NSDictionary * post in posts) + { + [redAPI addPostToHideQueue:[post valueForKey:@"name"]]; + [localHideQueue addObject:[post valueForKey:@"name"]]; + } + [posts removeAllObjects]; + [[self tableView] reloadData]; + + // load a fresh set of results + [self fetchPosts:nil]; +} + + +- (IBAction) fetchPosts:(id)sender +{ + resultsFetched = NO; + + [self enableProgressBar]; + NSLog(@"Show more ..."); + NSString * afterPostID = @""; + if (!posts) + { + posts = [[NSMutableArray alloc] init]; + } + + if (posts && [posts count] > 0) + { + NSDictionary * lastPost = [posts objectAtIndex:([posts count] - 1)]; + afterPostID = [lastPost valueForKey:@"name"]; + NSLog(@"asking for posts after id [%@] in subreddit [%@]", afterPostID, subreddit); + } + + [redAPI fetchPostsForSubreddit:subreddit afterPostID:afterPostID callBackTarget:self]; + + [[self tableView] reloadData]; +} + + + +- (IBAction)loadPosts:(id)sender +{ + NSLog(@"Load Posts Clicked!"); +// [self refreshProgressBar:0.5]; +// [self enableProgressBar]; + +// RedditAPI * redAPI = [(NeuRedditAppDelegate *) [[UIApplication sharedApplication] delegate] redditAPI]; +// [redAPI fetchPostsForSubreddit:@"" afterPostID:@"" callBackTarget:self]; + + +// +// [posts release]; +// posts = [[NSMutableArray alloc] init]; +// +// SBJSON *parser = [[SBJSON alloc] init]; +// +// NSString *jsonfile = [[NSBundle mainBundle] pathForResource:@"home" ofType:@"json"]; +// +// NSString *json_string = [[NSString alloc] initWithContentsOfFile:jsonfile]; +// NSMutableDictionary *data = [parser objectWithString:json_string error:nil]; +// +// [posts addObjectsFromArray:[[data objectForKey:@"data"] objectForKey:@"children"]]; +// +// NSLog(@"Imported %d", [posts count]); +// +// NSDictionary * p = [[posts objectAtIndex:20] objectForKey:@"data"]; +// NSLog([p objectForKey:@"title"]); +// +// +//// for (int i=0; i<50; i++) { +//// Post *post = [[Post alloc] init]; +//// NSString *s = [NSString stringWithFormat: @"Title : %d", i]; +//// [post setTitleText:@"title "]; +//// [post setAuthor:@"author name!"]; +//// [posts addObject:post]; +//// } +// [self.tableView reloadData]; +} + +- (NSString *) tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath +{ + return @"Hide Post"; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath +{ + // empty set + if (indexPath.row == 0 && [posts count] == 0 && resultsFetched && [redAPI hideQueueLength] == 0) + return 150; + + if (indexPath.row == [posts count]) + return 150; + + float cs = 0; + CGSize constraintSize = CGSizeMake([[self tableView] frame].size.width - 45, MAXFLOAT); + NSMutableDictionary * post = [posts objectAtIndex:indexPath.row]; + CGSize labelSize = [[post valueForKey:@"title"] sizeWithFont:[Resources mainFont] constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap]; + cs += labelSize.height; + cs += 120; + return cs; + +} + +- (IBAction) textFieldDone: (id) sender +{ + [sender resignFirstResponder]; +} + +- (IBAction)cancelSubredditSelect:(id)sender +{ + [self subredditPickerShouldShow:NO]; + [[self segmentControl] setTitle:@"Subreddit" forSegmentAtIndex:1]; +} + +- (IBAction)chooseSubredditButton:(id)sender +{ + NSLog(@"choose subreddit button in()"); + [subredditManualText resignFirstResponder]; + [self shiftSubredditManualFieldDown]; + int row = [subredditScrollingPickerView selectedRowInComponent:0]; + + // custom reddit entered + if (row == [subreddits count]) + { + NSString * custom = [[self subredditManualText] text]; + int slash_location = [custom rangeOfString:@"/" options:NSCaseInsensitiveSearch].location; + // append a "/" if the user didn't enter it in. + // the slash_location == 0 check is used to handle entry like /pics/ (with prepending /) + if (slash_location == NSNotFound || slash_location == 0) + custom = [custom stringByAppendingString:@"/"]; + custom = [@"/r/" stringByAppendingString:custom]; + subreddit = [custom copy]; + } + else + { + subreddit = [subreddits objectAtIndex:row]; + } + self.navigationItem.title = subreddit; + [self subredditPickerShouldShow:NO]; + [posts removeAllObjects]; + [[self tableView] reloadData]; + [self fetchPosts:nil]; + NSLog(@"Selected Subreddit : %@", subreddit); +} + +- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation +{ + NSLog(@"Post View rotated"); + [self refreshSegmentControl]; + + [self positionSubredditPickFrameFromOrientation:fromInterfaceOrientation]; + [[self tableView] reloadRowsAtIndexPaths:[[self tableView] indexPathsForVisibleRows] withRowAnimation:NO]; +} + +@end + diff --git a/Classes/Reachability.h b/Classes/Reachability.h new file mode 100755 index 0000000..668adfa --- /dev/null +++ b/Classes/Reachability.h @@ -0,0 +1,88 @@ +/* + + File: Reachability.h + Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs. + + Version: 2.0 + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. + ("Apple") in consideration of your agreement to the following terms, and your + use, installation, modification or redistribution of this Apple software + constitutes acceptance of these terms. If you do not agree with these terms, + please do not use, install, modify or redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject + to these terms, Apple grants you a personal, non-exclusive license, under + Apple's copyrights in this original Apple software (the "Apple Software"), to + use, reproduce, modify and redistribute the Apple Software, with or without + modifications, in source and/or binary forms; provided that if you redistribute + the Apple Software in its entirety and without modifications, you must retain + this notice and the following text and disclaimers in all such redistributions + of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Inc. may be used + to endorse or promote products derived from the Apple Software without specific + prior written permission from Apple. Except as expressly stated in this notice, + no other rights or licenses, express or implied, are granted by Apple herein, + including but not limited to any patent rights that may be infringed by your + derivative works or by other works in which the Apple Software may be + incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO + WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED + WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN + COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR + DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF + CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF + APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2009 Apple Inc. All Rights Reserved. + +*/ + + +#import +#import + +typedef enum { + NotReachable = 0, + ReachableViaWiFi, + ReachableViaWWAN +} NetworkStatus; +#define kReachabilityChangedNotification @"kNetworkReachabilityChangedNotification" + +@interface Reachability: NSObject +{ + BOOL localWiFiRef; + SCNetworkReachabilityRef reachabilityRef; +} + +//reachabilityWithHostName- Use to check the reachability of a particular host name. ++ (Reachability*) reachabilityWithHostName: (NSString*) hostName; + +//reachabilityWithAddress- Use to check the reachability of a particular IP address. ++ (Reachability*) reachabilityWithAddress: (const struct sockaddr_in*) hostAddress; + +//reachabilityForInternetConnection- checks whether the default route is available. +// Should be used by applications that do not connect to a particular host ++ (Reachability*) reachabilityForInternetConnection; + +//reachabilityForLocalWiFi- checks whether a local wifi connection is available. ++ (Reachability*) reachabilityForLocalWiFi; + +//Start listening for reachability notifications on the current run loop +- (BOOL) startNotifer; +- (void) stopNotifer; + +- (NetworkStatus) currentReachabilityStatus; +//WWAN may be available, but not active until a connection has been established. +//WiFi may require a connection for VPN on Demand. +- (BOOL) connectionRequired; +@end + + diff --git a/Classes/Reachability.m b/Classes/Reachability.m new file mode 100755 index 0000000..5e1fd7a --- /dev/null +++ b/Classes/Reachability.m @@ -0,0 +1,274 @@ +/* + + File: Reachability.m + Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs. + + Version: 2.0 + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. + ("Apple") in consideration of your agreement to the following terms, and your + use, installation, modification or redistribution of this Apple software + constitutes acceptance of these terms. If you do not agree with these terms, + please do not use, install, modify or redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject + to these terms, Apple grants you a personal, non-exclusive license, under + Apple's copyrights in this original Apple software (the "Apple Software"), to + use, reproduce, modify and redistribute the Apple Software, with or without + modifications, in source and/or binary forms; provided that if you redistribute + the Apple Software in its entirety and without modifications, you must retain + this notice and the following text and disclaimers in all such redistributions + of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Inc. may be used + to endorse or promote products derived from the Apple Software without specific + prior written permission from Apple. Except as expressly stated in this notice, + no other rights or licenses, express or implied, are granted by Apple herein, + including but not limited to any patent rights that may be infringed by your + derivative works or by other works in which the Apple Software may be + incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO + WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED + WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN + COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR + DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF + CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF + APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2009 Apple Inc. All Rights Reserved. + +*/ + +#import +#import +#import +#import +#import +#import + +#import + +#import "Reachability.h" + +#define kShouldPrintReachabilityFlags 1 + +static void PrintReachabilityFlags(SCNetworkReachabilityFlags flags, const char* comment) +{ +#if kShouldPrintReachabilityFlags + + NSLog(@"Reachability Flag Status: %c%c %c%c%c%c%c%c%c %s\n", + (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', + (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', + + (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', + (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', + (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', + (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-', + comment + ); +#endif +} + + +@implementation Reachability +static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) +{ + #pragma unused (target, flags) + NSCAssert(info != NULL, @"info was NULL in ReachabilityCallback"); + NSCAssert([(NSObject*) info isKindOfClass: [Reachability class]], @"info was wrong class in ReachabilityCallback"); + + //We're on the main RunLoop, so an NSAutoreleasePool is not necessary, but is added defensively + // in case someon uses the Reachablity object in a different thread. + NSAutoreleasePool* myPool = [[NSAutoreleasePool alloc] init]; + + Reachability* noteObject = (Reachability*) info; + // Post a notification to notify the client that the network reachability changed. + [[NSNotificationCenter defaultCenter] postNotificationName: kReachabilityChangedNotification object: noteObject]; + + [myPool release]; +} + +- (BOOL) startNotifer +{ + BOOL retVal = NO; + SCNetworkReachabilityContext context = {0, self, NULL, NULL, NULL}; + if(SCNetworkReachabilitySetCallback(reachabilityRef, ReachabilityCallback, &context)) + { + if(SCNetworkReachabilityScheduleWithRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)) + { + retVal = YES; + } + } + return retVal; +} + +- (void) stopNotifer +{ + if(reachabilityRef!= NULL) + { + SCNetworkReachabilityUnscheduleFromRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + } +} + +- (void) dealloc +{ + [self stopNotifer]; + if(reachabilityRef!= NULL) + { + CFRelease(reachabilityRef); + } + [super dealloc]; +} + ++ (Reachability*) reachabilityWithHostName: (NSString*) hostName; +{ + Reachability* retVal = NULL; + SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]); + if(reachability!= NULL) + { + retVal= [[[self alloc] init] autorelease]; + if(retVal!= NULL) + { + retVal->reachabilityRef = reachability; + retVal->localWiFiRef = NO; + } + } + return retVal; +} + ++ (Reachability*) reachabilityWithAddress: (const struct sockaddr_in*) hostAddress; +{ + SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress); + Reachability* retVal = NULL; + if(reachability!= NULL) + { + retVal= [[[self alloc] init] autorelease]; + if(retVal!= NULL) + { + retVal->reachabilityRef = reachability; + retVal->localWiFiRef = NO; + } + } + return retVal; +} + ++ (Reachability*) reachabilityForInternetConnection; +{ + struct sockaddr_in zeroAddress; + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + return [self reachabilityWithAddress: &zeroAddress]; +} + ++ (Reachability*) reachabilityForLocalWiFi; +{ + [super init]; + struct sockaddr_in localWifiAddress; + bzero(&localWifiAddress, sizeof(localWifiAddress)); + localWifiAddress.sin_len = sizeof(localWifiAddress); + localWifiAddress.sin_family = AF_INET; + // IN_LINKLOCALNETNUM is defined in as 169.254.0.0 + localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); + Reachability* retVal = [self reachabilityWithAddress: &localWifiAddress]; + if(retVal!= NULL) + { + retVal->localWiFiRef = YES; + } + return retVal; +} + +#pragma mark Network Flag Handling + +- (NetworkStatus) localWiFiStatusForFlags: (SCNetworkReachabilityFlags) flags +{ + PrintReachabilityFlags(flags, "localWiFiStatusForFlags"); + + BOOL retVal = NotReachable; + if((flags & kSCNetworkReachabilityFlagsReachable) && (flags & kSCNetworkReachabilityFlagsIsDirect)) + { + retVal = ReachableViaWiFi; + } + return retVal; +} + +- (NetworkStatus) networkStatusForFlags: (SCNetworkReachabilityFlags) flags +{ + PrintReachabilityFlags(flags, "networkStatusForFlags"); + if ((flags & kSCNetworkReachabilityFlagsReachable) == 0) + { + // if target host is not reachable + return NotReachable; + } + + BOOL retVal = NotReachable; + + if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0) + { + // if target host is reachable and no connection is required + // then we'll assume (for now) that your on Wi-Fi + retVal = ReachableViaWiFi; + } + + + if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0)) + { + // ... and the connection is on-demand (or on-traffic) if the + // calling application is using the CFSocketStream or higher APIs + + if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0) + { + // ... and no [user] intervention is needed + retVal = ReachableViaWiFi; + } + } + + if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN) + { + // ... but WWAN connections are OK if the calling application + // is using the CFNetwork (CFSocketStream?) APIs. + retVal = ReachableViaWWAN; + } + return retVal; +} + +- (BOOL) connectionRequired; +{ + NSAssert(reachabilityRef != NULL, @"connectionRequired called with NULL reachabilityRef"); + SCNetworkReachabilityFlags flags; + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + return (flags & kSCNetworkReachabilityFlagsConnectionRequired); + } + return NO; +} + +- (NetworkStatus) currentReachabilityStatus +{ + NSAssert(reachabilityRef != NULL, @"currentNetworkStatus called with NULL reachabilityRef"); + NetworkStatus retVal = NotReachable; + SCNetworkReachabilityFlags flags; + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + if(localWiFiRef) + { + retVal = [self localWiFiStatusForFlags: flags]; + } + else + { + retVal = [self networkStatusForFlags: flags]; + } + } + return retVal; +} +@end diff --git a/Classes/RedditAPI.h b/Classes/RedditAPI.h new file mode 100755 index 0000000..eb39569 --- /dev/null +++ b/Classes/RedditAPI.h @@ -0,0 +1,80 @@ +// +// RedditAPI.h +// Alien Blue +// +// Created by Jason Morrissey on 4/04/10. +// Copyright 2010 The Design Shed. All rights reserved. +// + +#import +#import "JSON.h" + +@interface RedditAPI : NSObject { + NSString * modhash; + NSString * cookie; + NSMutableDictionary * connections; + NSMutableArray * hideQueue; + NSString * authenticatedUser; + + id loginResultCallBackTarget; + id postFetchResultCallBackTarget; + id commentFetchResultCallBackTarget; + id subredditListCallBackTarget; + id inboxCallBackTarget; + id unreadMessageCountCallBackTarget; + id runAfterLoginTarget; + id replyResultCallBackTarget; + + NSString * runAfterLoginMethod; + + id runAfterInboxCheckTarget; + NSString * runAfterInboxCheckMethod; + + BOOL authenticated; + int unreadMessageCount; + BOOL loadingPosts; + BOOL loadingMessages; + NSUserDefaults * prefs; +} + +@property (retain) NSString * modhash; +@property (retain) NSString * cookie; +@property (retain) NSString * authenticatedUser; +@property BOOL authenticated; +@property BOOL loadingPosts; +@property BOOL loadingMessages; +@property int unreadMessageCount; + +- (void) loginUser:(NSString *) username withPassword:(NSString *) password callBackTarget:(id) target; + +- (void) fetchPostsForSubreddit:(NSString *)subreddit afterPostID:(NSString *) postID callBackTarget:(id) target; +- (void) fetchCommentsForPostID:(NSString *) postID callBackTarget:(id) target; + +- (void) fetchMessageInboxAfterMessageID:(NSString *) messageID withCallBackTarget:(id) target; +- (void) authenticateWithCallbackTarget:(id) target andCallBackAction:(NSString *) method; + +- (int) hideQueueLength; +- (void) processFirstInPostQueue; +- (BOOL) isPostInHideQueue:(NSString *) postID; +- (void) addPostToHideQueue:(NSString *) postID; +- (void) hidePostWithID: (NSString *) postID; +- (void) savePostWithID: (NSString *) postID; +- (void) unsavePostWithID: (NSString *) postID; +- (void) fetchUnreadMessageCount:(id) target; +- (void) submitVote: (NSMutableDictionary *) item; +- (void) submitReply: (NSMutableDictionary *) item withCallBackTarget:(id) target; +- (void) showAuthorisationRequiredDialog; +- (void) fetchSubscribedRedditsWithCallBackTarget:(id) target; +- (void) submitChangeReply: (NSMutableDictionary *) item withCallBackTarget:(id) target; +- (void) unhidePostWithID: (NSString *) postID; +- (void) unhideResponseReceived:(id) sender; +- (void) testReachability; + ++ (BOOL) isImageLink:(NSString *) link; ++ (BOOL) isVideoLink:(NSString *) link; ++ (BOOL) isSelfLink:(NSString *) link; ++ (NSString *) getLinkType:(NSString *) url; ++ (NSString *) fixImgurLink:(NSString *) link; ++ (NSString *) useLowResImgurVersion:(NSString *) link; + +@end diff --git a/Classes/RedditAPI.m b/Classes/RedditAPI.m new file mode 100755 index 0000000..9c09e30 --- /dev/null +++ b/Classes/RedditAPI.m @@ -0,0 +1,914 @@ +// +// RedditAPI.m +// Alien Blue +// +// Created by Jason Morrissey on 4/04/10. +// Copyright 2010 The Design Shed. All rights reserved. +// + +#import "RedditAPI.h" +#import "Reachability.h" +#import "AlienBlueAppDelegate.h" + +@implementation RedditAPI + +@synthesize modhash; +@synthesize cookie; +@synthesize authenticated; +@synthesize unreadMessageCount; +@synthesize authenticatedUser; +@synthesize loadingPosts; +@synthesize loadingMessages; + +//NSString * server = @"http://10.1.1.2:8080"; +//NSString * cookieDomain = @"10.1.1.2"; + +NSString * server = @"http://www.reddit.com"; +NSString * cookieDomain = @".reddit.com"; + +- (id) init +{ + self = [super init]; + if (self != nil) { + NSLog(@"initialising RedditAPI"); + authenticatedUser = @""; + loadingPosts = NO; + loadingMessages = NO; + unreadMessageCount = 0; + connections = [[NSMutableDictionary alloc] init]; + hideQueue = [[NSMutableArray alloc] init]; + prefs = [NSUserDefaults standardUserDefaults]; + if([prefs objectForKey:@"modhash"]) + modhash = [prefs objectForKey:@"modhash"]; + else + modhash = @""; + if([prefs objectForKey:@"cookie"]) + cookie = [prefs objectForKey:@"cookie"]; + else + cookie = @""; + NSLog(@"modhash: %@", modhash); + NSLog(@"cookie: %@", cookie); + if([prefs objectForKey:@"username"]) + { + NSLog(@"username:%@", [prefs objectForKey:@"username"]); + authenticatedUser = [[prefs objectForKey:@"username"] copy]; + } + authenticated = NO; + } + return self; +} + +- (int) hideQueueLength +{ + return [hideQueue count]; +} + +- (void) processFirstInPostQueue +{ + if ([self hideQueueLength] > 0) + { + NSLog(@"-- hiding post : %@", [hideQueue objectAtIndex:0]); + [self hidePostWithID:[hideQueue objectAtIndex:0]]; + [hideQueue removeObjectAtIndex:0]; + } +} + + +- (void) showAuthorisationRequiredDialog +{ + NSLog(@"-- unauthorised --"); + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Please login." message:@"You need to enter your Reddit username and password in the 'Settings' panel." delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:nil]; + [alert show]; + [alert release]; +} + + +- (BOOL) isPostInHideQueue:(NSString *) postID +{ + return [hideQueue containsObject:postID]; +} + +- (void) addPostToHideQueue:(NSString *) postID +{ + if (![self isPostInHideQueue:postID]) + [hideQueue addObject:postID]; +} + + +// here is the flow for authentication +// 1. check inbox +// if not authorised, then 2. login. +// this is the order that methods are called in this process: +// authenticate => fetchfetchMessageInboxWithCallBackTarget +// => apiUnreadMessageCountResponse => authenticateStage2 => +// (if necessary) loginUser => apiLoginResponse => callbacks +// by the end of this process, the authenticated variable will +// be set to YES if the user is authorised. + +- (void) authenticateWithCallbackTarget:(id) target andCallBackAction:(NSString *) method +{ + NSLog(@"authenticate stage 1 in()"); + runAfterLoginTarget = target; + runAfterLoginMethod = method; + + // Only bother with all of this authentication stuff if the user has already entered a + // username and password (otherwise we can assume it is an unregistered user). + if ([prefs objectForKey:@"username"] && [prefs objectForKey:@"password"] + && [[prefs objectForKey:@"username"] length] > 0 && [[prefs objectForKey:@"password"] length] > 0) + { + // first try to hit the inbox and see if we're authenticated + runAfterInboxCheckTarget = self; + runAfterInboxCheckMethod = @"authenticateStage2:"; + [self fetchUnreadMessageCount:self]; +// [self fetchMessageInboxAfterMessageID:@"" withCallBackTarget:self]; + } + else + { + // assume unregistered user and run callbacks + if (runAfterLoginTarget) + { + SEL action = NSSelectorFromString(runAfterLoginMethod); + [runAfterLoginTarget performSelector:action withObject:nil]; + } + } +} + +// used for stage 1 of authentication :: check inbox to see if we're authenticated. +- (void) apiUnreadMessageCountResponse:(id)sender +{ + + NSLog(@"RedditAPI :: apiUnreadMessageCountResponse"); + if (runAfterInboxCheckTarget) + { + SEL action = NSSelectorFromString(runAfterInboxCheckMethod); + [runAfterInboxCheckTarget performSelector:action withObject:nil]; + } +} + +- (void) authenticateStage2:(id)sender +{ + NSLog(@"authenticate stage 2 in()"); + // the inbox check says that we're still unauthenticated + if (!authenticated) + { + [self loginUser:[prefs objectForKey:@"username"] withPassword:[prefs objectForKey:@"password"] callBackTarget:self]; + } + else + { + // assume we are logged in, so time to run callbacks: + if (runAfterLoginTarget) + { + SEL action = NSSelectorFromString(runAfterLoginMethod); + [runAfterLoginTarget performSelector:action withObject:nil]; + } + } +} + +// used for stage2 of authentication (when username and password response returns) +- (void) apiLoginResponse:(id)sender +{ + NSLog(@"RedditAPI :: apiLoginResponse"); + if (runAfterLoginTarget) + { + SEL action = NSSelectorFromString(runAfterLoginMethod); + [runAfterLoginTarget performSelector:action withObject:nil]; + } +} + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response +{ +// NSLog(@"RedditAPI::connection:didReceiveResponse in()"); + NSString *connectionKey = [NSString stringWithFormat: @"%d", ((intptr_t) connection)]; +// NSLog(connectionKey); + NSMutableDictionary * dl = [connections objectForKey:connectionKey]; + NSMutableData *data = [[NSMutableData alloc] init]; +// NSMutableData * data = [NSMutableData dataWithLength:0]; + [dl setObject:data forKey:@"data"]; +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data +{ +// NSLog(@"RedditAPI::connection:didReceiveData in()"); + NSString *connectionKey = [NSString stringWithFormat: @"%d", ((intptr_t) connection)]; + NSMutableDictionary * dl = [connections objectForKey:connectionKey]; + NSMutableData * oldData = (NSMutableData *) [dl objectForKey:@"data"]; + [oldData appendData:data]; +// NSLog(@"Data Length : %d", [oldData length]); +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error +{ + NSLog(@"RedditAPI::connection:DidFailWithError in()"); + NSString *connectionKey = [NSString stringWithFormat: @"%d", ((intptr_t) connection)]; + NSMutableDictionary * dl = [connections objectForKey:connectionKey]; + // perform necessary callbacks with the data. + if ([dl objectForKey:@"failedNotifyAction"] && [dl objectForKey:@"afterCompleteTarget"]) + { + SEL action = NSSelectorFromString([dl valueForKey:@"failedNotifyAction"]); + [[dl objectForKey:@"afterCompleteTarget"] performSelector:action]; + } +} + + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection +{ + [(AlienBlueAppDelegate *) [[UIApplication sharedApplication] delegate] hideConnectionErrorImage]; + NSLog(@"RedditAPI::connection:DidFinishLoading in()"); + NSString *connectionKey = [NSString stringWithFormat: @"%d", ((intptr_t) connection)]; + NSMutableDictionary * dl = [connections objectForKey:connectionKey]; + + // perform necessary callbacks with the data. + if ([dl objectForKey:@"afterCompleteAction"] && [dl objectForKey:@"afterCompleteTarget"]) + { + SEL action = NSSelectorFromString([dl valueForKey:@"afterCompleteAction"]); + [[dl objectForKey:@"afterCompleteTarget"] performSelector:action withObject:[dl objectForKey:@"data"]]; + } + [connections removeObjectForKey:connectionKey]; + [dl release]; +} + + + +- (NSURLRequest *)connection:(NSURLConnection *)connection + willSendRequest:(NSURLRequest *)request + redirectResponse:(NSURLResponse *)redirectResponse +{ +// NSLog(@"RedditAPI :: connection request"); + return request; +} + +- (IBAction)testFunction:(id)sender +{ + NSLog(@"testFunction in()"); + NSData * data = (NSData *) sender; + NSLog(@"Data Length : %d", [data length]); +} + +- (void) testReachability +{ + Reachability * r = [Reachability reachabilityWithHostName:server]; + + NetworkStatus internetStatus = [r currentReachabilityStatus]; + + if ((internetStatus != ReachableViaWiFi) && (internetStatus != ReachableViaWWAN)) + { + [(AlienBlueAppDelegate *) [[UIApplication sharedApplication] delegate] showConnectionErrorImage]; +// [self connectionFailedDialog:nil]; + } +} + + +- (IBAction)connectionFailedDialog:(id)sender +{ + NSLog(@"connection failed dialog in()"); + loadingPosts = NO; + loadingMessages = NO; + [hideQueue removeAllObjects]; + +// [[[UIApplication sharedApplication] delegate] showConnectionErrorImage]; + + // release connections so that we don't keep bugging the user if there are multiple + // connections. + [connections removeAllObjects]; +} + +- (void) doPostToURL:(NSString *) urlstring + withParams:(NSString *) params + callBackTarget:(id) target + callBackMethod:(NSString *) method + failedMethod:(NSString *) method_failed +{ + NSData *postData = [params dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]; + NSString *postLength = [NSString stringWithFormat:@"%d", [postData length]]; + NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease]; + [request setURL:[NSURL URLWithString:urlstring]]; + if (cookie && [cookie length] > 0) + { + NSDictionary *properties = [NSDictionary dictionaryWithObjectsAndKeys: + cookieDomain, NSHTTPCookieDomain, + @"/", NSHTTPCookiePath, // IMPORTANT! + @"reddit_session", NSHTTPCookieName, + cookie, NSHTTPCookieValue, + nil]; + NSHTTPCookie *http_cookie = [NSHTTPCookie cookieWithProperties:properties]; + NSArray* cookies = [NSArray arrayWithObjects: http_cookie, nil]; + NSDictionary * headers = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies]; + [request setAllHTTPHeaderFields:headers]; + } + + [request setHTTPMethod:@"POST"]; + [request setValue:postLength forHTTPHeaderField:@"Content-Length"]; + [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; + [request setHTTPBody:postData]; + +// NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; + NSURLConnection * connection = [NSURLConnection connectionWithRequest:request delegate:self]; + NSString *connectionKey = [NSString stringWithFormat: @"%d", ((intptr_t) connection)]; + NSMutableDictionary *dl = [[NSMutableDictionary alloc] init]; + [dl setValue:connectionKey forKey:@"connectionKey"]; + [dl setValue:target forKey:@"afterCompleteTarget"]; + [dl setValue:method forKey:@"afterCompleteAction"]; + [dl setValue:method_failed forKey:@"failedNotifyAction"]; + [connections setValue:dl forKey:connectionKey]; +// [connection release]; + [urlstring release]; +} + +- (void) doGetURL:(NSString *) urlstring + callBackTarget:(id) target + callBackMethod:(NSString *) method + failedMethod:(NSString *) method_failed +{ + + NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease]; + [request setURL:[NSURL URLWithString:urlstring]]; + NSLog(@"-- get URL with cookie : [%@]", [self cookie]); + if (cookie && [cookie length] > 0) + { + NSDictionary *properties = [NSDictionary dictionaryWithObjectsAndKeys: + cookieDomain, NSHTTPCookieDomain, + @"/", NSHTTPCookiePath, // IMPORTANT! + @"reddit_session", NSHTTPCookieName, + cookie, NSHTTPCookieValue, +// [cookie stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding], NSHTTPCookieValue, + nil]; + NSHTTPCookie *http_cookie = [NSHTTPCookie cookieWithProperties:properties]; + NSArray* cookies = [NSArray arrayWithObjects: http_cookie, nil]; + NSDictionary * headers = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies]; + [request setAllHTTPHeaderFields:headers]; + } + NSURLConnection * connection = [NSURLConnection connectionWithRequest:request delegate:self]; +// NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; + NSString *connectionKey = [NSString stringWithFormat: @"%d", ((intptr_t) connection)]; + NSMutableDictionary *dl = [[NSMutableDictionary alloc] init]; + + [dl setValue:connectionKey forKey:@"connectionKey"]; + if (target && method) + { + [dl setValue:target forKey:@"afterCompleteTarget"]; + [dl setValue:method forKey:@"afterCompleteAction"]; + } + [dl setValue:method_failed forKey:@"failedNotifyAction"]; + [connections setValue:dl forKey:connectionKey]; +// [connection release]; + [urlstring release]; +} + + +- (void) loginUser:(NSString *) username withPassword:(NSString *) password callBackTarget:(id) target +{ + loginResultCallBackTarget = target; + NSLog(@"login in (%@ : %@)", username, password); + NSString * login_url = [[NSString alloc] initWithFormat:@"%@/api/login/%@",server, username]; + NSString * params = [[NSString alloc] initWithFormat:@"api_type=json&user=%@&passwd=%@",username, password]; + NSLog(params); + + [self doPostToURL:login_url withParams:params callBackTarget:self callBackMethod:@"loginUserResponse:" failedMethod:@"connectionFailedDialog:"]; +} + +- (void) unhideResponseReceived:(id) sender +{ + NSLog(@"-- unhide response received --"); + NSData * data = (NSData *) sender; + NSString * responseString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; + NSLog(responseString); + [data release]; +} + +- (void) hideResponseReceived:(id) sender +{ + NSLog(@"-- hide response received --"); + NSData * data = (NSData *) sender; + NSString * responseString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; + NSLog(responseString); + [data release]; +} + +- (void) saveResponseReceived:(id) sender +{ + NSLog(@"-- save response received --"); + NSData * data = (NSData *) sender; + NSString * responseString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; + NSLog(responseString); + [data release]; +} + +- (void) unsaveResponseReceived:(id) sender +{ + NSLog(@"-- unsave response received --"); + NSData * data = (NSData *) sender; + NSString * responseString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; + NSLog(responseString); + [data release]; +} + + + +- (void) unsavePostWithID: (NSString *) postID +{ + NSLog(@"-- unsaving post with id (%@)", postID); + NSString * unsave_url = [[NSString alloc] initWithFormat:@"%@/api/unsave",server]; + NSString * params = [[NSString alloc] initWithFormat:@"api_type=json&executed=unsaved&id=%@&uh=%@",postID, modhash]; + NSLog(params); + [self doPostToURL:unsave_url withParams:params callBackTarget:self callBackMethod:@"unsaveResponseReceived:" failedMethod:@"connectionFailedDialog:"]; +} + + +- (void) savePostWithID: (NSString *) postID +{ + NSLog(@"-- saving post with id (%@)", postID); + NSString * save_url = [[NSString alloc] initWithFormat:@"%@/api/save",server]; + NSString * params = [[NSString alloc] initWithFormat:@"api_type=json&executed=saved&id=%@&uh=%@",postID, modhash]; + NSLog(params); + [self doPostToURL:save_url withParams:params callBackTarget:self callBackMethod:@"saveResponseReceived:" failedMethod:@"connectionFailedDialog:"]; +} + +- (void) unhidePostWithID: (NSString *) postID +{ + NSLog(@"-- un-hiding post with id (%@)", postID); + NSString * hide_url = [[NSString alloc] initWithFormat:@"%@/api/unhide",server]; + NSString * params = [[NSString alloc] initWithFormat:@"api_type=json&executed=unhidden&id=%@&uh=%@",postID, modhash]; + NSLog(params); + [self doPostToURL:hide_url withParams:params callBackTarget:self callBackMethod:@"unhideResponseReceived:" failedMethod:@"connectionFailedDialog:"]; +} + +- (void) hidePostWithID: (NSString *) postID +{ + NSLog(@"-- hiding post with id (%@)", postID); + NSString * hide_url = [[NSString alloc] initWithFormat:@"%@/api/hide",server]; + NSString * params = [[NSString alloc] initWithFormat:@"api_type=json&executed=hidden&id=%@&uh=%@",postID, modhash]; + NSLog(params); + [self doPostToURL:hide_url withParams:params callBackTarget:self callBackMethod:@"hideResponseReceived:" failedMethod:@"connectionFailedDialog:"]; +} + +- (void) voteResponseReceived:(id) sender +{ + NSLog(@"-- vote response received --"); + NSData * data = (NSData *) sender; + NSString * responseString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; + NSLog(responseString); + [data release]; +} + +- (void) submitVote: (NSMutableDictionary *) item +{ + NSString * itemID = [item valueForKey:@"name"]; + int voteDirection = [[item valueForKey:@"voteDirection"] intValue]; + NSLog(@"-- submitting vote (%d) for item with id (%@)", voteDirection, itemID); + NSString * hide_url = [[NSString alloc] initWithFormat:@"%@/api/vote",server]; + NSString * params = [[NSString alloc] initWithFormat:@"api_type=json&dir=%d&id=%@&uh=%@",voteDirection,itemID, modhash]; + NSLog(params); + [self doPostToURL:hide_url withParams:params callBackTarget:self callBackMethod:@"voteResponseReceived:" failedMethod:@"connectionFailedDialog:"]; +} + +- (void) replyResponseReceived:(id) sender +{ + NSLog(@"-- reply/change response received --"); + SBJSON *parser = [[SBJSON alloc] init]; + NSData * data = (NSData *) sender; + NSString * responseString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; + NSDictionary * response = [[parser objectWithString:responseString error:nil] objectForKey:@"json"]; + NSString * newID = [[[[[response objectForKey:@"data"] objectForKey:@"things"] objectAtIndex:0] objectForKey:@"data"] valueForKey:@"id"]; + NSLog(@"newly created id: %@", newID); + +// NSLog(@"----------------"); +// NSLog(responseString); +// NSLog(@"----------------"); + if (replyResultCallBackTarget) + { + SEL action = NSSelectorFromString(@"apiReplyResponse:"); + [replyResultCallBackTarget performSelector:action withObject:newID]; + } + +// NSLog(responseString); + [responseString release]; +// [response release]; + [data release]; + [parser release]; +} + +- (void) submitChangeReply: (NSMutableDictionary *) item withCallBackTarget:(id) target; +{ + replyResultCallBackTarget = target; + NSString * itemID = [item valueForKey:@"name"]; + NSString * replyText = [item valueForKey:@"replyText"]; + NSLog(@"-- changing reply (%@) for item with id (%@)", replyText, itemID); + NSString * reply_url = [[NSString alloc] initWithFormat:@"%@/api/editusertext",server]; + NSString * params = [[NSString alloc] initWithFormat:@"api_type=json&text=%@&thing_id=%@&uh=%@", replyText, itemID, modhash]; + NSLog(reply_url); + NSLog(params); + [self doPostToURL:reply_url withParams:params callBackTarget:self callBackMethod:@"replyResponseReceived:" failedMethod:@"connectionFailedDialog:"]; +} + + +- (void) submitReply: (NSMutableDictionary *) item withCallBackTarget:(id) target; +{ + replyResultCallBackTarget = target; + NSString * itemID = [item valueForKey:@"name"]; + NSString * replyText = [item valueForKey:@"replyText"]; + NSLog(@"-- submitting reply (%@) for item with id (%@)", replyText, itemID); + NSString * reply_url = [[NSString alloc] initWithFormat:@"%@/api/comment",server]; + NSString * params = [[NSString alloc] initWithFormat:@"api_type=json&text=%@&thing_id=%@&uh=%@", replyText, itemID, modhash]; + NSLog(reply_url); + NSLog(params); + [self doPostToURL:reply_url withParams:params callBackTarget:self callBackMethod:@"replyResponseReceived:" failedMethod:@"connectionFailedDialog:"]; +} + + +- (void) loginUserResponse:(id) sender +{ +// modhash = @"4wrjzavo86d94003784ca676bbd9f775ec5a01deb199aca959"; + + + NSLog(@"loginUserResponse in()"); + NSData * data = (NSData *) sender; + SBJSON *parser = [[SBJSON alloc] init]; + NSString * responseString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; + + NSLog(responseString); + NSDictionary *response = [[parser objectWithString:responseString error:nil] objectForKey:@"json"]; + if ([response objectForKey:@"data"]) + { + NSDictionary * loginResult = [response objectForKey:@"data"]; + modhash = (NSString *) [[loginResult valueForKey:@"modhash"] copy]; + cookie = (NSString *) [[loginResult valueForKey:@"cookie"] copy]; + authenticated = YES; + + [prefs setObject:modhash forKey:@"modhash"]; + [prefs setObject:cookie forKey:@"cookie"]; + [prefs synchronize]; + authenticatedUser = [[prefs objectForKey:@"username"] copy]; + NSLog(@"modhash : %@", modhash); + NSLog(@"cookie : %@", cookie); + + if (loginResultCallBackTarget) + { + SEL action = NSSelectorFromString(@"apiLoginResponse:"); + [loginResultCallBackTarget performSelector:action withObject:modhash]; + } +// [loginResult release]; + } + else + { + modhash = @""; + cookie = @""; + [prefs setObject:modhash forKey:@"modhash"]; + [prefs setObject:cookie forKey:@"cookie"]; + [prefs synchronize]; + + NSLog(@"login failed"); + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Login Failed" message:@"Please check your username and password in the Settings panel." delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:nil]; + [alert show]; + [alert release]; + + } +// [response release]; + [responseString release]; + [data release]; + [parser release]; +} + +- (void) fetchCommentsResponse:(id) sender +{ + NSLog(@"fetchCommentsResponse in()"); + NSData * data = (NSData *) sender; + SBJSON *parser = [[SBJSON alloc] init]; + NSString * responseString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; + + +// NSLog(@"-------------"); +// NSLog(responseString); +// NSLog(@"-------------"); + id response = [parser objectWithString:responseString error:nil]; + if (commentFetchResultCallBackTarget) + { + SEL action = NSSelectorFromString(@"apiCommentsResponse:"); + [commentFetchResultCallBackTarget performSelector:action withObject:response]; + } + [responseString release]; + [data release]; + [parser release]; +} + +- (void) fetchCommentsForPostID:(NSString *) postID callBackTarget:(id) target +{ + NSLog(@"fetchCommentsForPostID in()"); + NSString * fetch_comments_url = [[NSString alloc] initWithFormat:@"%@/comments/%@/.json?sort=top", server, postID]; +// NSString * fetch_comments_url = [[NSString alloc] initWithFormat:@"http://10.1.1.2:7777/funniest3.json"]; +// NSString * fetch_comments_url = [[NSString alloc] initWithFormat:@"file:///Volumes/media/Dev/AlienBlue/AlienBlue/funniest3.json"]; + + NSLog(fetch_comments_url); + + commentFetchResultCallBackTarget = target; + + [self doGetURL:fetch_comments_url callBackTarget:self callBackMethod:@"fetchCommentsResponse:" failedMethod:@"connectionFailedDialog:"]; +} + +- (void) fetchPostResponse:(id) sender +{ + NSLog(@"fetchPostResponse in()"); + NSData * data = (NSData *) sender; + SBJSON *parser = [[SBJSON alloc] init]; + NSString * responseString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; + +// NSLog(responseString); + NSMutableDictionary *response = [parser objectWithString:responseString error:nil]; + [self setLoadingPosts:NO]; + if (postFetchResultCallBackTarget) + { + SEL action = NSSelectorFromString(@"apiPostsResponse:"); + [postFetchResultCallBackTarget performSelector:action withObject:response]; +// [postFetchResultCallBackTarget performSelectorOnMainThread:action withObject:response waitUntilDone:NO]; + } + [data release]; + [responseString release]; + [parser release]; +} + +- (void) fetchPostsForSubreddit:(NSString *)subreddit afterPostID:(NSString *) postID callBackTarget:(id) target +{ + NSLog(@"fetchPostsWithCount in() for subreddit: %@ after Post ID: %@", subreddit, postID); + [self setLoadingPosts:YES]; + NSString * fetch_url; + NSString * after_post_param; + + // we fetch a little extra if we are in the process of hiding posts + // otherwise, when we filter out items that are currently in the hideQueue + // the user will end up seeing only a few posts. + // eg. retrieving 25 while still hiding 20 would otherwise show only 5 posts. + int fetch_count = 25 + [self hideQueueLength]; + + if (postID && [postID length] > 0) + after_post_param = [NSString stringWithFormat:@"?limit=%d&after=%@", fetch_count, postID]; + else + after_post_param = [NSString stringWithFormat:@"?limit=%d", fetch_count]; + + if ([subreddit length] > 0) + fetch_url = [[NSString alloc] initWithFormat:@"%@%@.json%@", server,subreddit, after_post_param]; + else + fetch_url = [[NSString alloc] initWithFormat:@"%@/.json%@",server, after_post_param]; + + +// fetch_url = [[NSString alloc] initWithFormat:@"file:///Volumes/media/Dev/AlienBlue/AlienBlue/home.json"]; + + NSLog(fetch_url); + + postFetchResultCallBackTarget = target; + + [self doGetURL:fetch_url callBackTarget:self callBackMethod:@"fetchPostResponse:" failedMethod:@"connectionFailedDialog:"]; + +} + +- (void) subscribedRedditsResponse:(id) sender +{ + NSLog(@"subscribedRedditsResponse in()"); + NSData * data = (NSData *) sender; + SBJSON *parser = [[SBJSON alloc] init]; + NSString * responseString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; + +// NSLog(responseString); + NSMutableDictionary *response = [parser objectWithString:responseString error:nil]; + if (subredditListCallBackTarget) + { + SEL action = NSSelectorFromString(@"apiSubredditsResponse:"); + [subredditListCallBackTarget performSelector:action withObject:response]; + // [postFetchResultCallBackTarget performSelectorOnMainThread:action withObject:response waitUntilDone:NO]; + } + [data release]; + [responseString release]; + [parser release]; +} + + + +- (void) fetchSubscribedRedditsWithCallBackTarget:(id) target +{ + NSLog(@"fetchSubscribedRedditsWithCallBackTarget in()"); + + NSString * subscribed_reddit_list_url = [[NSString alloc] initWithFormat:@"%@/reddits/mine/.json",server]; + NSLog(subscribed_reddit_list_url); + + subredditListCallBackTarget = target; + + [self doGetURL:subscribed_reddit_list_url callBackTarget:self callBackMethod:@"subscribedRedditsResponse:" failedMethod:@"connectionFailedDialog:"]; +} + + + + + + + +- (void) inboxFetchResponse:(id) sender +{ + NSLog(@"inboxFetchResponse in()"); + [self setLoadingMessages:NO]; + NSData * data = (NSData *) sender; + SBJSON *parser = [[SBJSON alloc] init]; + NSString * responseString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; + + NSMutableDictionary *response = [parser objectWithString:responseString error:nil]; + if (inboxCallBackTarget) + { + SEL action = NSSelectorFromString(@"apiInboxResponse:"); + [inboxCallBackTarget performSelector:action withObject:response]; + } + + // the mark=false flag does not work when using .json so we need to make an extra call + // to /message/unread even though we will not need the response. A call to this URL will + // mark all unread messages as "read". + if([[prefs valueForKey:@"auto_mark_as_read"] boolValue] && unreadMessageCount > 0) + { + NSLog(@"-- marking unread messages as read"); + + [self setUnreadMessageCount:0]; + [(AlienBlueAppDelegate *) [[UIApplication sharedApplication] delegate] refreshUnreadMailBadge]; + + NSString * fetch_url = [[NSString alloc] initWithFormat:@"%@/message/unread/", server]; +// NSString * fetch_url = [[NSString alloc] initWithFormat:@"%@/message/inbox/", server]; + [self doGetURL:fetch_url callBackTarget:nil callBackMethod:nil failedMethod:@"connectionFailedDialog:"]; + } + + [data release]; + [responseString release]; + [parser release]; + +} + + + +- (void) fetchMessageInboxAfterMessageID:(NSString *) messageID withCallBackTarget:(id) target +{ + NSLog(@"fetchMessageInboxWithCallBackTarget in()"); + + [self setLoadingMessages:YES]; + int fetch_count = 25; + NSString * fetch_url; + NSString * after_post_param = @""; + if ([messageID length] > 0) + after_post_param = [[NSString alloc] initWithFormat:@"&limit=%d&after=%@", fetch_count, messageID]; + + fetch_url = [[NSString alloc] initWithFormat:@"%@/message/inbox/.json?mark=false%@", server, after_post_param]; + NSLog(fetch_url); + + inboxCallBackTarget = target; + + [self doGetURL:fetch_url callBackTarget:self callBackMethod:@"inboxFetchResponse:" failedMethod:@"connectionFailedDialog:"]; +} + + +- (void) unreadMessageCountFetchResponse:(id) sender +{ + NSLog(@"unreadMessageCountFetchResponse in()"); + NSData * data = (NSData *) sender; + SBJSON *parser = [[SBJSON alloc] init]; + NSString * responseString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; +// NSLog(responseString); + if ([responseString length] < 8) + { + NSLog(@"-- inbox access disallowed -- modhash/cookie may be expired"); + authenticated = NO; + // this flag will also instruct the authenticate() method to hit the login API + } + else + { + NSLog(@"-- inbox access successful"); + authenticated = YES; + } + + NSMutableDictionary *response = [parser objectWithString:responseString error:nil]; + if (response) + { + NSArray * messages = [[response objectForKey:@"data"] objectForKey:@"children"]; + if (messages) + unreadMessageCount = [messages count]; +// [messages release]; + } + if (unreadMessageCountCallBackTarget) + { + SEL action = NSSelectorFromString(@"apiUnreadMessageCountResponse:"); + [unreadMessageCountCallBackTarget performSelector:action withObject:response]; + } + [responseString release]; + [parser release]; + [data release]; +} + +- (void) fetchUnreadMessageCount:(id) target +{ + NSLog(@"fetchUnreadMessageCount in()"); + + NSString * fetch_url; + + + fetch_url = [[NSString alloc] initWithFormat:@"%@/message/unread/.json?mark=false",server]; + NSLog(fetch_url); + + unreadMessageCountCallBackTarget = target; + + [self doGetURL:fetch_url callBackTarget:self callBackMethod:@"unreadMessageCountFetchResponse:" failedMethod:@"connectionFailedDialog:"]; + +} + + + ++ (NSString *) useLowResImgurVersion:(NSString *) link +{ + if([link rangeOfString:@"imgur" options:NSCaseInsensitiveSearch].location != NSNotFound) + { + NSMutableString * nlink = [NSMutableString stringWithString:link]; + [nlink replaceOccurrencesOfString:@".jpg" withString:@"l.jpg" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [nlink length])]; + [nlink replaceOccurrencesOfString:@".jpeg" withString:@"l.jpeg" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [nlink length])]; + [nlink replaceOccurrencesOfString:@".png" withString:@"l.png" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [nlink length])]; + return nlink; + } + else + return link; +} + + +// this method allows drawing imgur links inline if the link isn't pointing directly +// to the image file. ++ (NSString *) fixImgurLink:(NSString *) link +{ + if( + [link rangeOfString:@".gif" options:NSCaseInsensitiveSearch].location == NSNotFound && + [link rangeOfString:@".png" options:NSCaseInsensitiveSearch].location == NSNotFound && + [link rangeOfString:@".jpg" options:NSCaseInsensitiveSearch].location == NSNotFound && + [link rangeOfString:@".jpeg" options:NSCaseInsensitiveSearch].location == NSNotFound && + [link rangeOfString:@"imgur" options:NSCaseInsensitiveSearch].location != NSNotFound && + [[NSUserDefaults standardUserDefaults] boolForKey:@"use_direct_imgur_link"] + ) + { + // if there is no extension on an imgur link we add one here. Imgur doesn't + // care about the type of extension, as long as one exists it will return the image. + NSString * nlink = [link stringByAppendingString:@".jpg"]; + return nlink; + } + + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"use_lowres_imgur"]) + link = [RedditAPI useLowResImgurVersion:link]; + + return link; +} + ++ (BOOL) isImageLink:(NSString *) link +{ + // we need to treat .gifs as non-images as they will not work when shown inline + if([link rangeOfString:@".gif" options:NSCaseInsensitiveSearch].location != NSNotFound) + { + return NO; + } + + + if( + [link rangeOfString:@".png" options:NSCaseInsensitiveSearch].location != NSNotFound || + [link rangeOfString:@".jpg" options:NSCaseInsensitiveSearch].location != NSNotFound || + [link rangeOfString:@".jpeg" options:NSCaseInsensitiveSearch].location != NSNotFound || + ([link rangeOfString:@"imgur" options:NSCaseInsensitiveSearch].location != NSNotFound + && [[NSUserDefaults standardUserDefaults] boolForKey:@"use_direct_imgur_link"]) + ) + return true; + else + return false; +} + ++ (BOOL) isVideoLink:(NSString *) link +{ + if( + [link rangeOfString:@"youtube" options:NSCaseInsensitiveSearch].location != NSNotFound + ) + return true; + else + return false; +} + ++ (BOOL) isSelfLink:(NSString *) link +{ + if( + [link rangeOfString:@"self." options:NSCaseInsensitiveSearch].location != NSNotFound || + [link rangeOfString:@"reddit.com/comments/" options:NSCaseInsensitiveSearch].location != NSNotFound + ) + return true; + else + return false; +} + + ++ (NSString *) getLinkType:(NSString *) url +{ + NSString * linkType; + if ([RedditAPI isImageLink:url]) + linkType = @"image"; + else if ([RedditAPI isVideoLink:url]) + linkType = @"video"; + else if ([RedditAPI isSelfLink:url]) + linkType = @"self"; + else + linkType = @"article"; + return linkType; +} + + + +@end \ No newline at end of file diff --git a/Classes/Resources.h b/Classes/Resources.h new file mode 100755 index 0000000..9b73540 --- /dev/null +++ b/Classes/Resources.h @@ -0,0 +1,37 @@ +// +// Resources.h +// AlienBlue +// +// Created by Jason Morrissey on 5/05/10. +// Copyright 2010 The Design Shed. All rights reserved. +// + +#import +#import "RedditAPI.h" + +@interface Resources : NSObject { + +} + ++ (UIImage *) barImage; ++ (UIImage *) loadingImage; + ++ (UIColor *) cOrange; ++ (UIColor *) cGreen; ++ (UIColor *) cNormal; ++ (UIColor *) cBlue; ++ (UIColor *) cTitleColor; + ++ (UIFont *) mainFont; ++ (UIFont *) secondaryFont; ++ (UIFont *) tertiaryFont; + ++ (UIImage *) videoIcon; ++ (UIImage *) articleIcon; ++ (UIImage *) imageIcon; + ++ (UIImage *) rightArrowDimImage; + ++ (void) processPost:(NSMutableDictionary *) post; + +@end diff --git a/Classes/Resources.m b/Classes/Resources.m new file mode 100755 index 0000000..37f7a01 --- /dev/null +++ b/Classes/Resources.m @@ -0,0 +1,205 @@ +// +// Resources.m +// AlienBlue +// +// Created by Jason Morrissey on 5/05/10. +// Copyright 2010 The Design Shed. All rights reserved. +// + +#import "Resources.h" +static UIImage *barImage; +static UIImage *barImageDark; + +static UIColor * cTitleDark; +static UIColor * cTitleNormal; + +static UIColor * cOrange; +static UIColor * cGreen; +static UIColor * cNormal; +static UIColor * cNormalDark; +static UIColor * cBlue; + +static UIFont *mainFont; +static UIFont *secondaryFont; +static UIFont *tertiaryFont; + +static UIFont *mainFontLarge; +static UIFont *secondaryFontLarge; +static UIFont *tertiaryFontLarge; + +static UIFont *mainFontSmall; +static UIFont *secondaryFontSmall; +static UIFont *tertiaryFontSmall; + + +static UIImage * videoIcon; +static UIImage * articleIcon; +static UIImage * imageIcon; + +static UIImage * rightArrowDim; + +static UIImage *loadingImageIcon; + +static NSUserDefaults * prefs; + +#define MAIN_FONT_SIZE 16 +#define SECONDARY_FONT_SIZE 14 +#define TERTIARY_FONT_SIZE 12 + + +@implementation Resources + ++ (void)initialize { + if (self == [Resources class]) { + NSLog(@"Resources :: initialise in()"); + prefs = [NSUserDefaults standardUserDefaults]; + barImage = [[UIImage imageNamed:@"button-background.png"] retain]; + barImageDark = [[UIImage imageNamed:@"button-background-dark.png"] retain]; + loadingImageIcon = [[UIImage imageNamed:@"loading-icon.png"] retain]; + + mainFont = [UIFont systemFontOfSize:MAIN_FONT_SIZE]; + secondaryFont = [UIFont systemFontOfSize:SECONDARY_FONT_SIZE]; + tertiaryFont = [UIFont systemFontOfSize:TERTIARY_FONT_SIZE]; + + mainFontSmall = [UIFont systemFontOfSize:MAIN_FONT_SIZE - 2]; + secondaryFontSmall = [UIFont systemFontOfSize:SECONDARY_FONT_SIZE - 1]; + tertiaryFontSmall = [UIFont systemFontOfSize:TERTIARY_FONT_SIZE]; + + mainFontLarge = [UIFont systemFontOfSize:MAIN_FONT_SIZE + 2]; + secondaryFontLarge = [UIFont systemFontOfSize:SECONDARY_FONT_SIZE + 1]; + tertiaryFontLarge = [UIFont systemFontOfSize:TERTIARY_FONT_SIZE]; + + cOrange = [[UIColor colorWithRed:1.0 green:1.0 blue:0.1 alpha:1] retain]; + cGreen = [[UIColor colorWithRed:0.8 green:1.0 blue:0.4 alpha:1] retain]; + cNormal = [[UIColor colorWithRed:0.93 green:0.93 blue:0.93 alpha:1] retain]; + cNormalDark = [[UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1] retain]; + cTitleNormal = [[UIColor colorWithWhite:1 alpha:1] retain]; + cTitleDark = [[UIColor colorWithWhite:0.85 alpha:1] retain]; + cBlue = [[UIColor colorWithRed:0.6 green:0.6 blue:1 alpha:1] retain]; + + videoIcon = [[UIImage imageNamed:@"video-icon.png"] retain]; + articleIcon = [[UIImage imageNamed:@"article-icon.png"] retain]; + imageIcon = [[UIImage imageNamed:@"image-icon.png"] retain]; + + rightArrowDim = [[UIImage imageNamed:@"right-arrow-dim.png"] retain]; + + } +} + ++ (UIImage *) rightArrowDimImage +{ + return rightArrowDim; +} + + ++ (UIImage *) loadingImage +{ + return loadingImageIcon; +} + + ++ (UIImage *) videoIcon +{ + return videoIcon; +} + ++ (UIImage *) articleIcon +{ + return articleIcon; +} + ++ (UIImage *) imageIcon +{ + return imageIcon; +} + + + + + ++ (UIImage *) barImage +{ + if ([prefs boolForKey:@"night_mode"]) + return barImageDark; + else + return barImage; +} + ++ (UIColor *) cTitleColor +{ + if ([prefs boolForKey:@"night_mode"]) + return cTitleDark; + else + return cTitleNormal; + +} + ++ (UIColor *) cNormal +{ + if ([prefs boolForKey:@"night_mode"]) + return cNormalDark; + else + return cNormal; +} + ++ (UIColor *) cOrange { return cOrange; } ++ (UIColor *) cGreen { return cGreen; } ++ (UIColor *) cBlue { return cBlue; } + ++ (UIFont *) mainFont { + int tsize = [prefs integerForKey:@"textsize"]; + if (tsize == 0) + return mainFontSmall; + else if (tsize == 1) + return mainFont; + else + return mainFontLarge; +} + ++ (UIFont *) secondaryFont { + int tsize = [prefs integerForKey:@"textsize"]; + if (tsize == 0) + return secondaryFontSmall; + else if (tsize == 1) + return secondaryFont; + else + return secondaryFontLarge; +} + ++ (UIFont *) tertiaryFont { + int tsize = [prefs integerForKey:@"textsize"]; + if (tsize == 0) + return tertiaryFontSmall; + else if (tsize == 1) + return tertiaryFont; + else + return tertiaryFontLarge; +} + ++ (void) processPost:(NSMutableDictionary *) post +{ + int voteDirection = 0; + if(![[post objectForKey:@"likes"] isKindOfClass:[NSNull class]] && [[post valueForKey:@"likes"] boolValue]) + voteDirection = 1; + else if(![[post objectForKey:@"likes"] isKindOfClass:[NSNull class]] && ![[post valueForKey:@"likes"] boolValue]) + voteDirection = -1; + [post setValue:[NSNumber numberWithInt:voteDirection] forKey:@"voteDirection"]; + [post setValue:[[post valueForKey:@"title"] stringByReplacingOccurrencesOfString:@"&" withString:@"&"] forKey:@"title"]; + [post setValue:[[post valueForKey:@"title"] stringByReplacingOccurrencesOfString:@">" withString:@">"] forKey:@"title"]; + [post setValue:[[post valueForKey:@"title"] stringByReplacingOccurrencesOfString:@"<" withString:@"<"] forKey:@"title"]; + // calculate score (for some reason, the live reddit.com server doesn't compute the score + // for comments, so we do this manually: + int ups = [[post valueForKey:@"ups"] intValue]; + int downs = [[post valueForKey:@"downs"] intValue]; + [post setValue:[NSNumber numberWithInt:(ups - downs)] forKey:@"score"]; + + [post setValue:[RedditAPI getLinkType:[post valueForKey:@"url"]] forKey:@"type"]; + [post setValue:[RedditAPI fixImgurLink:[post valueForKey:@"url"]] forKey:@"url"]; + +} + + + + + +@end diff --git a/Classes/SettingsTableViewController.h b/Classes/SettingsTableViewController.h new file mode 100755 index 0000000..8789e53 --- /dev/null +++ b/Classes/SettingsTableViewController.h @@ -0,0 +1,33 @@ +// +// SettingsViewController.h +// Alien Blue +// +// Created by Jason Morrissey on 4/04/10. +// Copyright 2010 The Design Shed. All rights reserved. +// + +#import +#import "RedditAPI.h" + +@interface SettingsTableViewController : UITableViewController { + UITextField *usernameField; + UITextField *passwordField; + UITextField *newFilterField; + IBOutlet UILabel *modLabel; + IBOutlet UILabel *cookieLabel; + NSUserDefaults * prefs; + UIActivityIndicatorView * purchaseActivity; + UIScrollView * proSplashView; + UIScrollView * tipsView; + UIView * settingsView; + RedditAPI * redAPI; +} + +@property (nonatomic, retain) IBOutlet UILabel *modLabel; +@property (nonatomic, retain) IBOutlet UILabel *cookieLabel; + +- (IBAction)authorise:(id)sender; +- (void) showTipsSplash; +- (void) stopPurchaseActivityIndicator; +- (void) showProSplash; +@end diff --git a/Classes/SettingsTableViewController.m b/Classes/SettingsTableViewController.m new file mode 100755 index 0000000..5acbf53 --- /dev/null +++ b/Classes/SettingsTableViewController.m @@ -0,0 +1,987 @@ +// +// SettingsViewController.m +// Alien Blue +// +// Created by Jason Morrissey on 4/04/10. +// Copyright 2010 The Design Shed. All rights reserved. +// + +#import "SettingsTableViewController.h" +#import "AlienBlueAppDelegate.h" + + +static UIImage * proFeatureLabelImage; +static UIImage * upgradeNowLabel; +static UIImage * starImage; +static UIImage * deleteImage; + +#define SECTION_HEADER_HEIGHT 40 + +#define NUM_SETTING_SECTIONS 9 + +#define SECTION_REDDIT_ACCOUNT 0 +#define SECTION_MESSAGE_FETCH 1 +#define SECTION_VIEWING_MESSAGE 2 +#define SECTION_DISPLAY 3 +#define SECTION_FONTS 4 +#define SECTION_FILTER 5 +#define SECTION_ADVANCED 6 +#define SECTION_UPGRADE_PRO 7 +#define SECTION_CONTACT 8 +#define SECTION_HELP 9 +#define SECTION_ACKNOWLEDGEMENTS 10 + +#define NUM_ROWS_REDDIT_ACCOUNT 3 +#define NUM_ROWS_MESSAGE_FETCH 4 +#define NUM_ROWS_VIEWING_MESSAGE 1 +#define NUM_ROWS_DISPLAY 6 +#define NUM_ROWS_UPGRADE_PRO 2 +#define NUM_ROWS_ADVANCED 3 +#define NUM_ROWS_CONTACT 3 +#define NUM_ROWS_FONTS 3 +#define NUM_ROWS_HELP 2 +#define NUM_ROWS_ACKNOWLEDGEMENTS 4 + +@implementation SettingsTableViewController + +@synthesize modLabel; +@synthesize cookieLabel; + +- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex +{ + if(alertView.tag == 1 && buttonIndex == 1) + { + NSLog(@"launch Mail app in()"); + NSString *url = [NSString stringWithString: @"mailto:support@alienblue.org?subject=AlienBlue%20Fault"]; + [[UIApplication sharedApplication] openURL: [NSURL URLWithString: url]]; + + } + if(alertView.tag == 2 && buttonIndex == 1) + { + NSLog(@"launch safari in()"); + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"http://alienblue.org"]]; + } +} + +- (void) saveSettings +{ +// NSUserDefaults * prefs = [NSUserDefaults standardUserDefaults]; +// [prefs setObject:[username text] forKey:@"username"]; +// [prefs setObject:[password text] forKey:@"password"]; +// [prefs synchronize]; +} + +- (void) loadSettings +{ +// NSUserDefaults * prefs = [NSUserDefaults standardUserDefaults]; +// [username setText:[prefs objectForKey:@"username"]]; +// [password setText:[prefs objectForKey:@"password"]]; +// [modLabel setText:[prefs objectForKey:@"modhash"]]; +// [cookieLabel setText:[prefs objectForKey:@"cookie"]]; + +} + + +- (IBAction)apiLoginResponse:(id)sender { + NSLog(@"SettingsViewController :: apiLoginResponse()"); + NSString * modhash = (NSString *) sender; + NSLog(modhash); + + if ([modhash length] > 0) + { + [[self tableView] reloadData]; + + // reload the Posts in the other view (as the user likely has their own + // preferred subscribed reddits. + NavigationController * nc = (NavigationController *) [self parentViewController]; + [nc clearAndRefreshPosts]; + +// [self saveSettings]; +// +// UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Authorised" message:@"You have successfully logged into your Reddit account. Your subscribed reddits, saved posts and messages are now available." delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:nil]; +// [alert show]; +// [alert release]; + } +} + + +- (IBAction)authorise:(id)sender +{ + NSLog(@"-- authorise in"); + [prefs setObject:[usernameField text] forKey:@"username"]; + [prefs setObject:[passwordField text] forKey:@"password"]; + [prefs synchronize]; + redAPI = [(AlienBlueAppDelegate *) [[UIApplication sharedApplication] delegate] redditAPI]; + [redAPI loginUser:[prefs valueForKey:@"username"] withPassword:[prefs valueForKey:@"password"] callBackTarget:self]; +} + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + [textField resignFirstResponder]; + return NO; +} + +- (void)textFieldDidBeginEditing:(UITextField *)textField +{ + if (textField == newFilterField) + { + [textField setText:@""]; + } +} + +- (void)textFieldDidEndEditing:(UITextField *)textField +{ + if (textField == usernameField) + { + [prefs setValue:[textField text] forKey:@"username"]; + [prefs synchronize]; + NSLog(@"username changed"); + } + if (textField == passwordField) + { + [prefs setValue:[textField text] forKey:@"password"]; + [prefs synchronize]; + NSLog(@"password changed"); + } + if (textField == newFilterField) + { + if ([[textField text] length] < 3) + { + [textField setText:@"Add a filter..."]; + } + else + { + NSMutableArray * filterList = [NSMutableArray arrayWithArray:[prefs objectForKey:@"filterList"]]; + [filterList addObject:[newFilterField text]]; + [prefs setObject:filterList forKey:@"filterList"]; + [prefs synchronize]; + } + [[self tableView] reloadData]; + } +// [self saveSettings]; +} + +- (IBAction) removeFilterItem:(id)sender +{ + UIButton * b = (UIButton *) sender; + int removeIndex = [b tag]; + NSMutableArray * filterList = [NSMutableArray arrayWithArray:[prefs objectForKey:@"filterList"]]; + [filterList removeObjectAtIndex:removeIndex]; + [prefs setObject:filterList forKey:@"filterList"]; + [prefs synchronize]; + NSIndexPath * ip = [NSIndexPath indexPathForRow:removeIndex inSection:SECTION_FILTER]; + NSArray * arr = [NSArray arrayWithObject:ip]; + [[self tableView] deleteRowsAtIndexPaths:arr withRowAnimation:YES]; + [[self tableView] reloadData]; +} + + +/* + // The designated initializer. Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad. +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { + // Custom initialization + } + return self; +} +*/ + +/* +// Implement loadView to create a view hierarchy programmatically, without using a nib. +- (void)loadView { +} +*/ + + +- (void) drawTipsSplashImage +{ +// CGRect screenFrame = [UIScreen mainScreen].applicationFrame; + + tipsView = [[UIScrollView alloc] init]; + UIImageView * tipsImage = [[UIImageView alloc] initWithImage:[[UIImage imageNamed:@"tips-sheet.png"] retain]]; + [tipsView addSubview:tipsImage]; + + UIButton * backButton1 = [UIButton buttonWithType:UIButtonTypeRoundedRect]; + [backButton1 setImage:[UIImage imageNamed:@"orange-back-button.png"] forState:UIControlStateNormal]; + [backButton1 setFrame:CGRectMake(15, 22, 73, 25)]; + [backButton1 addTarget:self action:@selector(hideSplash:) forControlEvents:UIControlEventTouchUpInside]; + + UIButton * backButton2 = [UIButton buttonWithType:UIButtonTypeRoundedRect]; + [backButton2 addTarget:self action:@selector(hideSplash:) forControlEvents:UIControlEventTouchUpInside]; + [backButton2 setImage:[UIImage imageNamed:@"orange-back-button.png"] forState:UIControlStateNormal]; + [backButton2 setFrame:CGRectMake(15, 7290, 73, 25)]; + + + [tipsView addSubview:backButton1]; + [tipsView addSubview:backButton2]; + [tipsView setShowsVerticalScrollIndicator:YES]; +} + +- (void) drawProSplashImage +{ + // CGRect screenFrame = [UIScreen mainScreen].applicationFrame; + + proSplashView = [[UIScrollView alloc] init]; + UIImageView * proSplashImage = [[UIImageView alloc] initWithImage:[[UIImage imageNamed:@"why-go-pro-splash.png"] retain]]; + [proSplashView addSubview:proSplashImage]; + + UIButton * backButton1 = [UIButton buttonWithType:UIButtonTypeRoundedRect]; + [backButton1 setImage:[UIImage imageNamed:@"orange-back-button.png"] forState:UIControlStateNormal]; + [backButton1 setFrame:CGRectMake(15, 22, 73, 25)]; + [backButton1 addTarget:self action:@selector(hideSplash:) forControlEvents:UIControlEventTouchUpInside]; + + UIButton * backButton2 = [UIButton buttonWithType:UIButtonTypeRoundedRect]; + [backButton2 addTarget:self action:@selector(hideSplash:) forControlEvents:UIControlEventTouchUpInside]; + [backButton2 setImage:[UIImage imageNamed:@"orange-back-button.png"] forState:UIControlStateNormal]; + [backButton2 setFrame:CGRectMake(15, [proSplashImage bounds].size.height - 50, 73, 25)]; + + + [proSplashView addSubview:backButton1]; + [proSplashView addSubview:backButton2]; + [proSplashView setShowsVerticalScrollIndicator:FALSE]; +} + + +// Implement viewDidLoad to do additional setup after loading the view, typically from a nib. +- (void)viewDidLoad { + [super viewDidLoad]; + proFeatureLabelImage = [[UIImage imageNamed:@"pro-feature-label.png"] retain]; + upgradeNowLabel = [[UIImage imageNamed:@"upgrade-to-pro-label.png"] retain]; + starImage = [[UIImage imageNamed:@"star-selected-icon.png"] retain]; + deleteImage = [[UIImage imageNamed:@"delete-icon.png"] retain]; + prefs = [NSUserDefaults standardUserDefaults]; + + + + [[self tableView] setBackgroundColor:[UIColor clearColor]]; + [[self tableView] setSeparatorColor:[UIColor clearColor]]; + [[self tableView] setScrollsToTop:[[prefs valueForKey:@"allow_status_bar_scroll"] boolValue]]; + purchaseActivity = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; + [purchaseActivity setHidesWhenStopped:YES]; + [purchaseActivity stopAnimating]; + + settingsView = [[self view] retain]; + [self drawProSplashImage]; + [self drawTipsSplashImage]; +} + + +// this is called from the AppDelegate once the purchase is finished. +- (void) stopPurchaseActivityIndicator +{ + [purchaseActivity stopAnimating]; +} + + +/* +// Override to allow orientations other than the default portrait orientation. +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + // Return YES for supported orientations + return (interfaceOrientation == UIInterfaceOrientationPortrait); +} +*/ + +- (void)didReceiveMemoryWarning { + // Releases the view if it doesn't have a superview. + [super didReceiveMemoryWarning]; + + // Release any cached data, images, etc that aren't in use. +} + +- (void)viewDidUnload { + // Release any retained subviews of the main view. + // e.g. self.myOutlet = nil; +} + + +- (void)dealloc { + [super dealloc]; +} + +#pragma mark Table view methods + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return NUM_SETTING_SECTIONS; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + if ([self tableView:tableView titleForHeaderInSection:section] != nil) { + return SECTION_HEADER_HEIGHT; + } + else { + // If no section header title, no section header needed + return 0; + } +} + +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + + NSString *sectionTitle = [self tableView:tableView titleForHeaderInSection:section]; + if (sectionTitle == nil) { + return nil; + } + + // Create label with section title + UILabel *label = [[[UILabel alloc] init] autorelease]; + label.frame = CGRectMake(20, 6, 300, 30); + label.backgroundColor = [UIColor clearColor]; + label.textColor = [Resources cTitleColor]; +// label.textColor = [UIColor whiteColor]; +// label.textColor = [UIColor colorWithHue:(136.0/360.0) // Slightly bluish green +// saturation:1.0 +// brightness:0.60 +// alpha:1.0]; +// label.shadowColor = [UIColor whiteColor]; +// label.shadowOffset = CGSizeMake(0.0, 1.0); + label.font = [UIFont boldSystemFontOfSize:16]; + label.text = sectionTitle; + + // Create header view and add label as a subview + UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, SECTION_HEADER_HEIGHT)]; + [view autorelease]; + [view addSubview:label]; + + return view; +} + +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { + + switch (section) { + case SECTION_REDDIT_ACCOUNT: + return @"Reddit Account"; + break; + case SECTION_MESSAGE_FETCH: + return @"Fetch New Messages"; + break; + case SECTION_VIEWING_MESSAGE: + return @"When viewing messages..."; + break; + case SECTION_DISPLAY: + return @"Display Preferences"; + break; + case SECTION_FONTS: + return @"Text Size"; + break; + case SECTION_UPGRADE_PRO: + if(![MKStoreManager isProUpgraded]) + return @"Upgrade to PRO"; + else + return @"PRO Activated"; + break; + case SECTION_FILTER: + return @"Exclude posts that contain:"; + break; + case SECTION_ADVANCED: + return @"Advanced Settings"; + break; + case SECTION_CONTACT: + return @"Get In Touch"; + break; + case SECTION_HELP: + return @"User Guide"; + break; + case SECTION_ACKNOWLEDGEMENTS: + return @"Acknowledgements"; + break; + + default: + return nil; + break; + } +} + +- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section +{ + return nil; +} + +// Customize the number of rows in the table view. +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + + switch (section) { + case SECTION_REDDIT_ACCOUNT: + return NUM_ROWS_REDDIT_ACCOUNT; + break; + case SECTION_MESSAGE_FETCH: + return NUM_ROWS_MESSAGE_FETCH; + break; + case SECTION_VIEWING_MESSAGE: + return NUM_ROWS_VIEWING_MESSAGE; + break; + case SECTION_DISPLAY: + return NUM_ROWS_DISPLAY; + break; + case SECTION_FONTS: + return NUM_ROWS_FONTS; + break; + case SECTION_UPGRADE_PRO: + if(![MKStoreManager isProUpgraded]) + return NUM_ROWS_UPGRADE_PRO; + else + return NUM_ROWS_UPGRADE_PRO - 1; + break; + case SECTION_FILTER: + return [(NSMutableArray *) [prefs objectForKey:@"filterList"] count] + 1; + break; + case SECTION_ADVANCED: + return NUM_ROWS_ADVANCED; + break; + case SECTION_CONTACT: + return NUM_ROWS_CONTACT; + break; + case SECTION_HELP: + return NUM_ROWS_HELP; + break; + case SECTION_ACKNOWLEDGEMENTS: + return NUM_ROWS_ACKNOWLEDGEMENTS; + break; + default: + return 0; + break; + } + +} + +- (UILabel *) createSettingNameLabelForRowAtIndexPath:(NSIndexPath *)indexPath +{ + UILabel * settingLabel = [[UILabel alloc] initWithFrame:CGRectMake(22, 8, 300, 30)]; + [settingLabel setFont:[UIFont fontWithName:@"Helvetica" size:14]]; + [settingLabel setBackgroundColor:[UIColor clearColor]]; + [settingLabel setTextColor:[Resources cNormal]]; + NSString * label = @""; + + switch (indexPath.section) { + case SECTION_REDDIT_ACCOUNT: + if (indexPath.row == 0) + label = @"Username"; + else if (indexPath.row == 1) + label = @"Password"; + break; + case SECTION_MESSAGE_FETCH: + if (indexPath.row == 0) + label = @"Manually"; + else if (indexPath.row == 1) + label = @"Every 2 Minutes"; + else if (indexPath.row == 2) + label = @"Every 5 Minutes"; + else if (indexPath.row == 3) + label = @"Every 10 Minutes"; + break; + case SECTION_VIEWING_MESSAGE: + if (indexPath.row == 0) + label = @"Auto Mark As Read"; + break; + case SECTION_FONTS: + if (indexPath.row == 0) + label = @"Small"; + else if (indexPath.row == 1) + label = @"Medium"; + else if (indexPath.row == 2) + label = @"Large"; + break; + case SECTION_DISPLAY: + if (indexPath.row == 0) + label = @"Allow Rotation"; + else if (indexPath.row == 1) + label = @"Status-Bar Tap Scrolling"; +// else if (indexPath.row == 2) +// label = @"Show Quick-Scroll Buttons"; + else if (indexPath.row == 2) + label = @"Enable Tilt-Scrolling"; + else if (indexPath.row == 3) + label = @"Reverse Tilt-Scroll Direction"; + else if (indexPath.row == 4) + label = @"Show Thumbnails"; + else if (indexPath.row == 5) + label = @"Night Mode"; + break; + case SECTION_UPGRADE_PRO: + if (indexPath.row == 0) + { + if(![MKStoreManager isProUpgraded]) + label = @""; + else + label = @"Thank you for upgrading."; + [settingLabel setTextColor:[UIColor orangeColor]]; + } + else if (indexPath.row == 1) + { + label = @"PRO Features"; +// [settingLabel setNumberOfLines:5]; +// [settingLabel setFrame:CGRectMake(22, 8, 300, 90)]; +// [settingLabel setTextColor:[UIColor colorWithWhite:0.8 alpha:1]]; + } + break; + case SECTION_ADVANCED: + if (indexPath.row == 0) + label = @"Display Queue When Hiding Posts"; + else if (indexPath.row == 1) + label = @"Use Resized Imgur Images (Faster)"; + else if (indexPath.row == 2) + label = @"Deeplink Imgur For Inline Viewing"; + + break; + case SECTION_CONTACT: + if (indexPath.row == 0) + label = @"Report a Bug"; + else if (indexPath.row == 1) + label = @"Visit Online"; + break; + case SECTION_HELP: + if (indexPath.row == 0) + label = @" Power User Tips"; + else if (indexPath.row == 1) + label = @"Show Help Icons"; + break; + case SECTION_ACKNOWLEDGEMENTS: + if (indexPath.row == 0) + label = @"Reddit.com and the Reddit API"; + else if (indexPath.row == 1) + label = @"JSON Framework by Stig Brautaset"; + else if (indexPath.row == 2) + label = @"MKStoreKit by Mugunth Kumar"; + else if (indexPath.row == 3) + label = @"Readability by Arc90"; + case SECTION_FILTER: + if (indexPath.row < [(NSMutableArray *) [prefs objectForKey:@"filterList"] count]) + label = [(NSMutableArray *) [prefs objectForKey:@"filterList"] objectAtIndex:indexPath.row]; + break; + default: + break; + } + [settingLabel setText:label]; + return settingLabel; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (indexPath.section == SECTION_CONTACT && indexPath.row == 2) + return 110; + else + return 44; +} + +- (void) createInteractionForCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath +{ + + switch (indexPath.section) { + case SECTION_REDDIT_ACCOUNT: + if (indexPath.row == 0) + { + usernameField = [[UITextField alloc] initWithFrame:CGRectMake(130,12,170,24)]; + [usernameField setFont:[UIFont fontWithName:@"Helvetica" size:15]]; + [usernameField setTextAlignment:UITextAlignmentCenter]; + [usernameField setBackgroundColor:[UIColor colorWithWhite:1 alpha:0.8]]; + [usernameField setDelegate:self]; + [usernameField setReturnKeyType:UIReturnKeyDone]; + [usernameField setAutocapitalizationType:UITextAutocapitalizationTypeNone]; + [usernameField setAutocorrectionType:UITextAutocorrectionTypeNo]; + [usernameField setTag:0]; + [usernameField setText:[prefs objectForKey:@"username"]]; + [cell addSubview:usernameField]; + } + else if (indexPath.row == 1) + { + passwordField = [[UITextField alloc] initWithFrame:CGRectMake(130,12,170,24)]; + [passwordField setFont:[UIFont fontWithName:@"Helvetica" size:15]]; + [passwordField setTextAlignment:UITextAlignmentCenter]; + [passwordField setBackgroundColor:[UIColor colorWithWhite:1 alpha:0.8]]; + [passwordField setReturnKeyType:UIReturnKeyDone]; + [passwordField setAutocapitalizationType:UITextAutocapitalizationTypeNone]; + [passwordField setAutocorrectionType:UITextAutocorrectionTypeNo]; + [passwordField setDelegate:self]; + [passwordField setTag:1]; + [passwordField setSecureTextEntry:YES]; + [passwordField setText:[prefs objectForKey:@"password"]]; + [cell addSubview:passwordField]; + } + else if (indexPath.row == 2) + { + UIButton * authButton = [[UIButton buttonWithType:UIButtonTypeRoundedRect] retain]; + [authButton setTitle:@"Login" forState:UIControlStateNormal]; + [authButton setFrame:CGRectMake(130,12,170,24)]; + [authButton addTarget:self action:@selector(authorise:) forControlEvents:UIControlEventTouchUpInside]; + [authButton setTag:2]; + [cell addSubview:authButton]; + if ([redAPI authenticated]) + { + UIImage * tickImage = [[UIImage imageNamed:@"green-tick-icon.png"] retain]; + UIImageView * tickView = [[UIImageView alloc] initWithImage:tickImage]; + [tickView setFrame:CGRectMake(270, 14, 20, 20)]; + [cell insertSubview:tickView atIndex:99]; + } + + } + break; + case SECTION_MESSAGE_FETCH: + if (indexPath.row == [[prefs valueForKey:@"fetch_message_frequency"] intValue]) + [cell setAccessoryType:UITableViewCellAccessoryCheckmark]; + break; + case SECTION_FONTS: + if (indexPath.row == [[prefs valueForKey:@"textsize"] intValue]) + [cell setAccessoryType:UITableViewCellAccessoryCheckmark]; + break; + case SECTION_VIEWING_MESSAGE: + NSLog(@""); + if ([[prefs valueForKey:@"auto_mark_as_read"] boolValue]) + [cell setAccessoryType:UITableViewCellAccessoryCheckmark]; + break; + case SECTION_DISPLAY: + if ( (indexPath.row == 0 && [[prefs valueForKey:@"allow_rotation"] boolValue]) || + (indexPath.row == 1 && [[prefs valueForKey:@"allow_status_bar_scroll"] boolValue]) || +// (indexPath.row == 2 && [[prefs valueForKey:@"show_quick_scroll"] boolValue]) || + (indexPath.row == 2 && [[prefs valueForKey:@"allow_tilt_scroll"] boolValue]) || + (indexPath.row == 3 && [[prefs valueForKey:@"reverse_tilt_axis"] boolValue]) || + (indexPath.row == 4 && [[prefs valueForKey:@"show_thumbs"] boolValue]) || + (indexPath.row == 5 && [[prefs valueForKey:@"night_mode"] boolValue]) + ) + [cell setAccessoryType:UITableViewCellAccessoryCheckmark]; + + if (indexPath.row == 2 && ![MKStoreManager isProUpgraded]) + { + UIImageView * proLabelView = [[UIImageView alloc] initWithImage:proFeatureLabelImage]; + [proLabelView setFrame:CGRectMake(cell.bounds.size.width - 95, 19, 67, 10)]; + [cell addSubview:proLabelView]; + } + break; + case SECTION_UPGRADE_PRO: + if (indexPath.row == 0) + { + if([MKStoreManager isProUpgraded]) + [cell setAccessoryType:UITableViewCellAccessoryNone]; + else + [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator]; + + if ([[cell subviews] containsObject:purchaseActivity]) + { + [purchaseActivity removeFromSuperview]; + } + [purchaseActivity setFrame:CGRectMake([cell frame].size.width - 70, 10, 25, 25)]; + [cell addSubview:purchaseActivity]; + + if(![MKStoreManager isProUpgraded]) + { + UIImageView * upgradeNowLabelView = [[UIImageView alloc] initWithImage:upgradeNowLabel]; + [upgradeNowLabelView setFrame:CGRectMake(13, 10, 106, 26)]; + [cell addSubview:upgradeNowLabelView]; + } + } + break; + case SECTION_ADVANCED: + if ( (indexPath.row == 0 && [[prefs valueForKey:@"show_hide_queue"] boolValue]) || + (indexPath.row == 1 && [[prefs valueForKey:@"use_lowres_imgur"] boolValue]) || + (indexPath.row == 2 && [[prefs valueForKey:@"use_direct_imgur_link"] boolValue]) + ) + [cell setAccessoryType:UITableViewCellAccessoryCheckmark]; + break; + case SECTION_CONTACT: + [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator]; + if (indexPath.row == 2) + { + [cell setAccessoryType:UITableViewCellAccessoryNone]; + UILabel * descLabel = [[UILabel alloc] initWithFrame:CGRectMake(22, 10, 280, 80)]; + [descLabel setText:@"Alien Blue is an open source project. The source code is available from alienblue.org, and includes all PRO features. We encourage developers to use the framework without restriction for non-commercial applications."]; + [descLabel setFont:[UIFont systemFontOfSize:13]]; + [descLabel setBackgroundColor:[UIColor clearColor]]; + [descLabel setNumberOfLines:10]; + [descLabel setTextColor:[UIColor colorWithWhite:0.8 alpha:1]]; + [cell addSubview:descLabel]; + } + break; + case SECTION_HELP: + [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator]; + if (indexPath.row == 0) + { + UIImageView * starView = [[UIImageView alloc] initWithImage:starImage]; + [starView setFrame:CGRectMake(20, 7, 30, 30)]; + [cell addSubview:starView]; + } + else if (indexPath.row == 1) + { + if ([[prefs valueForKey:@"show_help_icon"] boolValue]) + [cell setAccessoryType:UITableViewCellAccessoryCheckmark]; + else + [cell setAccessoryType:UITableViewCellAccessoryNone]; + } + break; + case SECTION_FILTER: + if (indexPath.row == [(NSMutableArray *) [prefs objectForKey:@"filterList"] count]) + { + newFilterField = [[UITextField alloc] initWithFrame:CGRectMake(24,12,290,24)]; + [newFilterField setFont:[UIFont boldSystemFontOfSize:15]]; + [newFilterField setTextAlignment:UITextAlignmentLeft]; + [newFilterField setBackgroundColor:nil]; + [newFilterField setTextColor:[UIColor whiteColor]]; + [newFilterField setReturnKeyType:UIReturnKeyDone]; + [newFilterField setAutocapitalizationType:UITextAutocapitalizationTypeSentences]; + [newFilterField setAutocorrectionType:UITextAutocorrectionTypeNo]; + [newFilterField setDelegate:self]; + [newFilterField setTag:3]; + [newFilterField setText:@"Add a filter..."]; + [cell addSubview:newFilterField]; +// UIButton * newFilterButton = [[UIButton buttonWithType:UIButtonTypeContactAdd] retain]; +// [newFilterButton setFrame:CGRectMake(20,12,30,24)]; +// [cell addSubview:newFilterButton]; + } + else + { + UIButton * removeFilterButton = [[UIButton buttonWithType:UIButtonTypeCustom] retain]; +// [removeFilterButton setImage:deleteImage forState:UIControlStateNormal]; + [removeFilterButton setFrame:CGRectMake(cell.bounds.size.width - 46 + ,11,24,24)]; + [removeFilterButton setBackgroundColor:[UIColor clearColor]]; + [removeFilterButton setBackgroundImage:deleteImage forState:UIControlStateNormal]; + [removeFilterButton setTag:indexPath.row]; + [removeFilterButton addTarget:self action:@selector(removeFilterItem:) forControlEvents:UIControlEventTouchUpInside]; + [cell addSubview:removeFilterButton]; + } + break; + + default: + break; + } +} + + + +// Customize the appearance of table view cells. +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + + static NSString *CellIdentifier = @"SettingCell"; + UITableViewCell * cell = (UITableViewCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; + if (cell == nil) { + cell = [[UITableViewCell alloc] init]; + } + [cell setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin]; + [cell setSelectionStyle:UITableViewCellSelectionStyleNone]; + [cell setBackgroundColor:[UIColor colorWithWhite:0 alpha:0.3]]; + + + [cell addSubview:[self createSettingNameLabelForRowAtIndexPath:indexPath]]; + [self createInteractionForCell:cell atIndexPath:indexPath]; +// [cell addSubview:username]; + return cell; +} + + + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + // Navigation logic may go here. Create and push another view controller. + // AnotherViewController *anotherViewController = [[AnotherViewController alloc] initWithNibName:@"AnotherView" bundle:nil]; + // [self.navigationController pushViewController:anotherViewController]; + // [anotherViewController release]; + + // Fetch new messages section + if (indexPath.section == SECTION_MESSAGE_FETCH) + { + [prefs setInteger:indexPath.row forKey:@"fetch_message_frequency"]; + } + + UITableViewCell * cell = [tableView cellForRowAtIndexPath:indexPath]; + + // When viewing messages... + if (indexPath.section == SECTION_VIEWING_MESSAGE) + { + [prefs setBool:([cell accessoryType] != UITableViewCellAccessoryCheckmark) forKey:@"auto_mark_as_read"]; + } + + // Font size + if (indexPath.section == SECTION_FONTS) + { + [prefs setInteger:indexPath.row forKey:@"textsize"]; + } + + + if (indexPath.section == SECTION_DISPLAY) + { + if (indexPath.row == 0) + [prefs setBool:([cell accessoryType] != UITableViewCellAccessoryCheckmark) forKey:@"allow_rotation"]; + else if (indexPath.row == 1) + [prefs setBool:([cell accessoryType] != UITableViewCellAccessoryCheckmark) forKey:@"allow_status_bar_scroll"]; +// else if (indexPath.row == 2) +// [prefs setBool:([cell accessoryType] != UITableViewCellAccessoryCheckmark) forKey:@"show_quick_scroll"]; + else if (indexPath.row == 2) + { + if (![MKStoreManager isProUpgraded]) + { + [MKStoreManager needProAlert]; + return; + } + [prefs setBool:([cell accessoryType] != UITableViewCellAccessoryCheckmark) forKey:@"allow_tilt_scroll"]; + if (![cell accessoryType]) + { + NavigationController * nc = (NavigationController *) [self parentViewController]; + [nc activateTiltCalibrationMode]; + } + + } + else if (indexPath.row == 3) + { + [prefs setBool:([cell accessoryType] != UITableViewCellAccessoryCheckmark) forKey:@"reverse_tilt_axis"]; + } + + else if (indexPath.row == 4) + [prefs setBool:([cell accessoryType] != UITableViewCellAccessoryCheckmark) forKey:@"show_thumbs"]; + + else if (indexPath.row == 5) + { + [prefs setBool:([cell accessoryType] != UITableViewCellAccessoryCheckmark) forKey:@"night_mode"]; + [(AlienBlueAppDelegate *) [[UIApplication sharedApplication] delegate] refreshBackground]; + } + + } + + if (indexPath.section == SECTION_UPGRADE_PRO) + { + if (indexPath.row == 0 && ![MKStoreManager isProUpgraded]) + { + NSLog(@"upgrade now clicked"); + [purchaseActivity startAnimating]; +// [[MKStoreManager sharedManager] requestProductData]; + [[MKStoreManager sharedManager] buyProUpgrade]; + } + else if (indexPath.row == 1) + { + [self showProSplash]; + } + } + + + if (indexPath.section == SECTION_ADVANCED) + { + if (indexPath.row == 0) + [prefs setBool:([cell accessoryType] != UITableViewCellAccessoryCheckmark) forKey:@"show_hide_queue"]; + else if (indexPath.row == 1) + [prefs setBool:([cell accessoryType] != UITableViewCellAccessoryCheckmark) forKey:@"use_lowres_imgur"]; + else if (indexPath.row == 2) + [prefs setBool:([cell accessoryType] != UITableViewCellAccessoryCheckmark) forKey:@"use_direct_imgur_link"]; + + } + + + if (indexPath.section == SECTION_CONTACT) + { + if (indexPath.row == 0) + { + NSString * message = @"This will launch your Mail application and exit Alien Blue. Do you want to continue?"; + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Report a Bug" + message:message + delegate:self + cancelButtonTitle:@"No" + otherButtonTitles:@"Yes",nil]; + [alert setTag:1]; + [alert show]; + [alert release]; + } + else if (indexPath.row == 1) + { + NSString * message = @"This will launch Mobile Safari and exit Alien Blue. Do you want to continue?"; + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Visit AlienBlue.org" + message:message + delegate:self + cancelButtonTitle:@"No" + otherButtonTitles:@"Yes",nil]; + [alert setTag:2]; + [alert show]; + [alert release]; + } + } + + if (indexPath.section == SECTION_HELP) + { + if (indexPath.row == 0) + { + [self showTipsSplash]; + } + else if (indexPath.row == 1) + { + [prefs setBool:([cell accessoryType] != UITableViewCellAccessoryCheckmark) forKey:@"show_help_icon"]; + } + } + + + [prefs synchronize]; + [tableView reloadData]; + +} + +- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath +{ + return UITableViewCellEditingStyleNone; +} + + +- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath +{ + return YES; +} + + +- (void) correctSplashImageOrientation +{ + if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation)) + { + [proSplashView setFrame:CGRectMake(65, 0, 415, 320)]; + [proSplashView setContentSize:CGSizeMake(320, 600)]; + [tipsView setFrame:CGRectMake(65, 0, 415, 320)]; + [tipsView setContentSize:CGSizeMake(320, 7400)]; + + } + else + { + [proSplashView setFrame:CGRectMake(0, 0, 320, 460)]; + [proSplashView setContentSize:CGSizeMake(320, 600)]; + [tipsView setFrame:CGRectMake(0, 0, 320, 460)]; + [tipsView setContentSize:CGSizeMake(320, 7400)]; + } +} + +- (IBAction)hideSplash:(id)sender +{ + [self correctSplashImageOrientation]; + [self setView:settingsView]; + CGRect vFrame = [[self view] frame]; + vFrame.origin.x = 0; + vFrame.origin.y = 0; + if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation)) + vFrame.size.width = [UIScreen mainScreen].applicationFrame.size.height; + else + vFrame.size.width = [UIScreen mainScreen].applicationFrame.size.width; + [[self view] setFrame:vFrame]; +} + +- (void) showTipsSplash +{ + [self setView:tipsView]; + [self correctSplashImageOrientation]; + +} + +- (void) showProSplash +{ + [self setView:proSplashView]; + [self correctSplashImageOrientation]; +} + + +- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation +{ + [[self tableView] reloadData]; + + [self correctSplashImageOrientation]; + +// if (fromInterfaceOrientation == UIInterfaceOrientationPortrait && ([self view] == proSplashView)) +// { +// [proSplashView setFrame:CGRectMake(65, 0, 415, 320)]; +// [proSplashView setContentSize:CGSizeMake(320, 600)]; +// } +// else +// { +// [proSplashView setFrame:CGRectMake(0, 0, 320, 460)]; +// [proSplashView setContentSize:CGSizeMake(320, 600)]; +// } + +// [self drawProSplashImage]; +} + +@end diff --git a/Default.png b/Default.png new file mode 100755 index 0000000..2899da1 Binary files /dev/null and b/Default.png differ diff --git a/Icon.png b/Icon.png new file mode 100755 index 0000000..ab66aeb Binary files /dev/null and b/Icon.png differ diff --git a/IconBig.png b/IconBig.png new file mode 100755 index 0000000..1c77c1c Binary files /dev/null and b/IconBig.png differ diff --git a/Images/DefaultBG.png b/Images/DefaultBG.png new file mode 100755 index 0000000..a3f4029 Binary files /dev/null and b/Images/DefaultBG.png differ diff --git a/Images/DefaultBGBlack.png b/Images/DefaultBGBlack.png new file mode 100755 index 0000000..5e6cf15 Binary files /dev/null and b/Images/DefaultBGBlack.png differ diff --git a/Images/article-icon.png b/Images/article-icon.png new file mode 100755 index 0000000..d3ba917 Binary files /dev/null and b/Images/article-icon.png differ diff --git a/Images/black-gradient.png b/Images/black-gradient.png new file mode 100755 index 0000000..5b981e2 Binary files /dev/null and b/Images/black-gradient.png differ diff --git a/Images/bottom-scroll.png b/Images/bottom-scroll.png new file mode 100755 index 0000000..5cf2a5d Binary files /dev/null and b/Images/bottom-scroll.png differ diff --git a/Images/button-background-dark.png b/Images/button-background-dark.png new file mode 100755 index 0000000..0f2adb1 Binary files /dev/null and b/Images/button-background-dark.png differ diff --git a/Images/button-background.png b/Images/button-background.png new file mode 100755 index 0000000..ad28cc7 Binary files /dev/null and b/Images/button-background.png differ diff --git a/Images/comment-parent-icon.png b/Images/comment-parent-icon.png new file mode 100755 index 0000000..db9c88c Binary files /dev/null and b/Images/comment-parent-icon.png differ diff --git a/Images/delete-icon.png b/Images/delete-icon.png new file mode 100644 index 0000000..e53fbf0 Binary files /dev/null and b/Images/delete-icon.png differ diff --git a/Images/edit-icon.png b/Images/edit-icon.png new file mode 100755 index 0000000..58a7640 Binary files /dev/null and b/Images/edit-icon.png differ diff --git a/Images/fullscreen-back-button.png b/Images/fullscreen-back-button.png new file mode 100644 index 0000000..ef85ad3 Binary files /dev/null and b/Images/fullscreen-back-button.png differ diff --git a/Images/fullscreen-icon.png b/Images/fullscreen-icon.png new file mode 100644 index 0000000..12c4780 Binary files /dev/null and b/Images/fullscreen-icon.png differ diff --git a/Images/gear-icon.png b/Images/gear-icon.png new file mode 100755 index 0000000..57c5f5f Binary files /dev/null and b/Images/gear-icon.png differ diff --git a/Images/green-tick-icon.png b/Images/green-tick-icon.png new file mode 100755 index 0000000..b728e63 Binary files /dev/null and b/Images/green-tick-icon.png differ diff --git a/Images/hide-icon.png b/Images/hide-icon.png new file mode 100755 index 0000000..c04026e Binary files /dev/null and b/Images/hide-icon.png differ diff --git a/Images/hide-selected-icon.png b/Images/hide-selected-icon.png new file mode 100755 index 0000000..edb0af3 Binary files /dev/null and b/Images/hide-selected-icon.png differ diff --git a/Images/image-icon.png b/Images/image-icon.png new file mode 100755 index 0000000..c729618 Binary files /dev/null and b/Images/image-icon.png differ diff --git a/Images/info-icon.png b/Images/info-icon.png new file mode 100644 index 0000000..08994b9 Binary files /dev/null and b/Images/info-icon.png differ diff --git a/Images/level-arrows.png b/Images/level-arrows.png new file mode 100755 index 0000000..4f4adde Binary files /dev/null and b/Images/level-arrows.png differ diff --git a/Images/load-all-images.png b/Images/load-all-images.png new file mode 100755 index 0000000..c279d3c Binary files /dev/null and b/Images/load-all-images.png differ diff --git a/Images/loading-icon.png b/Images/loading-icon.png new file mode 100644 index 0000000..c3ea280 Binary files /dev/null and b/Images/loading-icon.png differ diff --git a/Images/mail-gray.png b/Images/mail-gray.png new file mode 100755 index 0000000..aa68f2e Binary files /dev/null and b/Images/mail-gray.png differ diff --git a/Images/mail-red-thin.png b/Images/mail-red-thin.png new file mode 100755 index 0000000..6f5d6ef Binary files /dev/null and b/Images/mail-red-thin.png differ diff --git a/Images/mail-red.png b/Images/mail-red.png new file mode 100755 index 0000000..1118404 Binary files /dev/null and b/Images/mail-red.png differ diff --git a/Images/more-options-icon.png b/Images/more-options-icon.png new file mode 100755 index 0000000..7e77f9a Binary files /dev/null and b/Images/more-options-icon.png differ diff --git a/Images/no-internet-connection.png b/Images/no-internet-connection.png new file mode 100755 index 0000000..ffb22f7 Binary files /dev/null and b/Images/no-internet-connection.png differ diff --git a/Images/orange-back-button.png b/Images/orange-back-button.png new file mode 100755 index 0000000..555876b Binary files /dev/null and b/Images/orange-back-button.png differ diff --git a/Images/posts-icon.png b/Images/posts-icon.png new file mode 100755 index 0000000..b5e33e0 Binary files /dev/null and b/Images/posts-icon.png differ diff --git a/Images/pro-feature-label.png b/Images/pro-feature-label.png new file mode 100755 index 0000000..f03138e Binary files /dev/null and b/Images/pro-feature-label.png differ diff --git a/Images/readability-back-icon.png b/Images/readability-back-icon.png new file mode 100755 index 0000000..4d122cc Binary files /dev/null and b/Images/readability-back-icon.png differ diff --git a/Images/readability-icon.png b/Images/readability-icon.png new file mode 100755 index 0000000..56d6f15 Binary files /dev/null and b/Images/readability-icon.png differ diff --git a/Images/reply-selected.png b/Images/reply-selected.png new file mode 100755 index 0000000..b521649 Binary files /dev/null and b/Images/reply-selected.png differ diff --git a/Images/reply.png b/Images/reply.png new file mode 100755 index 0000000..791b0e7 Binary files /dev/null and b/Images/reply.png differ diff --git a/Images/right-arrow-dim.png b/Images/right-arrow-dim.png new file mode 100644 index 0000000..185896b Binary files /dev/null and b/Images/right-arrow-dim.png differ diff --git a/Images/show-toolbars-button.png b/Images/show-toolbars-button.png new file mode 100644 index 0000000..215a2ab Binary files /dev/null and b/Images/show-toolbars-button.png differ diff --git a/Images/star-icon.png b/Images/star-icon.png new file mode 100755 index 0000000..c0c0198 Binary files /dev/null and b/Images/star-icon.png differ diff --git a/Images/star-selected-icon.png b/Images/star-selected-icon.png new file mode 100755 index 0000000..d68d582 Binary files /dev/null and b/Images/star-selected-icon.png differ diff --git a/Images/tips-sheet.png b/Images/tips-sheet.png new file mode 100644 index 0000000..a7aef78 Binary files /dev/null and b/Images/tips-sheet.png differ diff --git a/Images/top-scroll.png b/Images/top-scroll.png new file mode 100755 index 0000000..3ff11a4 Binary files /dev/null and b/Images/top-scroll.png differ diff --git a/Images/upgrade-to-pro-label.png b/Images/upgrade-to-pro-label.png new file mode 100755 index 0000000..d1a8ee4 Binary files /dev/null and b/Images/upgrade-to-pro-label.png differ diff --git a/Images/video-icon.png b/Images/video-icon.png new file mode 100755 index 0000000..3560219 Binary files /dev/null and b/Images/video-icon.png differ diff --git a/Images/vote-down-selected-small.png b/Images/vote-down-selected-small.png new file mode 100755 index 0000000..542c756 Binary files /dev/null and b/Images/vote-down-selected-small.png differ diff --git a/Images/vote-down-selected.png b/Images/vote-down-selected.png new file mode 100755 index 0000000..452c01c Binary files /dev/null and b/Images/vote-down-selected.png differ diff --git a/Images/vote-down-small.png b/Images/vote-down-small.png new file mode 100755 index 0000000..faf58ca Binary files /dev/null and b/Images/vote-down-small.png differ diff --git a/Images/vote-down.png b/Images/vote-down.png new file mode 100755 index 0000000..31ebe41 Binary files /dev/null and b/Images/vote-down.png differ diff --git a/Images/vote-up-selected-small.png b/Images/vote-up-selected-small.png new file mode 100755 index 0000000..de53397 Binary files /dev/null and b/Images/vote-up-selected-small.png differ diff --git a/Images/vote-up-selected.png b/Images/vote-up-selected.png new file mode 100755 index 0000000..eb2b46b Binary files /dev/null and b/Images/vote-up-selected.png differ diff --git a/Images/vote-up-small.png b/Images/vote-up-small.png new file mode 100755 index 0000000..f29f78d Binary files /dev/null and b/Images/vote-up-small.png differ diff --git a/Images/vote-up.png b/Images/vote-up.png new file mode 100755 index 0000000..0146684 Binary files /dev/null and b/Images/vote-up.png differ diff --git a/Images/why-go-pro-splash.png b/Images/why-go-pro-splash.png new file mode 100644 index 0000000..6c2e267 Binary files /dev/null and b/Images/why-go-pro-splash.png differ diff --git a/JSON/JSON.h b/JSON/JSON.h new file mode 100644 index 0000000..1e58c9a --- /dev/null +++ b/JSON/JSON.h @@ -0,0 +1,50 @@ +/* + Copyright (C) 2009 Stig Brautaset. 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 author 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 OWNER 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. + */ + +/** + @mainpage A strict JSON parser and generator for Objective-C + + JSON (JavaScript Object Notation) is a lightweight data-interchange + format. This framework provides two apis for parsing and generating + JSON. One standard object-based and a higher level api consisting of + categories added to existing Objective-C classes. + + Learn more on the http://code.google.com/p/json-framework project site. + + This framework does its best to be as strict as possible, both in what it + accepts and what it generates. For example, it does not support trailing commas + in arrays or objects. Nor does it support embedded comments, or + anything else not in the JSON specification. This is considered a feature. + +*/ + +#import "SBJSON.h" +#import "NSObject+SBJSON.h" +#import "NSString+SBJSON.h" + diff --git a/JSON/NSObject+SBJSON.h b/JSON/NSObject+SBJSON.h new file mode 100644 index 0000000..ecf0ee4 --- /dev/null +++ b/JSON/NSObject+SBJSON.h @@ -0,0 +1,68 @@ +/* + Copyright (C) 2009 Stig Brautaset. 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 author 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 OWNER 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. + */ + +#import + + +/** + @brief Adds JSON generation to Foundation classes + + This is a category on NSObject that adds methods for returning JSON representations + of standard objects to the objects themselves. This means you can call the + -JSONRepresentation method on an NSArray object and it'll do what you want. + */ +@interface NSObject (NSObject_SBJSON) + +/** + @brief Returns a string containing the receiver encoded as a JSON fragment. + + This method is added as a category on NSObject but is only actually + supported for the following objects: + @li NSDictionary + @li NSArray + @li NSString + @li NSNumber (also used for booleans) + @li NSNull + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (NSString *)JSONFragment; + +/** + @brief Returns a string containing the receiver encoded in JSON. + + This method is added as a category on NSObject but is only actually + supported for the following objects: + @li NSDictionary + @li NSArray + */ +- (NSString *)JSONRepresentation; + +@end + diff --git a/JSON/NSObject+SBJSON.m b/JSON/NSObject+SBJSON.m new file mode 100644 index 0000000..20b084b --- /dev/null +++ b/JSON/NSObject+SBJSON.m @@ -0,0 +1,53 @@ +/* + Copyright (C) 2009 Stig Brautaset. 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 author 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 OWNER 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. + */ + +#import "NSObject+SBJSON.h" +#import "SBJsonWriter.h" + +@implementation NSObject (NSObject_SBJSON) + +- (NSString *)JSONFragment { + SBJsonWriter *jsonWriter = [SBJsonWriter new]; + NSString *json = [jsonWriter stringWithFragment:self]; + if (!json) + NSLog(@"-JSONFragment failed. Error trace is: %@", [jsonWriter errorTrace]); + [jsonWriter release]; + return json; +} + +- (NSString *)JSONRepresentation { + SBJsonWriter *jsonWriter = [SBJsonWriter new]; + NSString *json = [jsonWriter stringWithObject:self]; + if (!json) + NSLog(@"-JSONRepresentation failed. Error trace is: %@", [jsonWriter errorTrace]); + [jsonWriter release]; + return json; +} + +@end diff --git a/JSON/NSString+SBJSON.h b/JSON/NSString+SBJSON.h new file mode 100644 index 0000000..fad7179 --- /dev/null +++ b/JSON/NSString+SBJSON.h @@ -0,0 +1,58 @@ +/* + Copyright (C) 2009 Stig Brautaset. 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 author 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 OWNER 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. + */ + +#import + +/** + @brief Adds JSON parsing methods to NSString + +This is a category on NSString that adds methods for parsing the target string. +*/ +@interface NSString (NSString_SBJSON) + + +/** + @brief Returns the object represented in the receiver, or nil on error. + + Returns a a scalar object represented by the string's JSON fragment representation. + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (id)JSONFragmentValue; + +/** + @brief Returns the NSDictionary or NSArray represented by the current string's JSON representation. + + Returns the dictionary or array represented in the receiver, or nil on error. + + Returns the NSDictionary or NSArray represented by the current string's JSON representation. + */ +- (id)JSONValue; + +@end diff --git a/JSON/NSString+SBJSON.m b/JSON/NSString+SBJSON.m new file mode 100644 index 0000000..41a5a85 --- /dev/null +++ b/JSON/NSString+SBJSON.m @@ -0,0 +1,55 @@ +/* + Copyright (C) 2007-2009 Stig Brautaset. 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 author 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 OWNER 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. + */ + +#import "NSString+SBJSON.h" +#import "SBJsonParser.h" + +@implementation NSString (NSString_SBJSON) + +- (id)JSONFragmentValue +{ + SBJsonParser *jsonParser = [SBJsonParser new]; + id repr = [jsonParser fragmentWithString:self]; + if (!repr) + NSLog(@"-JSONFragmentValue failed. Error trace is: %@", [jsonParser errorTrace]); + [jsonParser release]; + return repr; +} + +- (id)JSONValue +{ + SBJsonParser *jsonParser = [SBJsonParser new]; + id repr = [jsonParser objectWithString:self]; + if (!repr) + NSLog(@"-JSONValue failed. Error trace is: %@", [jsonParser errorTrace]); + [jsonParser release]; + return repr; +} + +@end diff --git a/JSON/SBJSON.h b/JSON/SBJSON.h new file mode 100644 index 0000000..43d63c3 --- /dev/null +++ b/JSON/SBJSON.h @@ -0,0 +1,75 @@ +/* + Copyright (C) 2007-2009 Stig Brautaset. 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 author 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 OWNER 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. + */ + +#import +#import "SBJsonParser.h" +#import "SBJsonWriter.h" + +/** + @brief Facade for SBJsonWriter/SBJsonParser. + + Requests are forwarded to instances of SBJsonWriter and SBJsonParser. + */ +@interface SBJSON : SBJsonBase { + +@private + SBJsonParser *jsonParser; + SBJsonWriter *jsonWriter; +} + + +/// Return the fragment represented by the given string +- (id)fragmentWithString:(NSString*)jsonrep + error:(NSError**)error; + +/// Return the object represented by the given string +- (id)objectWithString:(NSString*)jsonrep + error:(NSError**)error; + +/// Parse the string and return the represented object (or scalar) +- (id)objectWithString:(id)value + allowScalar:(BOOL)x + error:(NSError**)error; + + +/// Return JSON representation of an array or dictionary +- (NSString*)stringWithObject:(id)value + error:(NSError**)error; + +/// Return JSON representation of any legal JSON value +- (NSString*)stringWithFragment:(id)value + error:(NSError**)error; + +/// Return JSON representation (or fragment) for the given object +- (NSString*)stringWithObject:(id)value + allowScalar:(BOOL)x + error:(NSError**)error; + + +@end diff --git a/JSON/SBJSON.m b/JSON/SBJSON.m new file mode 100644 index 0000000..2a30f1a --- /dev/null +++ b/JSON/SBJSON.m @@ -0,0 +1,212 @@ +/* + Copyright (C) 2007-2009 Stig Brautaset. 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 author 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 OWNER 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. + */ + +#import "SBJSON.h" + +@implementation SBJSON + +- (id)init { + self = [super init]; + if (self) { + jsonWriter = [SBJsonWriter new]; + jsonParser = [SBJsonParser new]; + [self setMaxDepth:512]; + + } + return self; +} + +- (void)dealloc { + [jsonWriter release]; + [jsonParser release]; + [super dealloc]; +} + +#pragma mark Writer + + +- (NSString *)stringWithObject:(id)obj { + NSString *repr = [jsonWriter stringWithObject:obj]; + if (repr) + return repr; + + [errorTrace release]; + errorTrace = [[jsonWriter errorTrace] mutableCopy]; + return nil; +} + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p *error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + @param allowScalar wether to return json fragments for scalar objects + @param error used to return an error by reference (pass NULL if this is not desired) + +@deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (NSString*)stringWithObject:(id)value allowScalar:(BOOL)allowScalar error:(NSError**)error { + + NSString *json = allowScalar ? [jsonWriter stringWithFragment:value] : [jsonWriter stringWithObject:value]; + if (json) + return json; + + [errorTrace release]; + errorTrace = [[jsonWriter errorTrace] mutableCopy]; + + if (error) + *error = [errorTrace lastObject]; + return nil; +} + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + @param error used to return an error by reference (pass NULL if this is not desired) + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (NSString*)stringWithFragment:(id)value error:(NSError**)error { + return [self stringWithObject:value + allowScalar:YES + error:error]; +} + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p error can be interrogated to find the cause of the error. + + @param value a NSDictionary or NSArray instance + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (NSString*)stringWithObject:(id)value error:(NSError**)error { + return [self stringWithObject:value + allowScalar:NO + error:error]; +} + +#pragma mark Parsing + +- (id)objectWithString:(NSString *)repr { + id obj = [jsonParser objectWithString:repr]; + if (obj) + return obj; + + [errorTrace release]; + errorTrace = [[jsonParser errorTrace] mutableCopy]; + + return nil; +} + +/** + Returns the object represented by the passed-in string or nil on error. The returned object can be + a string, number, boolean, null, array or dictionary. + + @param value the json string to parse + @param allowScalar whether to return objects for JSON fragments + @param error used to return an error by reference (pass NULL if this is not desired) + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (id)objectWithString:(id)value allowScalar:(BOOL)allowScalar error:(NSError**)error { + + id obj = allowScalar ? [jsonParser fragmentWithString:value] : [jsonParser objectWithString:value]; + if (obj) + return obj; + + [errorTrace release]; + errorTrace = [[jsonParser errorTrace] mutableCopy]; + + if (error) + *error = [errorTrace lastObject]; + return nil; +} + +/** + Returns the object represented by the passed-in string or nil on error. The returned object can be + a string, number, boolean, null, array or dictionary. + + @param repr the json string to parse + @param error used to return an error by reference (pass NULL if this is not desired) + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (id)fragmentWithString:(NSString*)repr error:(NSError**)error { + return [self objectWithString:repr + allowScalar:YES + error:error]; +} + +/** + Returns the object represented by the passed-in string or nil on error. The returned object + will be either a dictionary or an array. + + @param repr the json string to parse + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (id)objectWithString:(NSString*)repr error:(NSError**)error { + return [self objectWithString:repr + allowScalar:NO + error:error]; +} + + + +#pragma mark Properties - parsing + +- (NSUInteger)maxDepth { + return jsonParser.maxDepth; +} + +- (void)setMaxDepth:(NSUInteger)d { + jsonWriter.maxDepth = jsonParser.maxDepth = d; +} + + +#pragma mark Properties - writing + +- (BOOL)humanReadable { + return jsonWriter.humanReadable; +} + +- (void)setHumanReadable:(BOOL)x { + jsonWriter.humanReadable = x; +} + +- (BOOL)sortKeys { + return jsonWriter.sortKeys; +} + +- (void)setSortKeys:(BOOL)x { + jsonWriter.sortKeys = x; +} + +@end diff --git a/JSON/SBJsonBase.h b/JSON/SBJsonBase.h new file mode 100644 index 0000000..7b10844 --- /dev/null +++ b/JSON/SBJsonBase.h @@ -0,0 +1,86 @@ +/* + Copyright (C) 2009 Stig Brautaset. 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 author 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 OWNER 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. + */ + +#import + +extern NSString * SBJSONErrorDomain; + + +enum { + EUNSUPPORTED = 1, + EPARSENUM, + EPARSE, + EFRAGMENT, + ECTRL, + EUNICODE, + EDEPTH, + EESCAPE, + ETRAILCOMMA, + ETRAILGARBAGE, + EEOF, + EINPUT +}; + +/** + @brief Common base class for parsing & writing. + + This class contains the common error-handling code and option between the parser/writer. + */ +@interface SBJsonBase : NSObject { + NSMutableArray *errorTrace; + +@protected + NSUInteger depth, maxDepth; +} + +/** + @brief The maximum recursing depth. + + Defaults to 512. If the input is nested deeper than this the input will be deemed to be + malicious and the parser returns nil, signalling an error. ("Nested too deep".) You can + turn off this security feature by setting the maxDepth value to 0. + */ +@property NSUInteger maxDepth; + +/** + @brief Return an error trace, or nil if there was no errors. + + Note that this method returns the trace of the last method that failed. + You need to check the return value of the call you're making to figure out + if the call actually failed, before you know call this method. + */ + @property(copy,readonly) NSArray* errorTrace; + +/// @internal for use in subclasses to add errors to the stack trace +- (void)addErrorWithCode:(NSUInteger)code description:(NSString*)str; + +/// @internal for use in subclasess to clear the error before a new parsing attempt +- (void)clearErrorTrace; + +@end diff --git a/JSON/SBJsonBase.m b/JSON/SBJsonBase.m new file mode 100644 index 0000000..6684325 --- /dev/null +++ b/JSON/SBJsonBase.m @@ -0,0 +1,78 @@ +/* + Copyright (C) 2009 Stig Brautaset. 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 author 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 OWNER 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. + */ + +#import "SBJsonBase.h" +NSString * SBJSONErrorDomain = @"org.brautaset.JSON.ErrorDomain"; + + +@implementation SBJsonBase + +@synthesize errorTrace; +@synthesize maxDepth; + +- (id)init { + self = [super init]; + if (self) + self.maxDepth = 512; + return self; +} + +- (void)dealloc { + [errorTrace release]; + [super dealloc]; +} + +- (void)addErrorWithCode:(NSUInteger)code description:(NSString*)str { + NSDictionary *userInfo; + if (!errorTrace) { + errorTrace = [NSMutableArray new]; + userInfo = [NSDictionary dictionaryWithObject:str forKey:NSLocalizedDescriptionKey]; + + } else { + userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + str, NSLocalizedDescriptionKey, + [errorTrace lastObject], NSUnderlyingErrorKey, + nil]; + } + + NSError *error = [NSError errorWithDomain:SBJSONErrorDomain code:code userInfo:userInfo]; + + [self willChangeValueForKey:@"errorTrace"]; + [errorTrace addObject:error]; + [self didChangeValueForKey:@"errorTrace"]; +} + +- (void)clearErrorTrace { + [self willChangeValueForKey:@"errorTrace"]; + [errorTrace release]; + errorTrace = nil; + [self didChangeValueForKey:@"errorTrace"]; +} + +@end diff --git a/JSON/SBJsonParser.h b/JSON/SBJsonParser.h new file mode 100644 index 0000000..e95304d --- /dev/null +++ b/JSON/SBJsonParser.h @@ -0,0 +1,87 @@ +/* + Copyright (C) 2009 Stig Brautaset. 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 author 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 OWNER 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. + */ + +#import +#import "SBJsonBase.h" + +/** + @brief Options for the parser class. + + This exists so the SBJSON facade can implement the options in the parser without having to re-declare them. + */ +@protocol SBJsonParser + +/** + @brief Return the object represented by the given string. + + Returns the object represented by the passed-in string or nil on error. The returned object can be + a string, number, boolean, null, array or dictionary. + + @param repr the json string to parse + */ +- (id)objectWithString:(NSString *)repr; + +@end + + +/** + @brief The JSON parser class. + + JSON is mapped to Objective-C types in the following way: + + @li Null -> NSNull + @li String -> NSMutableString + @li Array -> NSMutableArray + @li Object -> NSMutableDictionary + @li Boolean -> NSNumber (initialised with -initWithBool:) + @li Number -> NSDecimalNumber + + Since Objective-C doesn't have a dedicated class for boolean values, these turns into NSNumber + instances. These are initialised with the -initWithBool: method, and + round-trip back to JSON properly. (They won't silently suddenly become 0 or 1; they'll be + represented as 'true' and 'false' again.) + + JSON numbers turn into NSDecimalNumber instances, + as we can thus avoid any loss of precision. (JSON allows ridiculously large numbers.) + + */ +@interface SBJsonParser : SBJsonBase { + +@private + const char *c; +} + +@end + +// don't use - exists for backwards compatibility with 2.1.x only. Will be removed in 2.3. +@interface SBJsonParser (Private) +- (id)fragmentWithString:(id)repr; +@end + + diff --git a/JSON/SBJsonParser.m b/JSON/SBJsonParser.m new file mode 100644 index 0000000..eda051a --- /dev/null +++ b/JSON/SBJsonParser.m @@ -0,0 +1,475 @@ +/* + Copyright (C) 2009 Stig Brautaset. 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 author 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 OWNER 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. + */ + +#import "SBJsonParser.h" + +@interface SBJsonParser () + +- (BOOL)scanValue:(NSObject **)o; + +- (BOOL)scanRestOfArray:(NSMutableArray **)o; +- (BOOL)scanRestOfDictionary:(NSMutableDictionary **)o; +- (BOOL)scanRestOfNull:(NSNull **)o; +- (BOOL)scanRestOfFalse:(NSNumber **)o; +- (BOOL)scanRestOfTrue:(NSNumber **)o; +- (BOOL)scanRestOfString:(NSMutableString **)o; + +// Cannot manage without looking at the first digit +- (BOOL)scanNumber:(NSNumber **)o; + +- (BOOL)scanHexQuad:(unichar *)x; +- (BOOL)scanUnicodeChar:(unichar *)x; + +- (BOOL)scanIsAtEnd; + +@end + +#define skipWhitespace(c) while (isspace(*c)) c++ +#define skipDigits(c) while (isdigit(*c)) c++ + + +@implementation SBJsonParser + +static char ctrl[0x22]; + + ++ (void)initialize { + ctrl[0] = '\"'; + ctrl[1] = '\\'; + for (int i = 1; i < 0x20; i++) + ctrl[i+1] = i; + ctrl[0x21] = 0; +} + +/** + @deprecated This exists in order to provide fragment support in older APIs in one more version. + It should be removed in the next major version. + */ +- (id)fragmentWithString:(id)repr { + [self clearErrorTrace]; + + if (!repr) { + [self addErrorWithCode:EINPUT description:@"Input was 'nil'"]; + return nil; + } + + depth = 0; + c = [repr UTF8String]; + + id o; + if (![self scanValue:&o]) { + return nil; + } + + // We found some valid JSON. But did it also contain something else? + if (![self scanIsAtEnd]) { + [self addErrorWithCode:ETRAILGARBAGE description:@"Garbage after JSON"]; + return nil; + } + + NSAssert1(o, @"Should have a valid object from %@", repr); + return o; +} + +- (id)objectWithString:(NSString *)repr { + + id o = [self fragmentWithString:repr]; + if (!o) + return nil; + + // Check that the object we've found is a valid JSON container. + if (![o isKindOfClass:[NSDictionary class]] && ![o isKindOfClass:[NSArray class]]) { + [self addErrorWithCode:EFRAGMENT description:@"Valid fragment, but not JSON"]; + return nil; + } + + return o; +} + +/* + In contrast to the public methods, it is an error to omit the error parameter here. + */ +- (BOOL)scanValue:(NSObject **)o +{ + skipWhitespace(c); + + switch (*c++) { + case '{': + return [self scanRestOfDictionary:(NSMutableDictionary **)o]; + break; + case '[': + return [self scanRestOfArray:(NSMutableArray **)o]; + break; + case '"': + return [self scanRestOfString:(NSMutableString **)o]; + break; + case 'f': + return [self scanRestOfFalse:(NSNumber **)o]; + break; + case 't': + return [self scanRestOfTrue:(NSNumber **)o]; + break; + case 'n': + return [self scanRestOfNull:(NSNull **)o]; + break; + case '-': + case '0'...'9': + c--; // cannot verify number correctly without the first character + return [self scanNumber:(NSNumber **)o]; + break; + case '+': + [self addErrorWithCode:EPARSENUM description: @"Leading + disallowed in number"]; + return NO; + break; + case 0x0: + [self addErrorWithCode:EEOF description:@"Unexpected end of string"]; + return NO; + break; + default: + [self addErrorWithCode:EPARSE description: @"Unrecognised leading character"]; + return NO; + break; + } + + NSAssert(0, @"Should never get here"); + return NO; +} + +- (BOOL)scanRestOfTrue:(NSNumber **)o +{ + if (!strncmp(c, "rue", 3)) { + c += 3; + *o = [NSNumber numberWithBool:YES]; + return YES; + } + [self addErrorWithCode:EPARSE description:@"Expected 'true'"]; + return NO; +} + +- (BOOL)scanRestOfFalse:(NSNumber **)o +{ + if (!strncmp(c, "alse", 4)) { + c += 4; + *o = [NSNumber numberWithBool:NO]; + return YES; + } + [self addErrorWithCode:EPARSE description: @"Expected 'false'"]; + return NO; +} + +- (BOOL)scanRestOfNull:(NSNull **)o { + if (!strncmp(c, "ull", 3)) { + c += 3; + *o = [NSNull null]; + return YES; + } + [self addErrorWithCode:EPARSE description: @"Expected 'null'"]; + return NO; +} + +- (BOOL)scanRestOfArray:(NSMutableArray **)o { + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + + *o = [NSMutableArray arrayWithCapacity:8]; + + for (; *c ;) { + id v; + + skipWhitespace(c); + if (*c == ']' && c++) { + depth--; + return YES; + } + + if (![self scanValue:&v]) { + [self addErrorWithCode:EPARSE description:@"Expected value while parsing array"]; + return NO; + } + + [*o addObject:v]; + + skipWhitespace(c); + if (*c == ',' && c++) { + skipWhitespace(c); + if (*c == ']') { + [self addErrorWithCode:ETRAILCOMMA description: @"Trailing comma disallowed in array"]; + return NO; + } + } + } + + [self addErrorWithCode:EEOF description: @"End of input while parsing array"]; + return NO; +} + +- (BOOL)scanRestOfDictionary:(NSMutableDictionary **)o +{ + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + + *o = [NSMutableDictionary dictionaryWithCapacity:7]; + + for (; *c ;) { + id k, v; + + skipWhitespace(c); + if (*c == '}' && c++) { + depth--; + return YES; + } + + if (!(*c == '\"' && c++ && [self scanRestOfString:&k])) { + [self addErrorWithCode:EPARSE description: @"Object key string expected"]; + return NO; + } + + skipWhitespace(c); + if (*c != ':') { + [self addErrorWithCode:EPARSE description: @"Expected ':' separating key and value"]; + return NO; + } + + c++; + if (![self scanValue:&v]) { + NSString *string = [NSString stringWithFormat:@"Object value expected for key: %@", k]; + [self addErrorWithCode:EPARSE description: string]; + return NO; + } + + [*o setObject:v forKey:k]; + + skipWhitespace(c); + if (*c == ',' && c++) { + skipWhitespace(c); + if (*c == '}') { + [self addErrorWithCode:ETRAILCOMMA description: @"Trailing comma disallowed in object"]; + return NO; + } + } + } + + [self addErrorWithCode:EEOF description: @"End of input while parsing object"]; + return NO; +} + +- (BOOL)scanRestOfString:(NSMutableString **)o +{ + *o = [NSMutableString stringWithCapacity:16]; + do { + // First see if there's a portion we can grab in one go. + // Doing this caused a massive speedup on the long string. + size_t len = strcspn(c, ctrl); + if (len) { + // check for + id t = [[NSString alloc] initWithBytesNoCopy:(char*)c + length:len + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + if (t) { + [*o appendString:t]; + [t release]; + c += len; + } + } + + if (*c == '"') { + c++; + return YES; + + } else if (*c == '\\') { + unichar uc = *++c; + switch (uc) { + case '\\': + case '/': + case '"': + break; + + case 'b': uc = '\b'; break; + case 'n': uc = '\n'; break; + case 'r': uc = '\r'; break; + case 't': uc = '\t'; break; + case 'f': uc = '\f'; break; + + case 'u': + c++; + if (![self scanUnicodeChar:&uc]) { + [self addErrorWithCode:EUNICODE description: @"Broken unicode character"]; + return NO; + } + c--; // hack. + break; + default: + [self addErrorWithCode:EESCAPE description: [NSString stringWithFormat:@"Illegal escape sequence '0x%x'", uc]]; + return NO; + break; + } + CFStringAppendCharacters((CFMutableStringRef)*o, &uc, 1); + c++; + + } else if (*c < 0x20) { + [self addErrorWithCode:ECTRL description: [NSString stringWithFormat:@"Unescaped control character '0x%x'", *c]]; + return NO; + + } else { + NSLog(@"should not be able to get here"); + } + } while (*c); + + [self addErrorWithCode:EEOF description:@"Unexpected EOF while parsing string"]; + return NO; +} + +- (BOOL)scanUnicodeChar:(unichar *)x +{ + unichar hi, lo; + + if (![self scanHexQuad:&hi]) { + [self addErrorWithCode:EUNICODE description: @"Missing hex quad"]; + return NO; + } + + if (hi >= 0xd800) { // high surrogate char? + if (hi < 0xdc00) { // yes - expect a low char + + if (!(*c == '\\' && ++c && *c == 'u' && ++c && [self scanHexQuad:&lo])) { + [self addErrorWithCode:EUNICODE description: @"Missing low character in surrogate pair"]; + return NO; + } + + if (lo < 0xdc00 || lo >= 0xdfff) { + [self addErrorWithCode:EUNICODE description:@"Invalid low surrogate char"]; + return NO; + } + + hi = (hi - 0xd800) * 0x400 + (lo - 0xdc00) + 0x10000; + + } else if (hi < 0xe000) { + [self addErrorWithCode:EUNICODE description:@"Invalid high character in surrogate pair"]; + return NO; + } + } + + *x = hi; + return YES; +} + +- (BOOL)scanHexQuad:(unichar *)x +{ + *x = 0; + for (int i = 0; i < 4; i++) { + unichar uc = *c; + c++; + int d = (uc >= '0' && uc <= '9') + ? uc - '0' : (uc >= 'a' && uc <= 'f') + ? (uc - 'a' + 10) : (uc >= 'A' && uc <= 'F') + ? (uc - 'A' + 10) : -1; + if (d == -1) { + [self addErrorWithCode:EUNICODE description:@"Missing hex digit in quad"]; + return NO; + } + *x *= 16; + *x += d; + } + return YES; +} + +- (BOOL)scanNumber:(NSNumber **)o +{ + const char *ns = c; + + // The logic to test for validity of the number formatting is relicensed + // from JSON::XS with permission from its author Marc Lehmann. + // (Available at the CPAN: http://search.cpan.org/dist/JSON-XS/ .) + + if ('-' == *c) + c++; + + if ('0' == *c && c++) { + if (isdigit(*c)) { + [self addErrorWithCode:EPARSENUM description: @"Leading 0 disallowed in number"]; + return NO; + } + + } else if (!isdigit(*c) && c != ns) { + [self addErrorWithCode:EPARSENUM description: @"No digits after initial minus"]; + return NO; + + } else { + skipDigits(c); + } + + // Fractional part + if ('.' == *c && c++) { + + if (!isdigit(*c)) { + [self addErrorWithCode:EPARSENUM description: @"No digits after decimal point"]; + return NO; + } + skipDigits(c); + } + + // Exponential part + if ('e' == *c || 'E' == *c) { + c++; + + if ('-' == *c || '+' == *c) + c++; + + if (!isdigit(*c)) { + [self addErrorWithCode:EPARSENUM description: @"No digits after exponent"]; + return NO; + } + skipDigits(c); + } + + id str = [[NSString alloc] initWithBytesNoCopy:(char*)ns + length:c - ns + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + [str autorelease]; + if (str && (*o = [NSDecimalNumber decimalNumberWithString:str])) + return YES; + + [self addErrorWithCode:EPARSENUM description: @"Failed creating decimal instance"]; + return NO; +} + +- (BOOL)scanIsAtEnd +{ + skipWhitespace(c); + return !*c; +} + + +@end diff --git a/JSON/SBJsonWriter.h b/JSON/SBJsonWriter.h new file mode 100644 index 0000000..f6f5e17 --- /dev/null +++ b/JSON/SBJsonWriter.h @@ -0,0 +1,129 @@ +/* + Copyright (C) 2009 Stig Brautaset. 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 author 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 OWNER 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. + */ + +#import +#import "SBJsonBase.h" + +/** + @brief Options for the writer class. + + This exists so the SBJSON facade can implement the options in the writer without having to re-declare them. + */ +@protocol SBJsonWriter + +/** + @brief Whether we are generating human-readable (multiline) JSON. + + Set whether or not to generate human-readable JSON. The default is NO, which produces + JSON without any whitespace. (Except inside strings.) If set to YES, generates human-readable + JSON with linebreaks after each array value and dictionary key/value pair, indented two + spaces per nesting level. + */ +@property BOOL humanReadable; + +/** + @brief Whether or not to sort the dictionary keys in the output. + + If this is set to YES, the dictionary keys in the JSON output will be in sorted order. + (This is useful if you need to compare two structures, for example.) The default is NO. + */ +@property BOOL sortKeys; + +/** + @brief Return JSON representation (or fragment) for the given object. + + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p *error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + + */ +- (NSString*)stringWithObject:(id)value; + +@end + + +/** + @brief The JSON writer class. + + Objective-C types are mapped to JSON types in the following way: + + @li NSNull -> Null + @li NSString -> String + @li NSArray -> Array + @li NSDictionary -> Object + @li NSNumber (-initWithBool:) -> Boolean + @li NSNumber -> Number + + In JSON the keys of an object must be strings. NSDictionary keys need + not be, but attempting to convert an NSDictionary with non-string keys + into JSON will throw an exception. + + NSNumber instances created with the +initWithBool: method are + converted into the JSON boolean "true" and "false" values, and vice + versa. Any other NSNumber instances are converted to a JSON number the + way you would expect. + + */ +@interface SBJsonWriter : SBJsonBase { + +@private + BOOL sortKeys, humanReadable; +} + +@end + +// don't use - exists for backwards compatibility. Will be removed in 2.3. +@interface SBJsonWriter (Private) +- (NSString*)stringWithFragment:(id)value; +@end + +/** + @brief Allows generation of JSON for otherwise unsupported classes. + + If you have a custom class that you want to create a JSON representation for you can implement + this method in your class. It should return a representation of your object defined + in terms of objects that can be translated into JSON. For example, a Person + object might implement it like this: + + @code + - (id)jsonProxyObject { + return [NSDictionary dictionaryWithObjectsAndKeys: + name, @"name", + phone, @"phone", + email, @"email", + nil]; + } + @endcode + + */ +@interface NSObject (SBProxyForJson) +- (id)proxyForJson; +@end + diff --git a/JSON/SBJsonWriter.m b/JSON/SBJsonWriter.m new file mode 100644 index 0000000..0f32904 --- /dev/null +++ b/JSON/SBJsonWriter.m @@ -0,0 +1,237 @@ +/* + Copyright (C) 2009 Stig Brautaset. 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 author 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 OWNER 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. + */ + +#import "SBJsonWriter.h" + +@interface SBJsonWriter () + +- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json; +- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json; +- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json; +- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json; + +- (NSString*)indent; + +@end + +@implementation SBJsonWriter + +static NSMutableCharacterSet *kEscapeChars; + ++ (void)initialize { + kEscapeChars = [[NSMutableCharacterSet characterSetWithRange: NSMakeRange(0,32)] retain]; + [kEscapeChars addCharactersInString: @"\"\\"]; +} + + +@synthesize sortKeys; +@synthesize humanReadable; + +/** + @deprecated This exists in order to provide fragment support in older APIs in one more version. + It should be removed in the next major version. + */ +- (NSString*)stringWithFragment:(id)value { + [self clearErrorTrace]; + depth = 0; + NSMutableString *json = [NSMutableString stringWithCapacity:128]; + + if ([self appendValue:value into:json]) + return json; + + return nil; +} + + +- (NSString*)stringWithObject:(id)value { + + if ([value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSArray class]]) { + return [self stringWithFragment:value]; + } + + if ([value respondsToSelector:@selector(proxyForJson)]) { + NSString *tmp = [self stringWithObject:[value proxyForJson]]; + if (tmp) + return tmp; + } + + + [self clearErrorTrace]; + [self addErrorWithCode:EFRAGMENT description:@"Not valid type for JSON"]; + return nil; +} + + +- (NSString*)indent { + return [@"\n" stringByPaddingToLength:1 + 2 * depth withString:@" " startingAtIndex:0]; +} + +- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json { + if ([fragment isKindOfClass:[NSDictionary class]]) { + if (![self appendDictionary:fragment into:json]) + return NO; + + } else if ([fragment isKindOfClass:[NSArray class]]) { + if (![self appendArray:fragment into:json]) + return NO; + + } else if ([fragment isKindOfClass:[NSString class]]) { + if (![self appendString:fragment into:json]) + return NO; + + } else if ([fragment isKindOfClass:[NSNumber class]]) { + if ('c' == *[fragment objCType]) + [json appendString:[fragment boolValue] ? @"true" : @"false"]; + else + [json appendString:[fragment stringValue]]; + + } else if ([fragment isKindOfClass:[NSNull class]]) { + [json appendString:@"null"]; + } else if ([fragment respondsToSelector:@selector(proxyForJson)]) { + [self appendValue:[fragment proxyForJson] into:json]; + + } else { + [self addErrorWithCode:EUNSUPPORTED description:[NSString stringWithFormat:@"JSON serialisation not supported for %@", [fragment class]]]; + return NO; + } + return YES; +} + +- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json { + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + [json appendString:@"["]; + + BOOL addComma = NO; + for (id value in fragment) { + if (addComma) + [json appendString:@","]; + else + addComma = YES; + + if ([self humanReadable]) + [json appendString:[self indent]]; + + if (![self appendValue:value into:json]) { + return NO; + } + } + + depth--; + if ([self humanReadable] && [fragment count]) + [json appendString:[self indent]]; + [json appendString:@"]"]; + return YES; +} + +- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json { + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + [json appendString:@"{"]; + + NSString *colon = [self humanReadable] ? @" : " : @":"; + BOOL addComma = NO; + NSArray *keys = [fragment allKeys]; + if (self.sortKeys) + keys = [keys sortedArrayUsingSelector:@selector(compare:)]; + + for (id value in keys) { + if (addComma) + [json appendString:@","]; + else + addComma = YES; + + if ([self humanReadable]) + [json appendString:[self indent]]; + + if (![value isKindOfClass:[NSString class]]) { + [self addErrorWithCode:EUNSUPPORTED description: @"JSON object key must be string"]; + return NO; + } + + if (![self appendString:value into:json]) + return NO; + + [json appendString:colon]; + if (![self appendValue:[fragment objectForKey:value] into:json]) { + [self addErrorWithCode:EUNSUPPORTED description:[NSString stringWithFormat:@"Unsupported value for key %@ in object", value]]; + return NO; + } + } + + depth--; + if ([self humanReadable] && [fragment count]) + [json appendString:[self indent]]; + [json appendString:@"}"]; + return YES; +} + +- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json { + + [json appendString:@"\""]; + + NSRange esc = [fragment rangeOfCharacterFromSet:kEscapeChars]; + if ( !esc.length ) { + // No special chars -- can just add the raw string: + [json appendString:fragment]; + + } else { + NSUInteger length = [fragment length]; + for (NSUInteger i = 0; i < length; i++) { + unichar uc = [fragment characterAtIndex:i]; + switch (uc) { + case '"': [json appendString:@"\\\""]; break; + case '\\': [json appendString:@"\\\\"]; break; + case '\t': [json appendString:@"\\t"]; break; + case '\n': [json appendString:@"\\n"]; break; + case '\r': [json appendString:@"\\r"]; break; + case '\b': [json appendString:@"\\b"]; break; + case '\f': [json appendString:@"\\f"]; break; + default: + if (uc < 0x20) { + [json appendFormat:@"\\u%04x", uc]; + } else { + CFStringAppendCharacters((CFMutableStringRef)json, &uc, 1); + } + break; + + } + } + } + + [json appendString:@"\""]; + return YES; +} + + +@end diff --git a/MKStoreKit/MKStoreManager.h b/MKStoreKit/MKStoreManager.h new file mode 100755 index 0000000..dd645ae --- /dev/null +++ b/MKStoreKit/MKStoreManager.h @@ -0,0 +1,51 @@ +// +// StoreManager.h +// MKSync +// +// Created by Mugunth Kumar on 17-Oct-09. +// Copyright 2009 MK Inc. All rights reserved. +// mugunthkumar.com + +#import +#import +#import "MKStoreObserver.h" + +@protocol MKStoreKitDelegate +@optional +- (void)productPurchased:(NSString *)productId; +@end + +@interface MKStoreManager : NSObject { + + NSMutableArray *purchasableObjects; + MKStoreObserver *storeObserver; + +} + +@property (nonatomic, retain) NSMutableArray *purchasableObjects; +@property (nonatomic, retain) MKStoreObserver *storeObserver; + +- (void) requestProductData; + +- (BOOL) canCurrentDeviceUseFeature: (NSString*) featureID; +- (void) buyProUpgrade; +- (void) buyFeatureB; // your product ids. This will minimize changes when you change product ids later + +// do not call this directly. This is like a private method +- (void) buyFeature:(NSString*) featureId; + +- (void) failedTransaction: (SKPaymentTransaction *)transaction; +-(void) provideContent: (NSString*) productIdentifier shouldSerialize: (BOOL) serialize; + ++ (MKStoreManager*)sharedManager; + ++ (BOOL) isProUpgraded; ++ (BOOL) featureBPurchased; + ++ (void) needProAlert; + +//DELEGATES ++(id)delegate; ++(void)setDelegate:(id)newDelegate; + +@end diff --git a/MKStoreKit/MKStoreManager.m b/MKStoreKit/MKStoreManager.m new file mode 100755 index 0000000..9827f34 --- /dev/null +++ b/MKStoreKit/MKStoreManager.m @@ -0,0 +1,278 @@ +// +// MKStoreManager.m +// +// Created by Mugunth Kumar on 15-Nov-09. +// Copyright 2009 Mugunth Kumar. All rights reserved. +// mugunthkumar.com +// + +#import "MKStoreManager.h" + + +@implementation MKStoreManager + +@synthesize purchasableObjects; +@synthesize storeObserver; + +static NSString *ownServer = nil; + +// all your features should be managed one and only by StoreManager +//static NSString *featureAId = @"com.designshed.alienblue.proupgrade"; +static NSString *featureAId = @"proupgrade"; +static NSString *featureBId = @""; + +BOOL featureAPurchased; +BOOL featureBPurchased; + +static __weak id _delegate; +static MKStoreManager* _sharedStoreManager; // self + +- (void)dealloc { + + [_sharedStoreManager release]; + [storeObserver release]; + [super dealloc]; +} + ++ (id)delegate { + + return _delegate; +} + ++ (void)setDelegate:(id)newDelegate { + + _delegate = newDelegate; +} + ++ (BOOL) isProUpgraded { + + return featureAPurchased; +} + ++ (BOOL) featureBPurchased { + + return featureBPurchased; +} + ++ (MKStoreManager*)sharedManager +{ + @synchronized(self) { + + if (_sharedStoreManager == nil) { + + [[self alloc] init]; // assignment not done here + _sharedStoreManager.purchasableObjects = [[NSMutableArray alloc] init]; + [_sharedStoreManager requestProductData]; + + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + featureAPurchased = [userDefaults boolForKey:featureAId]; + featureBPurchased = [userDefaults boolForKey:featureBId]; + + _sharedStoreManager.storeObserver = [[MKStoreObserver alloc] init]; + [[SKPaymentQueue defaultQueue] addTransactionObserver:_sharedStoreManager.storeObserver]; + } + } + return _sharedStoreManager; +} + + +#pragma mark Singleton Methods + ++ (id)allocWithZone:(NSZone *)zone + +{ + @synchronized(self) { + + if (_sharedStoreManager == nil) { + + _sharedStoreManager = [super allocWithZone:zone]; + return _sharedStoreManager; // assignment and return on first allocation + } + } + + return nil; //on subsequent allocation attempts return nil +} + + +- (id)copyWithZone:(NSZone *)zone +{ + return self; +} + +- (id)retain +{ + return self; +} + +- (unsigned)retainCount +{ + return UINT_MAX; //denotes an object that cannot be released +} + +- (void)release +{ + //do nothing +} + +- (id)autorelease +{ + return self; +} + + +- (void) requestProductData +{ + SKProductsRequest *request= [[SKProductsRequest alloc] initWithProductIdentifiers: + [NSSet setWithObjects: featureAId, nil]]; + request.delegate = self; + [request start]; +} + + +- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response +{ + [purchasableObjects addObjectsFromArray:response.products]; + // populate UI + for(int i=0;i<[purchasableObjects count];i++) + { + + SKProduct *product = [purchasableObjects objectAtIndex:i]; + NSLog(@"Feature: %@, Cost: %f, ID: %@",[product localizedTitle], + [[product price] doubleValue], [product productIdentifier]); + } + + [request autorelease]; +} + + ++ (void) needProAlert +{ + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"PRO Feature" message:@"You can activate this feature by upgrading to the PRO version in the \"Settings\" panel." + delegate:[[UIApplication sharedApplication] delegate] cancelButtonTitle:@"OK" otherButtonTitles: @"Upgrade Now",nil]; + [alert setTag:99]; + [alert show]; + [alert release]; +} + +- (void) buyFeatureB +{ + [self buyFeature:featureBId]; +} + +- (void) buyFeature:(NSString*) featureId +{ + NSLog(@"-- buy feature in -- for [%@]", featureId); + if([self canCurrentDeviceUseFeature: featureId]) + { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"In-App Purchase" message:@"You can use this feature for this session." + delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil]; + [alert show]; + [alert release]; + + [self provideContent:featureId shouldSerialize:NO]; + return; + } + + if ([SKPaymentQueue canMakePayments]) + { + NSLog(@"can make payments"); + SKPayment *payment = [SKPayment paymentWithProductIdentifier:featureId]; + [[SKPaymentQueue defaultQueue] addPayment:payment]; + } + else + { + NSLog(@"not authorised"); + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Upgrade Warning" message:@"You are not authorized to purchase from AppStore" + delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil]; + [alert show]; + [alert release]; + } +} + +- (BOOL) canCurrentDeviceUseFeature: (NSString*) featureID +{ + NSString *uniqueID = [[UIDevice currentDevice] uniqueIdentifier]; + // check udid and featureid with developer's server + + if(ownServer == nil) return NO; // sanity check + + NSURL *url = [NSURL URLWithString:ownServer]; + + NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url + cachePolicy:NSURLRequestReloadIgnoringCacheData + timeoutInterval:60]; + + [theRequest setHTTPMethod:@"POST"]; + [theRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; + + NSString *postData = [NSString stringWithFormat:@"productid=%@&udid=%@", featureID, uniqueID]; + + NSString *length = [NSString stringWithFormat:@"%d", [postData length]]; + [theRequest setValue:length forHTTPHeaderField:@"Content-Length"]; + + [theRequest setHTTPBody:[postData dataUsingEncoding:NSASCIIStringEncoding]]; + + NSHTTPURLResponse* urlResponse = nil; + NSError *error = [[[NSError alloc] init] autorelease]; + + NSData *responseData = [NSURLConnection sendSynchronousRequest:theRequest + returningResponse:&urlResponse + error:&error]; + + NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSASCIIStringEncoding]; + + BOOL retVal = NO; + if([responseString isEqualToString:@"YES"]) + { + retVal = YES; + } + + [responseString release]; + return retVal; +} + +- (void) buyProUpgrade +{ + [self buyFeature:featureAId]; +} + + +- (void) failedTransaction: (SKPaymentTransaction *)transaction +{ + NSString *messageToBeShown = [NSString stringWithFormat:@"Reason: %@, You can try: %@", [transaction.error localizedFailureReason], [transaction.error localizedRecoverySuggestion]]; + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Unable to complete your purchase" message:messageToBeShown + delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil]; + [alert show]; + [alert release]; +} + +-(void) provideContent: (NSString*) productIdentifier shouldSerialize: (BOOL) serialize +{ + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + if([productIdentifier isEqualToString:featureAId]) + { + featureAPurchased = YES; + if(serialize) + { + if([_delegate respondsToSelector:@selector(productPurchased:)]) + [_delegate productPurchased:productIdentifier]; + + [userDefaults setBool:featureAPurchased forKey:featureAId]; + } + } + + if([productIdentifier isEqualToString:featureBId]) + { + featureBPurchased = YES; + if(serialize) + { + if([_delegate respondsToSelector:@selector(productPurchased:)]) + [_delegate productPurchased:productIdentifier]; + + [userDefaults setBool:featureBPurchased forKey:featureBId]; + } + } +} + + +@end diff --git a/MKStoreKit/MKStoreObserver.h b/MKStoreKit/MKStoreObserver.h new file mode 100755 index 0000000..e8c9935 --- /dev/null +++ b/MKStoreKit/MKStoreObserver.h @@ -0,0 +1,21 @@ +// +// MKStoreObserver.m +// +// Created by Mugunth Kumar on 17-Oct-09. +// Copyright 2009 Mugunth Kumar. All rights reserved. +// + +#import +#import + +@interface MKStoreObserver : NSObject { + + +} + +- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions; +- (void) failedTransaction: (SKPaymentTransaction *)transaction; +- (void) completeTransaction: (SKPaymentTransaction *)transaction; +- (void) restoreTransaction: (SKPaymentTransaction *)transaction; + +@end diff --git a/MKStoreKit/MKStoreObserver.m b/MKStoreKit/MKStoreObserver.m new file mode 100755 index 0000000..b992fc1 --- /dev/null +++ b/MKStoreKit/MKStoreObserver.m @@ -0,0 +1,93 @@ +// +// MKStoreObserver.m +// +// Created by Mugunth Kumar on 17-Oct-09. +// Copyright 2009 Mugunth Kumar. All rights reserved. +// + +#import "MKStoreObserver.h" +#import "MKStoreManager.h" +#import "AlienBlueAppDelegate.h" + +@implementation MKStoreObserver + +- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions +{ + NSLog(@"paymentQueue in()"); + for (SKPaymentTransaction *transaction in transactions) + { + switch (transaction.transactionState) + { + case SKPaymentTransactionStatePurchased: + + [self completeTransaction:transaction]; + + break; + + case SKPaymentTransactionStateFailed: + + [self failedTransaction:transaction]; + + break; + + case SKPaymentTransactionStateRestored: + + [self restoreTransaction:transaction]; + + default: + + break; + } + } +} + +- (void) upgradeError +{ + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Unable to complete your purchase" message:@"Please check your connection and iTunes username and password before trying again." + delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; + [alert setTag:55]; + [alert show]; + [alert release]; + [(AlienBlueAppDelegate *) [[UIApplication sharedApplication] delegate] stopPurchaseIndicator]; +} + + +- (void) failedTransaction: (SKPaymentTransaction *)transaction +{ + NSLog(@"failedTransaction in()"); + if (transaction.error.code != SKErrorPaymentCancelled) + { + + if (transaction.error.code == SKErrorClientInvalid) + NSLog(@"client invalid"); + else if (transaction.error.code == SKErrorPaymentCancelled) + NSLog(@"payment cancelled"); + else if (transaction.error.code == SKErrorPaymentInvalid) + NSLog(@"payment invalid"); + else if (transaction.error.code == SKErrorPaymentNotAllowed) + NSLog(@"payment not allowed"); + else if (transaction.error.code == SKErrorUnknown) + NSLog(@"unknown error"); + // Optionally, display an error here. + } + [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; + [self upgradeError]; +} + +- (void) completeTransaction: (SKPaymentTransaction *)transaction +{ + NSLog(@"completeTransaction in()"); + [[MKStoreManager sharedManager] provideContent: transaction.payment.productIdentifier shouldSerialize:YES]; + [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; + [(AlienBlueAppDelegate *) [[UIApplication sharedApplication] delegate] proVersionUpgraded]; + +} + +- (void) restoreTransaction: (SKPaymentTransaction *)transaction +{ + NSLog(@"restoreTransaction in()"); + [[MKStoreManager sharedManager] provideContent: transaction.originalTransaction.payment.productIdentifier shouldSerialize:YES]; + [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; +} + +@end diff --git a/MKStoreKit/Server Code/AddDevice.html b/MKStoreKit/Server Code/AddDevice.html new file mode 100755 index 0000000..b0bfca7 --- /dev/null +++ b/MKStoreKit/Server Code/AddDevice.html @@ -0,0 +1,7 @@ +
+

Product:

+

Your UDID:

+

EMail:

+

Message (if you haven't typed in who you are, please type some intro about you, your blog so that, it easy for the user to approve your device):

+

+
\ No newline at end of file diff --git a/MKStoreKit/Server Code/DBSetup.sql b/MKStoreKit/Server Code/DBSetup.sql new file mode 100755 index 0000000..7bd24a5 --- /dev/null +++ b/MKStoreKit/Server Code/DBSetup.sql @@ -0,0 +1,41 @@ +-- phpMyAdmin SQL Dump +-- version 2.11.9.5 +-- http://www.phpmyadmin.net +-- +-- Host: localhost +-- Generation Time: Nov 15, 2009 at 08:38 AM +-- Server version: 5.0.81 +-- PHP Version: 5.2.9 + +SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; + +-- +-- Database: `mugunth1_udid` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `products` +-- + +CREATE TABLE IF NOT EXISTS `products` ( + `productid` varchar(255) NOT NULL, + `productName` varchar(30) NOT NULL, + `productDesc` varchar(255) NOT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `requests` +-- + +CREATE TABLE IF NOT EXISTS `requests` ( + `udid` varchar(40) NOT NULL, + `productid` varchar(100) NOT NULL, + `email` varchar(100) default NULL, + `message` varchar(1000) default NULL, + `status` tinyint(1) NOT NULL default '0', + `lastUpdated` timestamp NOT NULL default CURRENT_TIMESTAMP +) ENGINE=MyISAM DEFAULT CHARSET=latin1; diff --git a/MKStoreKit/Server Code/featureCheck.php b/MKStoreKit/Server Code/featureCheck.php new file mode 100755 index 0000000..d3b8f45 --- /dev/null +++ b/MKStoreKit/Server Code/featureCheck.php @@ -0,0 +1,32 @@ + diff --git a/MKStoreKit/Server Code/requestAccess.php b/MKStoreKit/Server Code/requestAccess.php new file mode 100755 index 0000000..1b3d75a --- /dev/null +++ b/MKStoreKit/Server Code/requestAccess.php @@ -0,0 +1,33 @@ + + diff --git a/MKStoreKit/readme.txt b/MKStoreKit/readme.txt new file mode 100755 index 0000000..e28d796 --- /dev/null +++ b/MKStoreKit/readme.txt @@ -0,0 +1,34 @@ +This is version 2.0 of MKStoreKit + +The source code, MKStoreKit, contains four objective cfiles. MKStoreManager.h/m and MKStoreObserver.h/m and four server side files. The MKStoreManager is a singleton class that takes care of *everything*. Just include StoreKit framework into your product and drag these four files into the project. You then have to initialize it by calling [MKStoreManager sharedManager] in your applicationDidFinishLaunching. From then on, it does the magic. The MKStoreKit automatically activates/deactivates features based on your userDefaults. When a feature is purchased, it automatically records it into NSUserDefaults. For checking whether the user has purchased the feature, you can call a function like, + +if([MKStoreManager featureAPurchased]) +{ +//unlock it +} + +To purchase a feature, just call + +[[MKStoreManager sharedManager] buyFeatureA]; + +It’s that simple with my MKStoreKit. As always, all my source code can be used royalty-free into your app. Just make sure that you don’t remove the copyright notice from the source code if you make your app open source. You don’t have to attribute me in your app, although I would be glad if you do so. + +Best Practices: + +Rename featureAPurchased/buyFeatureA to meaningful functions as per your application. Manage all your product ids without MKStoreManager.m and call these functions. This is to ensure that you don't litter your product ids through out your app. + +What's new in Version 2 + +In Version 2, support for pinging the developer server for checking review requests is added. If you want to use this feature, you have to fill in "ownServer" variable to the location where you copy the server file featureCheck.php + +The database required can be created from the sql file attached. + +The code that you need for setting up your server is present in the ServerCode folder. + +Copy all the files to some location like +http://api.mycompany.com/inapp/ + +The URL which you should copy to "ownServer" variable in MKStoreManager.m is http://api.mycompany.com/inapp/featureCheck.php +Copy this URL to ownServer parameter in MKStoreManager.m + +It should all work. If it doesn't, hire me for debugging it! :) \ No newline at end of file diff --git a/Xibs/BrowserView.xib b/Xibs/BrowserView.xib new file mode 100755 index 0000000..f8725c4 --- /dev/null +++ b/Xibs/BrowserView.xib @@ -0,0 +1,271 @@ + + + + 768 + 9L31a + 680 + 949.54 + 353.00 + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + + 274 + + YES + + + 274 + {320, 436} + + + 1 + MSAxIDEAA + + YES + YES + YES + 3 + YES + + + + 289 + {{290, 9}, {20, 20}} + + NO + NO + NO + YES + 2 + + + {320, 436} + + + 3 + MQA + + 2 + + + NO + 1 + + NO + + + + + + YES + + + view + + + + 35 + + + + webView + + + + 47 + + + + delegate + + + + 48 + + + + loadingIndicator + + + + 50 + + + + + YES + + 0 + + YES + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + -2 + + + + + 33 + + + YES + + + + + + + 40 + + + + + 49 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 33.IBEditorWindowLastContentRect + 33.IBPluginDependency + 40.IBPluginDependency + 49.IBPluginDependency + + + YES + BrowserViewController + UIResponder + {{432, 276}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 76 + + + + YES + + BrowserViewController + UIViewController + + YES + + YES + goBack: + readability: + + + YES + id + id + + + + YES + + YES + backButton + loadingIndicator + navTitle + navbar + readabilityButton + webView + + + YES + UIBarButtonItem + UIActivityIndicatorView + UINavigationItem + UINavigationBar + UIBarButtonItem + UIWebView + + + + IBProjectSource + Classes/BrowserViewController.h + + + + NSObject + + IBProjectSource + JSON/NSObject+SBJSON.h + + + + NSObject + + IBProjectSource + JSON/SBJsonWriter.h + + + + + 0 + ../AlienBlue.xcodeproj + 3 + 3.1 + + diff --git a/Xibs/CommentsView.xib b/Xibs/CommentsView.xib new file mode 100755 index 0000000..377b645 --- /dev/null +++ b/Xibs/CommentsView.xib @@ -0,0 +1,182 @@ + + + + 768 + 9L31a + 677 + 949.54 + 353.00 + + YES + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + + 274 + {320, 416} + + 3 + MSAwAA + + NO + YES + NO + + 2 + + + NO + + NO + 0 + YES + YES + 4.400000e+01 + 2.200000e+01 + 2.200000e+01 + + + + + YES + + + view + + + + 21 + + + + delegate + + + + 32 + + + + + YES + + 0 + + YES + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + -2 + + + + + 20 + + + YES + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 20.IBEditorWindowLastContentRect + 20.IBPluginDependency + + + YES + CommentsTableViewController + UIResponder + {{1552, 924}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 39 + + + + YES + + CommentsTableViewController + UITableViewController + + IBProjectSource + Classes/CommentsTableViewController.h + + + + NSObject + + IBProjectSource + JSON/NSObject+SBJSON.h + + + + NSObject + + IBProjectSource + JSON/SBJsonWriter.h + + + + + 0 + ../NeuReddit.xcodeproj + 3 + 3.0 + + diff --git a/Xibs/CustomMessageCell.xib b/Xibs/CustomMessageCell.xib new file mode 100755 index 0000000..19f2d2b --- /dev/null +++ b/Xibs/CustomMessageCell.xib @@ -0,0 +1,634 @@ + + + + 768 + 9L31a + 680 + 949.54 + 353.00 + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + + 274 + + YES + + + 256 + + YES + + + 274 + + YES + + + -2147483382 + {{20, 182}, {280, 33}} + + NO + YES + NO + 1 + 0 + 0 + + Helvetica-Bold + 1.200000e+01 + 16 + + 1 + + 3 + MQA + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + 3 + MC41AA + + + + + 306 + {{14, 38}, {296, 178}} + + NO + NO + 2 + YES + NO + NO + NO + NO + NO + NO + NO + NO + NO + Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. + + 1 + MC45NzgyNjA4NyAwLjk3ODI2MDg3IDAuOTc4MjYwODcAA + + + Helvetica + 1.700000e+01 + 16 + + + 2 + + 2 + + + + -2147483382 + + YES + + + 274 + {{10, 9}, {279, 70}} + + + NO + YES + YES + NO + NO + NO + NO + Your reply... + + 1 + MC4yNTEzMDg2NSAwLjQ1NjcyODc2IDAuNjE0MTMwNDQgMC45OTAwMDAwMQA + + + Helvetica + 1.500000e+01 + 16 + + + 2 + 2 + 9 + + + + + 297 + {{217, 91}, {72, 25}} + + NO + NO + 0 + 0 + + Helvetica-Bold + 1.300000e+01 + 16 + + 1 + Submit + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + + + + 300 + {{15, 91}, {72, 25}} + + NO + NO + 0 + 0 + + 1 + Cancel + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + + + {{9, 95}, {300, 131}} + + + 1 + MC4zMjQ2OTk2NyAwLjU5MDEwOTcxIDAuNzkzNDc4MjUgMC4xNwA + + NO + 1 + + + + 290 + {{-16, -1}, {350, 41}} + + NO + NO + NO + + NSImage + button-background-dark.png + + + + + 292 + {{86, 10}, {129, 21}} + + NO + YES + NO + Label + + Helvetica-Bold + 1.400000e+01 + 16 + + + + 1 + 1.000000e+01 + + + + 292 + {{20, 10}, {58, 21}} + + NO + YES + NO + From : + + + 1 + MC45ODE3NTE4IDAuNjYyNzQ1MTIgMC4yMTk2MDc4NAA + + + 1 + 1.000000e+01 + + + + 289 + {{271, 6}, {35, 27}} + + NO + NO + 0 + 0 + + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + + NSImage + reply.png + + + + + 289 + {{230, 10}, {27, 23}} + + NO + NO + 0 + 0 + + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + + NSImage + comment-parent-icon.png + + + + {320, 282} + + + 1 + MC4xMTc4Njk0NCAwLjE4OTk5MzE3IDAuMzIwNjUyMTkgMC4wMzk5OTk5OTkAA + + YES + NO + 1 + + + {320, 248} + + + 3 + MCAwAA + + NO + YES + 4 + YES + + + {320, 248} + + + 1 + MSAxIDEgMAA + + YES + NO + 0 + 1 + 1.000000e+02 + + MessageCell + + + + + YES + + + body + + + + 38 + + + + submitReply + + + + 70 + + + + replyTextView + + + + 73 + + + + cancelReply + + + + 75 + + + + author + + + + 82 + + + + showReplyAreaButton + + + + 83 + + + + viewForMessageReply + + + + 84 + + + + showContextButton + + + + 87 + + + + + YES + + 0 + + YES + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + -2 + + + + + 3 + + + YES + + + + + + 6 + + + YES + + + + + + + + + + + + + 53 + + + + + 64 + + + YES + + + + + + Reply Section + + + 32 + + + + + 66 + + + + + 67 + + + + + 74 + + + + + 78 + + + + + 79 + + + + + 80 + + + + + 81 + + + + + 86 + + + + + + + YES + + YES + -2.CustomClassName + 3.CustomClassName + 3.IBEditorWindowLastContentRect + 3.IBPluginDependency + 32.IBPluginDependency + 53.IBPluginDependency + 6.IBPluginDependency + 64.IBPluginDependency + 64.IBUserGuides + 66.IBPluginDependency + 67.IBPluginDependency + 74.IBPluginDependency + 78.IBPluginDependency + 79.IBPluginDependency + 80.IBPluginDependency + 81.IBPluginDependency + 86.IBPluginDependency + + + YES + UIResponder + MessageCell + {{564, 243}, {320, 248}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + YES + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 87 + + + + YES + + MessageCell + UITableViewCell + + YES + + YES + author + body + cancelReply + datetime + replyTextView + showContextButton + showReplyAreaButton + submitReply + viewForMessageReply + + + YES + UILabel + UITextView + UIButton + UILabel + UITextView + UIButton + UIButton + UIButton + UIView + + + + IBProjectSource + Classes/MessageCell.h + + + + NSObject + + IBProjectSource + JSON/NSObject+SBJSON.h + + + + NSObject + + IBProjectSource + JSON/SBJsonWriter.h + + + + + 0 + AlienBlue.xcodeproj + 3 + 3.1 + + diff --git a/Xibs/MainWindow.xib b/Xibs/MainWindow.xib new file mode 100755 index 0000000..5fe2cb4 --- /dev/null +++ b/Xibs/MainWindow.xib @@ -0,0 +1,608 @@ + + + + 768 + 9L31a + 680 + 949.54 + 353.00 + + YES + + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + + + 1316 + + YES + + + 1298 + {320, 480} + + NO + NO + NO + + NSImage + DefaultBG.png + + + + + -2147482332 + {320, 480} + + NO + NO + 4 + NO + + NSImage + DefaultBGBlack.png + + + + + {320, 480} + + + 1 + MC4xMTk4NjcyMSAwLjE5MzIxMzM5IDAuMzI2MDg2OTQgMAA + + NO + NO + + 2 + + YES + + + + + 1 + NO + + + 2 + + + NO + + Posts + + NSImage + posts-icon.png + + + + + + + + 256 + {0, 0} + NO + NO + YES + YES + 1 + + + + 256 + {{0, 416}, {320, 44}} + NO + YES + 4 + YES + + + YES + + Posts + + Posts + + + YES + + + + Posts + + + PostsView + + + + + YES + + + Messages + + 1 + Messages + + NSImage + mail-red.png + + + + + YES + + + MessagesView + + + Settings + + 3 + Settings + + NSImage + gear-icon.png + + + + + SettingsView + + + + + 266 + {{129, 330}, {163, 49}} + + 1 + MC4xMjc4NTgzNiAwLjIwNjA5NDI2IDAuMzQ3ODI2MDYgMAA + + NO + + + + + + YES + + + window + + + + 9 + + + + delegate + + + + 99 + + + + tabBarController + + + + 113 + + + + postsNavigation + + + + 273 + + + + blackBG + + + + 276 + + + + + YES + + 0 + + YES + + + + + + 2 + + + YES + + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + 3 + + + + + 106 + + + YES + + + + + + + + + 107 + + + + + 109 + + + YES + + + + + + 110 + + + + + -2 + + + + + 124 + + + YES + + + + + + 125 + + + + + 248 + + + + + 266 + + + YES + + + + + + + + + 268 + + + + + 270 + + + + + 246 + + + YES + + + + + + + 271 + + + + + 247 + + + + + 274 + + + + + 275 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 106.CustomClassName + 106.IBEditorWindowLastContentRect + 106.IBPluginDependency + 107.IBPluginDependency + 109.CustomClassName + 109.IBPluginDependency + 110.IBPluginDependency + 124.CustomClassName + 2.IBAttributePlaceholdersKey + 2.IBEditorWindowLastContentRect + 2.IBPluginDependency + 246.CustomClassName + 248.IBEditorWindowLastContentRect + 248.IBPluginDependency + 266.IBPluginDependency + 268.IBPluginDependency + 275.IBPluginDependency + 3.CustomClassName + 3.IBPluginDependency + + + YES + UIApplication + UIResponder + NavigationController + {{234, 225}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + SettingsTableViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + MessagesTableViewController + + YES + + YES + + + YES + + + {{561, 197}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + PostsTableControllerView + {{84, 1314}, {240, 128}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + AlienBlueAppDelegate + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 276 + + + + YES + + AlienBlueAppDelegate + NSObject + + checkForNewMessages: + id + + + YES + + YES + blackBG + tabBarController + window + + + YES + UIImageView + NavigationController + UIWindow + + + + IBProjectSource + Classes/AlienBlueAppDelegate.h + + + + MessagesTableViewController + UITableViewController + + IBProjectSource + MessagesTableViewController.h + + + + NSObject + + IBProjectSource + JSON/NSObject+SBJSON.h + + + + NSObject + + IBProjectSource + JSON/SBJsonWriter.h + + + + NavigationController + UITabBarController + + voteSegmentChanged: + id + + + YES + + YES + postsNavigation + proFeaturesSplash + + + YES + UINavigationController + UIView + + + + IBProjectSource + NavigationController.h + + + + PostsTableControllerView + UITableViewController + + YES + + YES + cancelSubredditSelect: + chooseSubredditButton: + loadPosts: + + + YES + id + id + id + + + + YES + + YES + segmentControl + subredditDoneButton + subredditManualEnterView + subredditManualText + subredditPickView + subredditScrollingPickerView + + + YES + UISegmentedControl + UIButton + UIView + UITextField + UIView + UIPickerView + + + + IBProjectSource + PostsTableControllerView.h + + + + SettingsTableViewController + UITableViewController + + authorise: + id + + + YES + + YES + cookieLabel + modLabel + + + YES + UILabel + UILabel + + + + IBProjectSource + Classes/SettingsTableViewController.h + + + + + 0 + AlienBlue.xcodeproj + 3 + 3.1 + + diff --git a/Xibs/MessagesView.xib b/Xibs/MessagesView.xib new file mode 100755 index 0000000..0df9eb9 --- /dev/null +++ b/Xibs/MessagesView.xib @@ -0,0 +1,199 @@ + + + + 768 + 9L31a + 680 + 949.54 + 353.00 + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + + 274 + {320, 436} + + + 1 + MC4xMzk4NDUxIDAuMjI1NDE1NjUgMC4zODA0MzQ4MSAwAA + + NO + YES + NO + NO + 0 + YES + YES + 4.400000e+01 + 2.200000e+01 + 2.200000e+01 + + + + 292 + {320, 480} + + 1 + MSAxIDEAA + + NO + NO + + + + + + YES + + + view + + + + 21 + + + + delegate + + + + 32 + + + + + YES + + 0 + + YES + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + -2 + + + + + 20 + + + YES + + + + + 46 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 20.IBEditorWindowLastContentRect + 20.IBPluginDependency + 46.IBEditorWindowLastContentRect + 46.IBPluginDependency + + + YES + MessagesTableViewController + UIResponder + {{1240, 320}, {320, 436}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + {{955, 945}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 62 + + + + YES + + MessagesTableViewController + UITableViewController + + IBProjectSource + MessagesTableViewController.h + + + + NSObject + + IBProjectSource + JSON/NSObject+SBJSON.h + + + + NSObject + + IBProjectSource + JSON/SBJsonWriter.h + + + + + 0 + ../NeuReddit.xcodeproj + 3 + 3.1 + + diff --git a/Xibs/PostsView.xib b/Xibs/PostsView.xib new file mode 100755 index 0000000..6da0a96 --- /dev/null +++ b/Xibs/PostsView.xib @@ -0,0 +1,609 @@ + + + + 768 + 9L31a + 680 + 949.54 + 353.00 + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + + 274 + {320, 436} + + + 1 + MC4xMzk4NDUxIDAuMjI1NDE1NjUgMC4zODA0MzQ4MSAwAA + + NO + YES + NO + NO + 0 + YES + YES + 4.400000e+01 + 2.200000e+01 + 2.200000e+01 + + + + 274 + + YES + + + 290 + {320, 44} + + NO + NO + + YES + + + Choose a Subreddit + + Cancel + 1 + + + + + + + 288 + {{0, 44}, {320, 216}} + + NO + YES + 1 + YES + YES + + + + 293 + {{94, 362}, {132, 37}} + + NO + NO + 0 + 0 + + Helvetica-Bold + 1.500000e+01 + 16 + + 1 + View Posts + + 3 + MQA + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + 3 + MC41AA + + + + + -2147483355 + + YES + + + 256 + {{148, 6}, {112, 31}} + + NO + NO + 0 + + 3 + + 3 + MAA + + 2 + + + + Helvetica + 1.200000e+01 + 16 + + YES + YES + 1.700000e+01 + + 1 + 9 + + + + + 256 + {{20, 10}, {148, 21}} + + NO + YES + NO + http://reddit.com/r/ + + Helvetica + 1.400000e+01 + 16 + + + 1 + MSAxIDEAA + + + 1 + 1.000000e+01 + + + {{20, 287}, {280, 44}} + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + NO + + + {320, 664} + + 1 + MC4wODU5MDQ4MjkgMC4xMzg0Njk1OCAwLjIzMzY5NTYzAA + + NO + 1 + + + + 292 + {320, 480} + + NO + NO + + + + + 303 + {320, 30} + NO + NO + 0 + 0 + 2 + 5 + 0 + + YES + Front + Subreddit + New + Saved + Hidden + + + YES + + + + + + + + YES + + + + + + + + YES + {0, 0} + {0, 0} + {0, 0} + {0, 0} + {0, 0} + + + YES + + + + + + + YES + + 1 + MC4wMjA2MDkwMDEgMC4xNjg5OTA3OSAwLjI1NTQzNDgxAA + + + + + + YES + + + view + + + + 21 + + + + delegate + + + + 32 + + + + segmentControl + + + + 35 + + + + subredditPickView + + + + 48 + + + + delegate + + + + 52 + + + + delegate + + + + 53 + + + + subredditManualEnterView + + + + 55 + + + + subredditScrollingPickerView + + + + 57 + + + + subredditManualText + + + + 58 + + + + chooseSubredditButton: + + + 7 + + 60 + + + + cancelSubredditSelect: + + + + 61 + + + + subredditDoneButton + + + + 62 + + + + + YES + + 0 + + YES + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + -2 + + + + + 20 + + + YES + + + + + 37 + + + YES + + + + + + + SubredditPickView + + + 39 + + + YES + + + + + + 40 + + + YES + + + + + + 42 + + + + + 46 + + + + + 47 + + + YES + + + + + ManualRedditEnter + + + 44 + + + + + 49 + + + + + 54 + + + + + 43 + + + + + 33 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 20.IBEditorWindowLastContentRect + 20.IBPluginDependency + 33.IBPluginDependency + 37.IBEditorWindowLastContentRect + 37.IBPluginDependency + 37.IBUserGuides + 39.IBPluginDependency + 40.IBPluginDependency + 42.IBPluginDependency + 43.IBPluginDependency + 44.IBPluginDependency + 46.IBEditorWindowLastContentRect + 46.IBPluginDependency + 47.IBPluginDependency + 49.IBPluginDependency + 54.IBPluginDependency + + + YES + PostsTableControllerView + UIResponder + {{392, 244}, {320, 436}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + {{463, 92}, {320, 664}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + YES + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + {{955, 945}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 62 + + + + YES + + NSObject + + IBProjectSource + JSON/NSObject+SBJSON.h + + + + NSObject + + IBProjectSource + JSON/SBJsonWriter.h + + + + PostsTableControllerView + UITableViewController + + YES + + YES + cancelSubredditSelect: + chooseSubredditButton: + fetchPosts: + loadPosts: + + + YES + id + id + id + id + + + + YES + + YES + segmentControl + subredditDoneButton + subredditManualEnterView + subredditManualText + subredditPickView + subredditScrollingPickerView + + + YES + UISegmentedControl + UIButton + UIView + UITextField + UIView + UIPickerView + + + + IBProjectSource + PostsTableControllerView.h + + + + + 0 + ../AlienBlue.xcodeproj + 3 + 3.1 + + diff --git a/Xibs/SettingsView.xib b/Xibs/SettingsView.xib new file mode 100755 index 0000000..300c83a --- /dev/null +++ b/Xibs/SettingsView.xib @@ -0,0 +1,215 @@ + + + + 768 + 9L31a + 680 + 949.54 + 353.00 + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + + 274 + {320, 436} + + NO + YES + NO + NO + 1 + 0 + YES + YES + 4.400000e+01 + 1.000000e+01 + 1.000000e+01 + + + + 292 + {320, 480} + + 1 + MSAxIDEAA + + NO + NO + + + + + + YES + + + delegate + + + + 32 + + + + view + + + + 63 + + + + + YES + + 0 + + YES + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + -2 + + + + + 20 + + + YES + + + + + 46 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 20.IBEditorWindowLastContentRect + 20.IBPluginDependency + 46.IBEditorWindowLastContentRect + 46.IBPluginDependency + + + YES + SettingsTableViewController + UIResponder + {{381, 275}, {320, 436}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + {{955, 945}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 66 + + + + YES + + NSObject + + IBProjectSource + JSON/NSObject+SBJSON.h + + + + NSObject + + IBProjectSource + JSON/SBJsonWriter.h + + + + SettingsTableViewController + UITableViewController + + authorise: + id + + + YES + + YES + cookieLabel + modLabel + proFeaturesSplash + + + YES + UILabel + UILabel + UIView + + + + IBProjectSource + Classes/SettingsTableViewController.h + + + + + 0 + ../AlienBlue.xcodeproj + 3 + 3.1 + + diff --git a/main.m b/main.m new file mode 100755 index 0000000..ba99fb7 --- /dev/null +++ b/main.m @@ -0,0 +1,18 @@ +// +// main.m +// Alien Blue +// +// Created by Jason Morrissey on 28/03/10. +// Copyright The Design Shed 2010. All rights reserved. +// + +#import + +int main(int argc, char *argv[]) { + + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + int retVal = UIApplicationMain(argc, argv, nil, nil); + [pool release]; + return retVal; +} +