diff --git a/Baker.xcodeproj/project.pbxproj b/Baker.xcodeproj/project.pbxproj index cf4f9133..e91fe759 100644 --- a/Baker.xcodeproj/project.pbxproj +++ b/Baker.xcodeproj/project.pbxproj @@ -8,114 +8,111 @@ /* Begin PBXBuildFile section */ 20CC551D16AD346900DEFB5C /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 20CC551F16AD346900DEFB5C /* Localizable.strings */; }; + 904033901A10FF230035071A /* BKRShelfViewLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 9040338F1A10FF230035071A /* BKRShelfViewLayout.m */; }; + 904C31E71A12645C00E53E66 /* BKRCategoryFilterItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 904C31E61A12645C00E53E66 /* BKRCategoryFilterItem.m */; }; 9F55576815B4C6C3003072A3 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F55576515B4C6B7003072A3 /* QuartzCore.framework */; }; - 9F58D85115D66E870027902C /* UICustomNavigationBar.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F58D84E15D66E870027902C /* UICustomNavigationBar.m */; }; - 9F58D85215D66E870027902C /* UICustomNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F58D85015D66E870027902C /* UICustomNavigationController.m */; }; - 9F5EB3BD15FCA3A100E13796 /* NSString+Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F5EB3BC15FCA3A100E13796 /* NSString+Extensions.m */; }; - 9F8FC1C415C55E8C0065F0A3 /* BakerBook.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F8FC1C315C55E8C0065F0A3 /* BakerBook.m */; }; - 9FAB40DA162B13CF0088E9C1 /* UIColor+Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FAB40D9162B13CF0088E9C1 /* UIColor+Extensions.m */; }; - 9FF795A715B4C57E00899E51 /* ShelfViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FF795A615B4C57E00899E51 /* ShelfViewController.m */; }; - AB2317D4169F808000BE3BE7 /* PurchasesManager.m in Sources */ = {isa = PBXBuildFile; fileRef = AB2317D3169F808000BE3BE7 /* PurchasesManager.m */; }; - AB246A3116C5BC6E00715E25 /* NSMutableURLRequest+WebServiceClient.m in Sources */ = {isa = PBXBuildFile; fileRef = AB246A3016C5BC6E00715E25 /* NSMutableURLRequest+WebServiceClient.m */; }; - AB26005116011D9D00E9AA66 /* BakerBookStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = AB26005016011D9D00E9AA66 /* BakerBookStatus.m */; }; - AB40045315B4B83000D87E2A /* BakerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AB40042B15B4B83000D87E2A /* BakerViewController.m */; }; - AB40045F15B4B83000D87E2A /* IndexViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AB40043415B4B83000D87E2A /* IndexViewController.m */; }; - AB40046115B4B83000D87E2A /* InterceptorWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = AB40043615B4B83000D87E2A /* InterceptorWindow.m */; }; + 9F58D85115D66E870027902C /* BKRCustomNavigationBar.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F58D84E15D66E870027902C /* BKRCustomNavigationBar.m */; }; + 9F58D85215D66E870027902C /* BKRCustomNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F58D85015D66E870027902C /* BKRCustomNavigationController.m */; }; + 9F8FC1C415C55E8C0065F0A3 /* BKRBook.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F8FC1C315C55E8C0065F0A3 /* BKRBook.m */; }; + 9FF795A715B4C57E00899E51 /* BKRShelfViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FF795A615B4C57E00899E51 /* BKRShelfViewController.m */; }; + AB2317D4169F808000BE3BE7 /* BKRPurchasesManager.m in Sources */ = {isa = PBXBuildFile; fileRef = AB2317D3169F808000BE3BE7 /* BKRPurchasesManager.m */; }; + AB246A3116C5BC6E00715E25 /* NSMutableURLRequest+BakerExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = AB246A3016C5BC6E00715E25 /* NSMutableURLRequest+BakerExtensions.m */; }; + AB26005116011D9D00E9AA66 /* BKRBookStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = AB26005016011D9D00E9AA66 /* BKRBookStatus.m */; }; + AB40045315B4B83000D87E2A /* BKRBookViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AB40042B15B4B83000D87E2A /* BKRBookViewController.m */; }; + AB40045F15B4B83000D87E2A /* BKRIndexViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AB40043415B4B83000D87E2A /* BKRIndexViewController.m */; }; + AB40046115B4B83000D87E2A /* BKRInterceptorWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = AB40043615B4B83000D87E2A /* BKRInterceptorWindow.m */; }; AB40046315B4B83000D87E2A /* GTMNSString+HTML.m in Sources */ = {isa = PBXBuildFile; fileRef = AB40043A15B4B83000D87E2A /* GTMNSString+HTML.m */; }; - AB40046715B4B83000D87E2A /* ioapi.c in Sources */ = {isa = PBXBuildFile; fileRef = AB40044015B4B83000D87E2A /* ioapi.c */; }; - AB40046915B4B83000D87E2A /* mztools.c in Sources */ = {isa = PBXBuildFile; fileRef = AB40044215B4B83000D87E2A /* mztools.c */; }; - AB40046B15B4B83000D87E2A /* unzip.c in Sources */ = {isa = PBXBuildFile; fileRef = AB40044415B4B83000D87E2A /* unzip.c */; }; - AB40046D15B4B83000D87E2A /* zip.c in Sources */ = {isa = PBXBuildFile; fileRef = AB40044615B4B83000D87E2A /* zip.c */; }; - AB40046F15B4B83000D87E2A /* SSZipArchive.m in Sources */ = {isa = PBXBuildFile; fileRef = AB40044915B4B83000D87E2A /* SSZipArchive.m */; }; - AB40047115B4B83000D87E2A /* Utils.m in Sources */ = {isa = PBXBuildFile; fileRef = AB40044B15B4B83000D87E2A /* Utils.m */; }; - AB40047315B4B83000D87E2A /* ModalViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AB40044D15B4B83000D87E2A /* ModalViewController.m */; }; - AB40047715B4B83000D87E2A /* PageTitleLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = AB40045215B4B83000D87E2A /* PageTitleLabel.m */; }; + AB40047115B4B83000D87E2A /* BKRUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = AB40044B15B4B83000D87E2A /* BKRUtils.m */; }; + AB40047315B4B83000D87E2A /* BKRModalWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AB40044D15B4B83000D87E2A /* BKRModalWebViewController.m */; }; + AB40047715B4B83000D87E2A /* BKRPageTitleLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = AB40045215B4B83000D87E2A /* BKRPageTitleLabel.m */; }; AB40048115B4B90200D87E2A /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB40048015B4B90200D87E2A /* MessageUI.framework */; }; AB40048315B4B90A00D87E2A /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = AB40048215B4B90A00D87E2A /* libz.dylib */; }; AB40048515B4BB8B00D87E2A /* books in Resources */ = {isa = PBXBuildFile; fileRef = AB40048415B4BB8B00D87E2A /* books */; }; AB5D48D01609178900828D16 /* NewsstandKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB5D48CD1609176A00828D16 /* NewsstandKit.framework */; }; - AB67F5BA16EE99B2005A04F6 /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = AB67F5B916EE99B2005A04F6 /* Reachability.m */; }; + AB67F5BA16EE99B2005A04F6 /* BKRReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = AB67F5B916EE99B2005A04F6 /* BKRReachability.m */; }; AB67F5BC16EE9B79005A04F6 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB67F5BB16EE9B79005A04F6 /* SystemConfiguration.framework */; }; ABABF1A81646C06100A75E67 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ABABF1A51646C04D00A75E67 /* StoreKit.framework */; }; - ABADF3F916C7BAD900F52F3C /* NSURL+Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = ABADF3F816C7BAD900F52F3C /* NSURL+Extensions.m */; }; + ABADF3F916C7BAD900F52F3C /* NSURL+BakerExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = ABADF3F816C7BAD900F52F3C /* NSURL+BakerExtensions.m */; }; ABAFB58C15AB9A2E002FA498 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ABAFB58B15AB9A2E002FA498 /* UIKit.framework */; }; ABAFB58E15AB9A2E002FA498 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ABAFB58D15AB9A2E002FA498 /* Foundation.framework */; }; ABAFB59015AB9A2E002FA498 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ABAFB58F15AB9A2E002FA498 /* CoreGraphics.framework */; }; ABAFB59815AB9A2E002FA498 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = ABAFB59715AB9A2E002FA498 /* main.m */; }; - ABAFB59C15AB9A2E002FA498 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = ABAFB59B15AB9A2E002FA498 /* AppDelegate.m */; }; - ABBFD8F216C6614300BA64A4 /* NSString+UUID.m in Sources */ = {isa = PBXBuildFile; fileRef = ABBFD8F116C6614300BA64A4 /* NSString+UUID.m */; }; - ABC270D11616F3CB00AD97E9 /* IssuesManager.m in Sources */ = {isa = PBXBuildFile; fileRef = ABC270D01616F3CB00AD97E9 /* IssuesManager.m */; }; + ABAFB59C15AB9A2E002FA498 /* BKRAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = ABAFB59B15AB9A2E002FA498 /* BKRAppDelegate.m */; }; + ABC270D11616F3CB00AD97E9 /* BKRIssuesManager.m in Sources */ = {isa = PBXBuildFile; fileRef = ABC270D01616F3CB00AD97E9 /* BKRIssuesManager.m */; }; ABD245531639E3650080F056 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ABD245521639E3650080F056 /* AVFoundation.framework */; }; - ABD24C1E1620309B0042E49C /* IssueViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = ABD24C1D1620309B0042E49C /* IssueViewController.m */; }; - ABD6FC4016544EAD0032BC44 /* NSData+Base64.m in Sources */ = {isa = PBXBuildFile; fileRef = ABD6FC3F16544EAD0032BC44 /* NSData+Base64.m */; }; - ABD7D45F1616FC5B008B13B5 /* BakerIssue.m in Sources */ = {isa = PBXBuildFile; fileRef = ABD7D45E1616FC5B008B13B5 /* BakerIssue.m */; }; - ABDC05AF16A4C42A00F6B63D /* JSONStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = ABDC05AE16A4C42A00F6B63D /* JSONStatus.m */; }; - ABDC05B216A4CB3900F6B63D /* ShelfStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = ABDC05B116A4CB3900F6B63D /* ShelfStatus.m */; }; - ABF23B1D1708418E003AA3B0 /* BakerAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = ABF23B1C1708418E003AA3B0 /* BakerAPI.m */; }; + ABD24C1E1620309B0042E49C /* BKRIssueViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = ABD24C1D1620309B0042E49C /* BKRIssueViewController.m */; }; + ABD6FC4016544EAD0032BC44 /* NSData+BakerExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = ABD6FC3F16544EAD0032BC44 /* NSData+BakerExtensions.m */; }; + ABD7D45F1616FC5B008B13B5 /* BKRIssue.m in Sources */ = {isa = PBXBuildFile; fileRef = ABD7D45E1616FC5B008B13B5 /* BKRIssue.m */; }; + ABDC05AF16A4C42A00F6B63D /* BKRJSONStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = ABDC05AE16A4C42A00F6B63D /* BKRJSONStatus.m */; }; + ABDC05B216A4CB3900F6B63D /* BKRShelfStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = ABDC05B116A4CB3900F6B63D /* BKRShelfStatus.m */; }; + ABF23B1D1708418E003AA3B0 /* BKRBakerAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = ABF23B1C1708418E003AA3B0 /* BKRBakerAPI.m */; }; BF0785EB1794AF4000EB988E /* info in Resources */ = {isa = PBXBuildFile; fileRef = BF0785EA1794AF4000EB988E /* info */; }; BF78D1B0180C8F070093DFBC /* BakerAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF78D1AF180C8F070093DFBC /* BakerAssets.xcassets */; }; - BFAB78D317B2A67E00E91ADE /* BakerAnalyticsEvents.m in Sources */ = {isa = PBXBuildFile; fileRef = BFAB78D217B2A67E00E91ADE /* BakerAnalyticsEvents.m */; }; + C62D23371A0A710B003A26D2 /* BKRSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = C62D23361A0A710B003A26D2 /* BKRSettings.m */; }; + C62D233F1A0A71DC003A26D2 /* BKRAnalyticsEvents.m in Sources */ = {isa = PBXBuildFile; fileRef = C62D233E1A0A71DC003A26D2 /* BKRAnalyticsEvents.m */; }; + C62D23541A0A748A003A26D2 /* BKRZipArchive.m in Sources */ = {isa = PBXBuildFile; fileRef = C62D23491A0A748A003A26D2 /* BKRZipArchive.m */; }; + C62D23551A0A748A003A26D2 /* ioapi.c in Sources */ = {isa = PBXBuildFile; fileRef = C62D234C1A0A748A003A26D2 /* ioapi.c */; }; + C62D23561A0A748A003A26D2 /* mztools.c in Sources */ = {isa = PBXBuildFile; fileRef = C62D234E1A0A748A003A26D2 /* mztools.c */; }; + C62D23571A0A748A003A26D2 /* unzip.c in Sources */ = {isa = PBXBuildFile; fileRef = C62D23501A0A748A003A26D2 /* unzip.c */; }; + C62D23581A0A748A003A26D2 /* zip.c in Sources */ = {isa = PBXBuildFile; fileRef = C62D23521A0A748A003A26D2 /* zip.c */; }; + C649F0981A090B6000B02548 /* BKRShelfHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C649F0971A090B6000B02548 /* BKRShelfHeaderView.m */; }; + C649F09B1A0912F500B02548 /* UIScreen+BakerExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = C649F09A1A0912F500B02548 /* UIScreen+BakerExtensions.m */; }; + C649F09D1A0926CD00B02548 /* settings.plist in Resources */ = {isa = PBXBuildFile; fileRef = C649F09C1A0926CD00B02548 /* settings.plist */; }; + C659B2941A38BA3700CA238C /* newsstand-app-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = C659B2911A38BA3700CA238C /* newsstand-app-icon.png */; }; + C659B2951A38BA3700CA238C /* newsstand-app-icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C659B2921A38BA3700CA238C /* newsstand-app-icon@2x.png */; }; + C659B2961A38BA3700CA238C /* newsstand-app-icon@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C659B2931A38BA3700CA238C /* newsstand-app-icon@3x.png */; }; + C66857A01A07D38300776443 /* NSString+BakerExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = C668579D1A07D38300776443 /* NSString+BakerExtensions.m */; }; + C66857A11A07D38300776443 /* UIColor+BakerExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = C668579F1A07D38300776443 /* UIColor+BakerExtensions.m */; }; + C66857A41A07DDBE00776443 /* NSObject+BakerExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = C66857A31A07DDBE00776443 /* NSObject+BakerExtensions.m */; }; + C6F4FB991A0E6D9100E365F6 /* MBProgressHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = C6F4FB981A0E6D9100E365F6 /* MBProgressHUD.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 20CC551E16AD346900DEFB5C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 9040338E1A10FF230035071A /* BKRShelfViewLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRShelfViewLayout.h; sourceTree = ""; }; + 9040338F1A10FF230035071A /* BKRShelfViewLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRShelfViewLayout.m; sourceTree = ""; }; + 904C31E51A12645C00E53E66 /* BKRCategoryFilterItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRCategoryFilterItem.h; sourceTree = ""; }; + 904C31E61A12645C00E53E66 /* BKRCategoryFilterItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRCategoryFilterItem.m; sourceTree = ""; }; 9F55576515B4C6B7003072A3 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; - 9F58D84D15D66E870027902C /* UICustomNavigationBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UICustomNavigationBar.h; sourceTree = ""; }; - 9F58D84E15D66E870027902C /* UICustomNavigationBar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UICustomNavigationBar.m; sourceTree = ""; }; - 9F58D84F15D66E870027902C /* UICustomNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UICustomNavigationController.h; sourceTree = ""; }; - 9F58D85015D66E870027902C /* UICustomNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UICustomNavigationController.m; sourceTree = ""; }; - 9F5EB3BB15FCA3A100E13796 /* NSString+Extensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+Extensions.h"; sourceTree = ""; }; - 9F5EB3BC15FCA3A100E13796 /* NSString+Extensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+Extensions.m"; sourceTree = ""; }; - 9F8FC1C215C55E8C0065F0A3 /* BakerBook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BakerBook.h; sourceTree = ""; }; - 9F8FC1C315C55E8C0065F0A3 /* BakerBook.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BakerBook.m; sourceTree = ""; }; - 9FAB40D8162B13CF0088E9C1 /* UIColor+Extensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIColor+Extensions.h"; sourceTree = ""; }; - 9FAB40D9162B13CF0088E9C1 /* UIColor+Extensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIColor+Extensions.m"; sourceTree = ""; }; - 9FF795A515B4C57D00899E51 /* ShelfViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShelfViewController.h; sourceTree = ""; }; - 9FF795A615B4C57E00899E51 /* ShelfViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShelfViewController.m; sourceTree = ""; }; - AB2317D2169F808000BE3BE7 /* PurchasesManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PurchasesManager.h; sourceTree = ""; }; - AB2317D3169F808000BE3BE7 /* PurchasesManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PurchasesManager.m; sourceTree = ""; }; - AB246A2F16C5BC6E00715E25 /* NSMutableURLRequest+WebServiceClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableURLRequest+WebServiceClient.h"; sourceTree = ""; }; - AB246A3016C5BC6E00715E25 /* NSMutableURLRequest+WebServiceClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableURLRequest+WebServiceClient.m"; sourceTree = ""; }; - AB26004F16011D9D00E9AA66 /* BakerBookStatus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BakerBookStatus.h; sourceTree = ""; }; - AB26005016011D9D00E9AA66 /* BakerBookStatus.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BakerBookStatus.m; sourceTree = ""; }; - AB3496BB1661472A00AEB508 /* UIConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UIConstants.h; sourceTree = ""; }; - AB379313161759A900B4C1A3 /* Constants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Constants.h; sourceTree = ""; }; - AB40042A15B4B83000D87E2A /* BakerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BakerViewController.h; sourceTree = ""; }; - AB40042B15B4B83000D87E2A /* BakerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BakerViewController.m; sourceTree = ""; }; - AB40043315B4B83000D87E2A /* IndexViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IndexViewController.h; sourceTree = ""; }; - AB40043415B4B83000D87E2A /* IndexViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IndexViewController.m; sourceTree = ""; }; - AB40043515B4B83000D87E2A /* InterceptorWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InterceptorWindow.h; sourceTree = ""; }; - AB40043615B4B83000D87E2A /* InterceptorWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InterceptorWindow.m; sourceTree = ""; }; + 9F58D84D15D66E870027902C /* BKRCustomNavigationBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRCustomNavigationBar.h; sourceTree = ""; }; + 9F58D84E15D66E870027902C /* BKRCustomNavigationBar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRCustomNavigationBar.m; sourceTree = ""; }; + 9F58D84F15D66E870027902C /* BKRCustomNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRCustomNavigationController.h; sourceTree = ""; }; + 9F58D85015D66E870027902C /* BKRCustomNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRCustomNavigationController.m; sourceTree = ""; }; + 9F8FC1C215C55E8C0065F0A3 /* BKRBook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRBook.h; sourceTree = ""; }; + 9F8FC1C315C55E8C0065F0A3 /* BKRBook.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRBook.m; sourceTree = ""; }; + 9FF795A515B4C57D00899E51 /* BKRShelfViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRShelfViewController.h; sourceTree = ""; }; + 9FF795A615B4C57E00899E51 /* BKRShelfViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRShelfViewController.m; sourceTree = ""; }; + AB2317D2169F808000BE3BE7 /* BKRPurchasesManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRPurchasesManager.h; sourceTree = ""; }; + AB2317D3169F808000BE3BE7 /* BKRPurchasesManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRPurchasesManager.m; sourceTree = ""; }; + AB246A2F16C5BC6E00715E25 /* NSMutableURLRequest+BakerExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableURLRequest+BakerExtensions.h"; sourceTree = ""; }; + AB246A3016C5BC6E00715E25 /* NSMutableURLRequest+BakerExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableURLRequest+BakerExtensions.m"; sourceTree = ""; }; + AB26004F16011D9D00E9AA66 /* BKRBookStatus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRBookStatus.h; sourceTree = ""; }; + AB26005016011D9D00E9AA66 /* BKRBookStatus.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRBookStatus.m; sourceTree = ""; }; + AB40042A15B4B83000D87E2A /* BKRBookViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRBookViewController.h; sourceTree = ""; }; + AB40042B15B4B83000D87E2A /* BKRBookViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRBookViewController.m; sourceTree = ""; }; + AB40043315B4B83000D87E2A /* BKRIndexViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRIndexViewController.h; sourceTree = ""; }; + AB40043415B4B83000D87E2A /* BKRIndexViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRIndexViewController.m; sourceTree = ""; }; + AB40043515B4B83000D87E2A /* BKRInterceptorWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRInterceptorWindow.h; sourceTree = ""; }; + AB40043615B4B83000D87E2A /* BKRInterceptorWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRInterceptorWindow.m; sourceTree = ""; }; AB40043815B4B83000D87E2A /* GTMDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMDefines.h; sourceTree = ""; }; AB40043915B4B83000D87E2A /* GTMNSString+HTML.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSString+HTML.h"; sourceTree = ""; }; AB40043A15B4B83000D87E2A /* GTMNSString+HTML.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+HTML.m"; sourceTree = ""; }; - AB40043F15B4B83000D87E2A /* crypt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = crypt.h; sourceTree = ""; }; - AB40044015B4B83000D87E2A /* ioapi.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ioapi.c; sourceTree = ""; }; - AB40044115B4B83000D87E2A /* ioapi.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ioapi.h; sourceTree = ""; }; - AB40044215B4B83000D87E2A /* mztools.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mztools.c; sourceTree = ""; }; - AB40044315B4B83000D87E2A /* mztools.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mztools.h; sourceTree = ""; }; - AB40044415B4B83000D87E2A /* unzip.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = unzip.c; sourceTree = ""; }; - AB40044515B4B83000D87E2A /* unzip.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = unzip.h; sourceTree = ""; }; - AB40044615B4B83000D87E2A /* zip.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip.c; sourceTree = ""; }; - AB40044715B4B83000D87E2A /* zip.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = zip.h; sourceTree = ""; }; - AB40044815B4B83000D87E2A /* SSZipArchive.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSZipArchive.h; sourceTree = ""; }; - AB40044915B4B83000D87E2A /* SSZipArchive.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SSZipArchive.m; sourceTree = ""; }; - AB40044A15B4B83000D87E2A /* Utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Utils.h; sourceTree = ""; }; - AB40044B15B4B83000D87E2A /* Utils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Utils.m; sourceTree = ""; }; - AB40044C15B4B83000D87E2A /* ModalViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ModalViewController.h; sourceTree = ""; }; - AB40044D15B4B83000D87E2A /* ModalViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ModalViewController.m; sourceTree = ""; }; - AB40045115B4B83000D87E2A /* PageTitleLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PageTitleLabel.h; sourceTree = ""; }; - AB40045215B4B83000D87E2A /* PageTitleLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PageTitleLabel.m; sourceTree = ""; }; + AB40044A15B4B83000D87E2A /* BKRUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRUtils.h; sourceTree = ""; }; + AB40044B15B4B83000D87E2A /* BKRUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRUtils.m; sourceTree = ""; }; + AB40044C15B4B83000D87E2A /* BKRModalWebViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRModalWebViewController.h; sourceTree = ""; }; + AB40044D15B4B83000D87E2A /* BKRModalWebViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRModalWebViewController.m; sourceTree = ""; }; + AB40045115B4B83000D87E2A /* BKRPageTitleLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRPageTitleLabel.h; sourceTree = ""; }; + AB40045215B4B83000D87E2A /* BKRPageTitleLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRPageTitleLabel.m; sourceTree = ""; }; AB40048015B4B90200D87E2A /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; }; AB40048215B4B90A00D87E2A /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; AB40048415B4BB8B00D87E2A /* books */ = {isa = PBXFileReference; lastKnownFileType = folder; path = books; sourceTree = ""; }; AB5D48CD1609176A00828D16 /* NewsstandKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NewsstandKit.framework; path = System/Library/Frameworks/NewsstandKit.framework; sourceTree = SDKROOT; }; - AB67F5B816EE99B2005A04F6 /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Reachability.h; sourceTree = ""; }; - AB67F5B916EE99B2005A04F6 /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Reachability.m; sourceTree = ""; }; + AB67F5B816EE99B2005A04F6 /* BKRReachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRReachability.h; sourceTree = ""; }; + AB67F5B916EE99B2005A04F6 /* BKRReachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRReachability.m; sourceTree = ""; }; AB67F5BB16EE9B79005A04F6 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; ABABF1A51646C04D00A75E67 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; - ABADF3F716C7BAD900F52F3C /* NSURL+Extensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURL+Extensions.h"; sourceTree = ""; }; - ABADF3F816C7BAD900F52F3C /* NSURL+Extensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURL+Extensions.m"; sourceTree = ""; }; + ABADF3F716C7BAD900F52F3C /* NSURL+BakerExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURL+BakerExtensions.h"; sourceTree = ""; }; + ABADF3F816C7BAD900F52F3C /* NSURL+BakerExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURL+BakerExtensions.m"; sourceTree = ""; }; ABAFB58715AB9A2E002FA498 /* Baker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Baker.app; sourceTree = BUILT_PRODUCTS_DIR; }; ABAFB58B15AB9A2E002FA498 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; ABAFB58D15AB9A2E002FA498 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; @@ -123,29 +120,57 @@ ABAFB59315AB9A2E002FA498 /* Baker-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Baker-Info.plist"; sourceTree = ""; }; ABAFB59715AB9A2E002FA498 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; ABAFB59915AB9A2E002FA498 /* Baker-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Baker-Prefix.pch"; sourceTree = ""; }; - ABAFB59A15AB9A2E002FA498 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - ABAFB59B15AB9A2E002FA498 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - ABBFD8F016C6614300BA64A4 /* NSString+UUID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+UUID.h"; sourceTree = ""; }; - ABBFD8F116C6614300BA64A4 /* NSString+UUID.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+UUID.m"; sourceTree = ""; }; - ABC270CF1616F3CB00AD97E9 /* IssuesManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IssuesManager.h; sourceTree = ""; }; - ABC270D01616F3CB00AD97E9 /* IssuesManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IssuesManager.m; sourceTree = ""; }; + ABAFB59A15AB9A2E002FA498 /* BKRAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BKRAppDelegate.h; sourceTree = ""; }; + ABAFB59B15AB9A2E002FA498 /* BKRAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BKRAppDelegate.m; sourceTree = ""; }; + ABC270CF1616F3CB00AD97E9 /* BKRIssuesManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRIssuesManager.h; sourceTree = ""; }; + ABC270D01616F3CB00AD97E9 /* BKRIssuesManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRIssuesManager.m; sourceTree = ""; }; ABD245521639E3650080F056 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; - ABD24C1C1620309B0042E49C /* IssueViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IssueViewController.h; sourceTree = ""; }; - ABD24C1D1620309B0042E49C /* IssueViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IssueViewController.m; sourceTree = ""; }; - ABD6FC3E16544EAD0032BC44 /* NSData+Base64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+Base64.h"; sourceTree = ""; }; - ABD6FC3F16544EAD0032BC44 /* NSData+Base64.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+Base64.m"; sourceTree = ""; }; - ABD7D45D1616FC5B008B13B5 /* BakerIssue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BakerIssue.h; sourceTree = ""; }; - ABD7D45E1616FC5B008B13B5 /* BakerIssue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BakerIssue.m; sourceTree = ""; }; - ABDC05AD16A4C42A00F6B63D /* JSONStatus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONStatus.h; sourceTree = ""; }; - ABDC05AE16A4C42A00F6B63D /* JSONStatus.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONStatus.m; sourceTree = ""; }; - ABDC05B016A4CB3900F6B63D /* ShelfStatus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShelfStatus.h; sourceTree = ""; }; - ABDC05B116A4CB3900F6B63D /* ShelfStatus.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShelfStatus.m; sourceTree = ""; }; - ABF23B1B1708418E003AA3B0 /* BakerAPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BakerAPI.h; sourceTree = ""; }; - ABF23B1C1708418E003AA3B0 /* BakerAPI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BakerAPI.m; sourceTree = ""; }; + ABD24C1C1620309B0042E49C /* BKRIssueViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRIssueViewController.h; sourceTree = ""; }; + ABD24C1D1620309B0042E49C /* BKRIssueViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRIssueViewController.m; sourceTree = ""; }; + ABD6FC3E16544EAD0032BC44 /* NSData+BakerExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+BakerExtensions.h"; sourceTree = ""; }; + ABD6FC3F16544EAD0032BC44 /* NSData+BakerExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+BakerExtensions.m"; sourceTree = ""; }; + ABD7D45D1616FC5B008B13B5 /* BKRIssue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRIssue.h; sourceTree = ""; }; + ABD7D45E1616FC5B008B13B5 /* BKRIssue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRIssue.m; sourceTree = ""; }; + ABDC05AD16A4C42A00F6B63D /* BKRJSONStatus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRJSONStatus.h; sourceTree = ""; }; + ABDC05AE16A4C42A00F6B63D /* BKRJSONStatus.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRJSONStatus.m; sourceTree = ""; }; + ABDC05B016A4CB3900F6B63D /* BKRShelfStatus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRShelfStatus.h; sourceTree = ""; }; + ABDC05B116A4CB3900F6B63D /* BKRShelfStatus.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRShelfStatus.m; sourceTree = ""; }; + ABF23B1B1708418E003AA3B0 /* BKRBakerAPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRBakerAPI.h; sourceTree = ""; }; + ABF23B1C1708418E003AA3B0 /* BKRBakerAPI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRBakerAPI.m; sourceTree = ""; }; BF0785EA1794AF4000EB988E /* info */ = {isa = PBXFileReference; lastKnownFileType = folder; path = info; sourceTree = ""; }; BF78D1AF180C8F070093DFBC /* BakerAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = BakerAssets.xcassets; path = Baker/BakerAssets.xcassets; sourceTree = ""; }; - BFAB78D117B2A67E00E91ADE /* BakerAnalyticsEvents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BakerAnalyticsEvents.h; sourceTree = ""; }; - BFAB78D217B2A67E00E91ADE /* BakerAnalyticsEvents.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BakerAnalyticsEvents.m; sourceTree = ""; }; + C62D23351A0A710B003A26D2 /* BKRSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRSettings.h; sourceTree = ""; }; + C62D23361A0A710B003A26D2 /* BKRSettings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRSettings.m; sourceTree = ""; }; + C62D233D1A0A71DC003A26D2 /* BKRAnalyticsEvents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRAnalyticsEvents.h; sourceTree = ""; }; + C62D233E1A0A71DC003A26D2 /* BKRAnalyticsEvents.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRAnalyticsEvents.m; sourceTree = ""; }; + C62D23451A0A73A9003A26D2 /* BKRCore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRCore.h; sourceTree = ""; }; + C62D23481A0A748A003A26D2 /* BKRZipArchive.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRZipArchive.h; sourceTree = ""; }; + C62D23491A0A748A003A26D2 /* BKRZipArchive.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRZipArchive.m; sourceTree = ""; }; + C62D234B1A0A748A003A26D2 /* crypt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = crypt.h; sourceTree = ""; }; + C62D234C1A0A748A003A26D2 /* ioapi.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ioapi.c; sourceTree = ""; }; + C62D234D1A0A748A003A26D2 /* ioapi.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ioapi.h; sourceTree = ""; }; + C62D234E1A0A748A003A26D2 /* mztools.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mztools.c; sourceTree = ""; }; + C62D234F1A0A748A003A26D2 /* mztools.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mztools.h; sourceTree = ""; }; + C62D23501A0A748A003A26D2 /* unzip.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = unzip.c; sourceTree = ""; }; + C62D23511A0A748A003A26D2 /* unzip.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = unzip.h; sourceTree = ""; }; + C62D23521A0A748A003A26D2 /* zip.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip.c; sourceTree = ""; }; + C62D23531A0A748A003A26D2 /* zip.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = zip.h; sourceTree = ""; }; + C649F0961A090B6000B02548 /* BKRShelfHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKRShelfHeaderView.h; sourceTree = ""; }; + C649F0971A090B6000B02548 /* BKRShelfHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKRShelfHeaderView.m; sourceTree = ""; }; + C649F0991A0912F500B02548 /* UIScreen+BakerExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIScreen+BakerExtensions.h"; sourceTree = ""; }; + C649F09A1A0912F500B02548 /* UIScreen+BakerExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIScreen+BakerExtensions.m"; sourceTree = ""; }; + C649F09C1A0926CD00B02548 /* settings.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = settings.plist; sourceTree = ""; }; + C659B2911A38BA3700CA238C /* newsstand-app-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "newsstand-app-icon.png"; sourceTree = ""; }; + C659B2921A38BA3700CA238C /* newsstand-app-icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "newsstand-app-icon@2x.png"; sourceTree = ""; }; + C659B2931A38BA3700CA238C /* newsstand-app-icon@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "newsstand-app-icon@3x.png"; sourceTree = ""; }; + C668579C1A07D38300776443 /* NSString+BakerExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+BakerExtensions.h"; sourceTree = ""; }; + C668579D1A07D38300776443 /* NSString+BakerExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+BakerExtensions.m"; sourceTree = ""; }; + C668579E1A07D38300776443 /* UIColor+BakerExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIColor+BakerExtensions.h"; sourceTree = ""; }; + C668579F1A07D38300776443 /* UIColor+BakerExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIColor+BakerExtensions.m"; sourceTree = ""; }; + C66857A21A07DDBE00776443 /* NSObject+BakerExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+BakerExtensions.h"; sourceTree = ""; }; + C66857A31A07DDBE00776443 /* NSObject+BakerExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+BakerExtensions.m"; sourceTree = ""; }; + C6F4FB971A0E6D9100E365F6 /* MBProgressHUD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBProgressHUD.h; sourceTree = ""; }; + C6F4FB981A0E6D9100E365F6 /* MBProgressHUD.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBProgressHUD.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -169,6 +194,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 904C31E41A12645C00E53E66 /* BakerComponents */ = { + isa = PBXGroup; + children = ( + 904C31E51A12645C00E53E66 /* BKRCategoryFilterItem.h */, + 904C31E61A12645C00E53E66 /* BKRCategoryFilterItem.m */, + ); + path = BakerComponents; + sourceTree = ""; + }; 9F58D85E15D6743C0027902C /* img */ = { isa = PBXGroup; children = ( @@ -179,20 +213,19 @@ 9FF7957E15B4C4F600899E51 /* lib */ = { isa = PBXGroup; children = ( - ABD6FC3E16544EAD0032BC44 /* NSData+Base64.h */, - ABD6FC3F16544EAD0032BC44 /* NSData+Base64.m */, - 9F5EB3BB15FCA3A100E13796 /* NSString+Extensions.h */, - 9F5EB3BC15FCA3A100E13796 /* NSString+Extensions.m */, - 9FAB40D8162B13CF0088E9C1 /* UIColor+Extensions.h */, - 9FAB40D9162B13CF0088E9C1 /* UIColor+Extensions.m */, - AB246A2F16C5BC6E00715E25 /* NSMutableURLRequest+WebServiceClient.h */, - AB246A3016C5BC6E00715E25 /* NSMutableURLRequest+WebServiceClient.m */, - ABBFD8F016C6614300BA64A4 /* NSString+UUID.h */, - ABBFD8F116C6614300BA64A4 /* NSString+UUID.m */, - ABADF3F716C7BAD900F52F3C /* NSURL+Extensions.h */, - ABADF3F816C7BAD900F52F3C /* NSURL+Extensions.m */, - AB67F5B816EE99B2005A04F6 /* Reachability.h */, - AB67F5B916EE99B2005A04F6 /* Reachability.m */, + AB67F5B816EE99B2005A04F6 /* BKRReachability.h */, + AB67F5B916EE99B2005A04F6 /* BKRReachability.m */, + C62D23481A0A748A003A26D2 /* BKRZipArchive.h */, + C62D23491A0A748A003A26D2 /* BKRZipArchive.m */, + C6F4FB971A0E6D9100E365F6 /* MBProgressHUD.h */, + C6F4FB981A0E6D9100E365F6 /* MBProgressHUD.m */, + C62D234A1A0A748A003A26D2 /* minizip */, + ABD6FC3E16544EAD0032BC44 /* NSData+BakerExtensions.h */, + ABD6FC3F16544EAD0032BC44 /* NSData+BakerExtensions.m */, + AB246A2F16C5BC6E00715E25 /* NSMutableURLRequest+BakerExtensions.h */, + AB246A3016C5BC6E00715E25 /* NSMutableURLRequest+BakerExtensions.m */, + ABADF3F716C7BAD900F52F3C /* NSURL+BakerExtensions.h */, + ABADF3F816C7BAD900F52F3C /* NSURL+BakerExtensions.m */, ); path = lib; sourceTree = ""; @@ -200,70 +233,58 @@ AB40042915B4B83000D87E2A /* BakerView */ = { isa = PBXGroup; children = ( - AB40042E15B4B83000D87E2A /* img */, AB40043715B4B83000D87E2A /* lib */, AB40045015B4B83000D87E2A /* ui */, - 9F8FC1C215C55E8C0065F0A3 /* BakerBook.h */, - 9F8FC1C315C55E8C0065F0A3 /* BakerBook.m */, - AB26004F16011D9D00E9AA66 /* BakerBookStatus.h */, - AB26005016011D9D00E9AA66 /* BakerBookStatus.m */, - AB40042A15B4B83000D87E2A /* BakerViewController.h */, - AB40042B15B4B83000D87E2A /* BakerViewController.m */, - AB40043315B4B83000D87E2A /* IndexViewController.h */, - AB40043415B4B83000D87E2A /* IndexViewController.m */, - AB40043515B4B83000D87E2A /* InterceptorWindow.h */, - AB40043615B4B83000D87E2A /* InterceptorWindow.m */, - AB40044C15B4B83000D87E2A /* ModalViewController.h */, - AB40044D15B4B83000D87E2A /* ModalViewController.m */, + C62D23451A0A73A9003A26D2 /* BKRCore.h */, + C62D233D1A0A71DC003A26D2 /* BKRAnalyticsEvents.h */, + C62D233E1A0A71DC003A26D2 /* BKRAnalyticsEvents.m */, + 9F8FC1C215C55E8C0065F0A3 /* BKRBook.h */, + 9F8FC1C315C55E8C0065F0A3 /* BKRBook.m */, + AB26004F16011D9D00E9AA66 /* BKRBookStatus.h */, + AB26005016011D9D00E9AA66 /* BKRBookStatus.m */, + AB40042A15B4B83000D87E2A /* BKRBookViewController.h */, + AB40042B15B4B83000D87E2A /* BKRBookViewController.m */, + AB40043315B4B83000D87E2A /* BKRIndexViewController.h */, + AB40043415B4B83000D87E2A /* BKRIndexViewController.m */, + AB40043515B4B83000D87E2A /* BKRInterceptorWindow.h */, + AB40043615B4B83000D87E2A /* BKRInterceptorWindow.m */, + AB40044C15B4B83000D87E2A /* BKRModalWebViewController.h */, + AB40044D15B4B83000D87E2A /* BKRModalWebViewController.m */, + 9040338E1A10FF230035071A /* BKRShelfViewLayout.h */, + 9040338F1A10FF230035071A /* BKRShelfViewLayout.m */, + C62D23351A0A710B003A26D2 /* BKRSettings.h */, + C62D23361A0A710B003A26D2 /* BKRSettings.m */, ); path = BakerView; sourceTree = ""; }; - AB40042E15B4B83000D87E2A /* img */ = { - isa = PBXGroup; - children = ( - ); - path = img; - sourceTree = ""; - }; AB40043715B4B83000D87E2A /* lib */ = { isa = PBXGroup; children = ( - AB40043E15B4B83000D87E2A /* minizip */, + ABDC05AD16A4C42A00F6B63D /* BKRJSONStatus.h */, + ABDC05AE16A4C42A00F6B63D /* BKRJSONStatus.m */, + AB40044A15B4B83000D87E2A /* BKRUtils.h */, + AB40044B15B4B83000D87E2A /* BKRUtils.m */, AB40043815B4B83000D87E2A /* GTMDefines.h */, AB40043915B4B83000D87E2A /* GTMNSString+HTML.h */, AB40043A15B4B83000D87E2A /* GTMNSString+HTML.m */, - AB40044815B4B83000D87E2A /* SSZipArchive.h */, - AB40044915B4B83000D87E2A /* SSZipArchive.m */, - AB40044A15B4B83000D87E2A /* Utils.h */, - AB40044B15B4B83000D87E2A /* Utils.m */, - ABDC05AD16A4C42A00F6B63D /* JSONStatus.h */, - ABDC05AE16A4C42A00F6B63D /* JSONStatus.m */, + C66857A21A07DDBE00776443 /* NSObject+BakerExtensions.h */, + C66857A31A07DDBE00776443 /* NSObject+BakerExtensions.m */, + C668579C1A07D38300776443 /* NSString+BakerExtensions.h */, + C668579D1A07D38300776443 /* NSString+BakerExtensions.m */, + C668579E1A07D38300776443 /* UIColor+BakerExtensions.h */, + C668579F1A07D38300776443 /* UIColor+BakerExtensions.m */, + C649F0991A0912F500B02548 /* UIScreen+BakerExtensions.h */, + C649F09A1A0912F500B02548 /* UIScreen+BakerExtensions.m */, ); path = lib; sourceTree = ""; }; - AB40043E15B4B83000D87E2A /* minizip */ = { - isa = PBXGroup; - children = ( - AB40043F15B4B83000D87E2A /* crypt.h */, - AB40044015B4B83000D87E2A /* ioapi.c */, - AB40044115B4B83000D87E2A /* ioapi.h */, - AB40044215B4B83000D87E2A /* mztools.c */, - AB40044315B4B83000D87E2A /* mztools.h */, - AB40044415B4B83000D87E2A /* unzip.c */, - AB40044515B4B83000D87E2A /* unzip.h */, - AB40044615B4B83000D87E2A /* zip.c */, - AB40044715B4B83000D87E2A /* zip.h */, - ); - path = minizip; - sourceTree = ""; - }; AB40045015B4B83000D87E2A /* ui */ = { isa = PBXGroup; children = ( - AB40045115B4B83000D87E2A /* PageTitleLabel.h */, - AB40045215B4B83000D87E2A /* PageTitleLabel.m */, + AB40045115B4B83000D87E2A /* BKRPageTitleLabel.h */, + AB40045215B4B83000D87E2A /* BKRPageTitleLabel.m */, ); path = ui; sourceTree = ""; @@ -272,28 +293,28 @@ isa = PBXGroup; children = ( BF0785EA1794AF4000EB988E /* info */, - AB379313161759A900B4C1A3 /* Constants.h */, - AB3496BB1661472A00AEB508 /* UIConstants.h */, 9F58D85E15D6743C0027902C /* img */, 9FF7957E15B4C4F600899E51 /* lib */, - ABD7D45D1616FC5B008B13B5 /* BakerIssue.h */, - ABD7D45E1616FC5B008B13B5 /* BakerIssue.m */, - ABC270CF1616F3CB00AD97E9 /* IssuesManager.h */, - ABC270D01616F3CB00AD97E9 /* IssuesManager.m */, - ABD24C1C1620309B0042E49C /* IssueViewController.h */, - ABD24C1D1620309B0042E49C /* IssueViewController.m */, - AB2317D2169F808000BE3BE7 /* PurchasesManager.h */, - AB2317D3169F808000BE3BE7 /* PurchasesManager.m */, - ABDC05B016A4CB3900F6B63D /* ShelfStatus.h */, - ABDC05B116A4CB3900F6B63D /* ShelfStatus.m */, - 9FF795A515B4C57D00899E51 /* ShelfViewController.h */, - 9FF795A615B4C57E00899E51 /* ShelfViewController.m */, - 9F58D84D15D66E870027902C /* UICustomNavigationBar.h */, - 9F58D84E15D66E870027902C /* UICustomNavigationBar.m */, - 9F58D84F15D66E870027902C /* UICustomNavigationController.h */, - 9F58D85015D66E870027902C /* UICustomNavigationController.m */, - ABF23B1B1708418E003AA3B0 /* BakerAPI.h */, - ABF23B1C1708418E003AA3B0 /* BakerAPI.m */, + ABF23B1B1708418E003AA3B0 /* BKRBakerAPI.h */, + ABF23B1C1708418E003AA3B0 /* BKRBakerAPI.m */, + 9F58D84D15D66E870027902C /* BKRCustomNavigationBar.h */, + 9F58D84E15D66E870027902C /* BKRCustomNavigationBar.m */, + 9F58D84F15D66E870027902C /* BKRCustomNavigationController.h */, + 9F58D85015D66E870027902C /* BKRCustomNavigationController.m */, + ABD7D45D1616FC5B008B13B5 /* BKRIssue.h */, + ABD7D45E1616FC5B008B13B5 /* BKRIssue.m */, + ABC270CF1616F3CB00AD97E9 /* BKRIssuesManager.h */, + ABC270D01616F3CB00AD97E9 /* BKRIssuesManager.m */, + ABD24C1C1620309B0042E49C /* BKRIssueViewController.h */, + ABD24C1D1620309B0042E49C /* BKRIssueViewController.m */, + AB2317D2169F808000BE3BE7 /* BKRPurchasesManager.h */, + AB2317D3169F808000BE3BE7 /* BKRPurchasesManager.m */, + C649F0961A090B6000B02548 /* BKRShelfHeaderView.h */, + C649F0971A090B6000B02548 /* BKRShelfHeaderView.m */, + ABDC05B016A4CB3900F6B63D /* BKRShelfStatus.h */, + ABDC05B116A4CB3900F6B63D /* BKRShelfStatus.m */, + 9FF795A515B4C57D00899E51 /* BKRShelfViewController.h */, + 9FF795A615B4C57E00899E51 /* BKRShelfViewController.m */, ); path = BakerShelf; sourceTree = ""; @@ -308,6 +329,7 @@ ABAFB59115AB9A2E002FA498 /* Baker */, ABAFB58A15AB9A2E002FA498 /* Frameworks */, ABAFB58815AB9A2E002FA498 /* Products */, + 904C31E41A12645C00E53E66 /* BakerComponents */, ); sourceTree = ""; }; @@ -339,10 +361,12 @@ ABAFB59115AB9A2E002FA498 /* Baker */ = { isa = PBXGroup; children = ( - ABAFB59A15AB9A2E002FA498 /* AppDelegate.h */, - ABAFB59B15AB9A2E002FA498 /* AppDelegate.m */, - BFAB78D117B2A67E00E91ADE /* BakerAnalyticsEvents.h */, - BFAB78D217B2A67E00E91ADE /* BakerAnalyticsEvents.m */, + C659B2911A38BA3700CA238C /* newsstand-app-icon.png */, + C659B2921A38BA3700CA238C /* newsstand-app-icon@2x.png */, + C659B2931A38BA3700CA238C /* newsstand-app-icon@3x.png */, + C649F09C1A0926CD00B02548 /* settings.plist */, + ABAFB59A15AB9A2E002FA498 /* BKRAppDelegate.h */, + ABAFB59B15AB9A2E002FA498 /* BKRAppDelegate.m */, ABAFB59215AB9A2E002FA498 /* Supporting Files */, ); path = Baker; @@ -352,7 +376,6 @@ isa = PBXGroup; children = ( 20CC551F16AD346900DEFB5C /* Localizable.strings */, - BF3CCC9D15D080560087B9CA /* icon */, ABAFB59315AB9A2E002FA498 /* Baker-Info.plist */, ABAFB59715AB9A2E002FA498 /* main.m */, ABAFB59915AB9A2E002FA498 /* Baker-Prefix.pch */, @@ -360,11 +383,20 @@ name = "Supporting Files"; sourceTree = ""; }; - BF3CCC9D15D080560087B9CA /* icon */ = { + C62D234A1A0A748A003A26D2 /* minizip */ = { isa = PBXGroup; children = ( + C62D234B1A0A748A003A26D2 /* crypt.h */, + C62D234C1A0A748A003A26D2 /* ioapi.c */, + C62D234D1A0A748A003A26D2 /* ioapi.h */, + C62D234E1A0A748A003A26D2 /* mztools.c */, + C62D234F1A0A748A003A26D2 /* mztools.h */, + C62D23501A0A748A003A26D2 /* unzip.c */, + C62D23511A0A748A003A26D2 /* unzip.h */, + C62D23521A0A748A003A26D2 /* zip.c */, + C62D23531A0A748A003A26D2 /* zip.h */, ); - path = icon; + path = minizip; sourceTree = ""; }; /* End PBXGroup section */ @@ -393,7 +425,12 @@ ABAFB57E15AB9A2E002FA498 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0500; + LastUpgradeCheck = 0630; + TargetAttributes = { + ABAFB58615AB9A2E002FA498 = { + DevelopmentTeam = D98EH4D5B2; + }; + }; }; buildConfigurationList = ABAFB58115AB9A2E002FA498 /* Build configuration list for PBXProject "Baker" */; compatibilityVersion = "Xcode 3.2"; @@ -417,10 +454,14 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + C659B2941A38BA3700CA238C /* newsstand-app-icon.png in Resources */, + C659B2951A38BA3700CA238C /* newsstand-app-icon@2x.png in Resources */, + C659B2961A38BA3700CA238C /* newsstand-app-icon@3x.png in Resources */, AB40048515B4BB8B00D87E2A /* books in Resources */, BF78D1B0180C8F070093DFBC /* BakerAssets.xcassets in Resources */, 20CC551D16AD346900DEFB5C /* Localizable.strings in Resources */, BF0785EB1794AF4000EB988E /* info in Resources */, + C649F09D1A0926CD00B02548 /* settings.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -431,40 +472,46 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C62D23541A0A748A003A26D2 /* BKRZipArchive.m in Sources */, ABAFB59815AB9A2E002FA498 /* main.m in Sources */, - ABAFB59C15AB9A2E002FA498 /* AppDelegate.m in Sources */, - AB40045315B4B83000D87E2A /* BakerViewController.m in Sources */, - AB40045F15B4B83000D87E2A /* IndexViewController.m in Sources */, - AB40046115B4B83000D87E2A /* InterceptorWindow.m in Sources */, + ABAFB59C15AB9A2E002FA498 /* BKRAppDelegate.m in Sources */, + AB40045315B4B83000D87E2A /* BKRBookViewController.m in Sources */, + 904C31E71A12645C00E53E66 /* BKRCategoryFilterItem.m in Sources */, + AB40045F15B4B83000D87E2A /* BKRIndexViewController.m in Sources */, + AB40046115B4B83000D87E2A /* BKRInterceptorWindow.m in Sources */, + C62D23571A0A748A003A26D2 /* unzip.c in Sources */, + C6F4FB991A0E6D9100E365F6 /* MBProgressHUD.m in Sources */, AB40046315B4B83000D87E2A /* GTMNSString+HTML.m in Sources */, - AB40046715B4B83000D87E2A /* ioapi.c in Sources */, - AB40046915B4B83000D87E2A /* mztools.c in Sources */, - AB40046B15B4B83000D87E2A /* unzip.c in Sources */, - AB40046D15B4B83000D87E2A /* zip.c in Sources */, - AB40046F15B4B83000D87E2A /* SSZipArchive.m in Sources */, - AB40047115B4B83000D87E2A /* Utils.m in Sources */, - AB40047315B4B83000D87E2A /* ModalViewController.m in Sources */, - AB40047715B4B83000D87E2A /* PageTitleLabel.m in Sources */, - 9FF795A715B4C57E00899E51 /* ShelfViewController.m in Sources */, - 9F8FC1C415C55E8C0065F0A3 /* BakerBook.m in Sources */, - 9F58D85115D66E870027902C /* UICustomNavigationBar.m in Sources */, - 9F58D85215D66E870027902C /* UICustomNavigationController.m in Sources */, - 9F5EB3BD15FCA3A100E13796 /* NSString+Extensions.m in Sources */, - AB26005116011D9D00E9AA66 /* BakerBookStatus.m in Sources */, - ABD7D45F1616FC5B008B13B5 /* BakerIssue.m in Sources */, - ABC270D11616F3CB00AD97E9 /* IssuesManager.m in Sources */, - ABD24C1E1620309B0042E49C /* IssueViewController.m in Sources */, - 9FAB40DA162B13CF0088E9C1 /* UIColor+Extensions.m in Sources */, - ABD6FC4016544EAD0032BC44 /* NSData+Base64.m in Sources */, - AB2317D4169F808000BE3BE7 /* PurchasesManager.m in Sources */, - ABDC05AF16A4C42A00F6B63D /* JSONStatus.m in Sources */, - ABDC05B216A4CB3900F6B63D /* ShelfStatus.m in Sources */, - AB246A3116C5BC6E00715E25 /* NSMutableURLRequest+WebServiceClient.m in Sources */, - ABBFD8F216C6614300BA64A4 /* NSString+UUID.m in Sources */, - ABADF3F916C7BAD900F52F3C /* NSURL+Extensions.m in Sources */, - AB67F5BA16EE99B2005A04F6 /* Reachability.m in Sources */, - ABF23B1D1708418E003AA3B0 /* BakerAPI.m in Sources */, - BFAB78D317B2A67E00E91ADE /* BakerAnalyticsEvents.m in Sources */, + C66857A41A07DDBE00776443 /* NSObject+BakerExtensions.m in Sources */, + C649F0981A090B6000B02548 /* BKRShelfHeaderView.m in Sources */, + AB40047115B4B83000D87E2A /* BKRUtils.m in Sources */, + C66857A01A07D38300776443 /* NSString+BakerExtensions.m in Sources */, + C66857A11A07D38300776443 /* UIColor+BakerExtensions.m in Sources */, + AB40047315B4B83000D87E2A /* BKRModalWebViewController.m in Sources */, + AB40047715B4B83000D87E2A /* BKRPageTitleLabel.m in Sources */, + 904033901A10FF230035071A /* BKRShelfViewLayout.m in Sources */, + 9FF795A715B4C57E00899E51 /* BKRShelfViewController.m in Sources */, + 9F8FC1C415C55E8C0065F0A3 /* BKRBook.m in Sources */, + 9F58D85115D66E870027902C /* BKRCustomNavigationBar.m in Sources */, + 9F58D85215D66E870027902C /* BKRCustomNavigationController.m in Sources */, + AB26005116011D9D00E9AA66 /* BKRBookStatus.m in Sources */, + ABD7D45F1616FC5B008B13B5 /* BKRIssue.m in Sources */, + ABC270D11616F3CB00AD97E9 /* BKRIssuesManager.m in Sources */, + ABD24C1E1620309B0042E49C /* BKRIssueViewController.m in Sources */, + C62D23551A0A748A003A26D2 /* ioapi.c in Sources */, + ABD6FC4016544EAD0032BC44 /* NSData+BakerExtensions.m in Sources */, + AB2317D4169F808000BE3BE7 /* BKRPurchasesManager.m in Sources */, + ABDC05AF16A4C42A00F6B63D /* BKRJSONStatus.m in Sources */, + C649F09B1A0912F500B02548 /* UIScreen+BakerExtensions.m in Sources */, + ABDC05B216A4CB3900F6B63D /* BKRShelfStatus.m in Sources */, + AB246A3116C5BC6E00715E25 /* NSMutableURLRequest+BakerExtensions.m in Sources */, + C62D23561A0A748A003A26D2 /* mztools.c in Sources */, + C62D23371A0A710B003A26D2 /* BKRSettings.m in Sources */, + ABADF3F916C7BAD900F52F3C /* NSURL+BakerExtensions.m in Sources */, + AB67F5BA16EE99B2005A04F6 /* BKRReachability.m in Sources */, + C62D23581A0A748A003A26D2 /* zip.c in Sources */, + ABF23B1D1708418E003AA3B0 /* BKRBakerAPI.m in Sources */, + C62D233F1A0A71DC003A26D2 /* BKRAnalyticsEvents.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -491,11 +538,14 @@ CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -503,6 +553,7 @@ ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; @@ -524,11 +575,15 @@ CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; @@ -547,12 +602,13 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CLANG_ENABLE_OBJC_ARC = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Baker/Baker-Prefix.pch"; INFOPLIST_FILE = "Baker/Baker-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 6.0; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; PRODUCT_NAME = "$(TARGET_NAME)"; - TARGETED_DEVICE_FAMILY = 2; + TARGETED_DEVICE_FAMILY = "1,2"; WRAPPER_EXTENSION = app; }; name = Debug; @@ -562,12 +618,13 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CLANG_ENABLE_OBJC_ARC = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Baker/Baker-Prefix.pch"; INFOPLIST_FILE = "Baker/Baker-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 6.0; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; PRODUCT_NAME = "$(TARGET_NAME)"; - TARGETED_DEVICE_FAMILY = 2; + TARGETED_DEVICE_FAMILY = "1,2"; WRAPPER_EXTENSION = app; }; name = Release; diff --git a/Baker/AppDelegate.m b/Baker/AppDelegate.m deleted file mode 100644 index 8121af69..00000000 --- a/Baker/AppDelegate.m +++ /dev/null @@ -1,271 +0,0 @@ -// -// AppDelegate.m -// Baker -// -// ========================================================================================== -// -// Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi -// 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 Baker Framework nor the names of its contributors may be used to -// endorse or promote products derived from this software without specific prior written -// permission. -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT -// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// - -#import "Constants.h" -#import "UIConstants.h" - -#import "AppDelegate.h" -#import "UICustomNavigationController.h" -#import "UICustomNavigationBar.h" -#import "IssuesManager.h" -#import "BakerAPI.h" -#import "UIColor+Extensions.h" -#import "Utils.h" - -#import "BakerViewController.h" -#import "BakerAnalyticsEvents.h" - -@implementation AppDelegate - -@synthesize window; -@synthesize rootViewController; -@synthesize rootNavigationController; - -+ (void)initialize { - // Set user agent (the only problem is that we can't modify the User-Agent later in the program) - // We use a more browser-like User-Agent in order to allow browser detection scripts to run (like Tumult Hype). - NSDictionary *userAgent = [[NSDictionary alloc] initWithObjectsAndKeys:@"Mozilla/5.0 (compatible; BakerFramework) AppleWebKit/533.00+ (KHTML, like Gecko) Mobile", @"UserAgent", nil]; - [[NSUserDefaults standardUserDefaults] registerDefaults:userAgent]; - [userAgent release]; -} - -- (void)dealloc -{ - [window release]; - [rootViewController release]; - [rootNavigationController release]; - - [super dealloc]; -} - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions -{ - - #ifdef BAKER_NEWSSTAND - - NSLog(@"====== Baker Newsstand Mode enabled ======"); - [BakerAPI generateUUIDOnce]; - - // Let the device know we want to handle Newsstand push notifications - [application registerForRemoteNotificationTypes:UIRemoteNotificationTypeNewsstandContentAvailability]; - - #ifdef DEBUG - // For debug only... so that you can download multiple issues per day during development - [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"NKDontThrottleNewsstandContentNotifications"]; - [[NSUserDefaults standardUserDefaults] synchronize]; - #endif - - // Check if the app is runnig in response to a notification - NSDictionary *payload = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey]; - if (payload) { - NSDictionary *aps = [payload objectForKey:@"aps"]; - if (aps && [aps objectForKey:@"content-available"]) { - - __block UIBackgroundTaskIdentifier backgroundTask = [application beginBackgroundTaskWithExpirationHandler:^{ - [application endBackgroundTask:backgroundTask]; - backgroundTask = UIBackgroundTaskInvalid; - }]; - - // Credit where credit is due. This semaphore solution found here: - // http://stackoverflow.com/a/4326754/2998 - dispatch_semaphore_t sema = NULL; - sema = dispatch_semaphore_create(0); - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ - [self applicationWillHandleNewsstandNotificationOfContent:[payload objectForKey:@"content-name"]]; - [application endBackgroundTask:backgroundTask]; - backgroundTask = UIBackgroundTaskInvalid; - dispatch_semaphore_signal(sema); - }); - - dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); - dispatch_release(sema); - } - } - - self.rootViewController = [[[ShelfViewController alloc] init] autorelease]; - - #else - - NSLog(@"====== Baker Standalone Mode enabled ======"); - NSArray *books = [IssuesManager localBooksList]; - if ([books count] == 1) { - self.rootViewController = [[[BakerViewController alloc] initWithBook:[[books objectAtIndex:0] bakerBook]] autorelease]; - } else { - self.rootViewController = [[[ShelfViewController alloc] initWithBooks:books] autorelease]; - } - - #endif - - self.rootNavigationController = [[[UICustomNavigationController alloc] initWithRootViewController:self.rootViewController] autorelease]; - UICustomNavigationBar *navigationBar = (UICustomNavigationBar *)self.rootNavigationController.navigationBar; - - if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")) { - // Background is 64px high: in iOS7, it will be used as the background for the status bar as well. - [navigationBar setTintColor:[UIColor colorWithHexString:ISSUES_ACTION_BUTTON_BACKGROUND_COLOR]]; - [navigationBar setBarTintColor:[UIColor colorWithHexString:@"ffffff"]]; - [navigationBar setBackgroundImage:[UIImage imageNamed:@"navigation-bar-bg"] forBarMetrics:UIBarMetricsDefault]; - navigationBar.titleTextAttributes = [NSDictionary dictionaryWithObject:[UIColor colorWithHexString:@"000000"] forKey:UITextAttributeTextColor]; - } else { - // Background is 44px: in iOS6 and below, a higher background image would make the navigation bar - // appear higher than it should be. - [navigationBar setBackgroundImage:[UIImage imageNamed:@"navigation-bar-bg-ios6"] forBarMetrics:UIBarMetricsDefault]; - [navigationBar setTintColor:[UIColor colorWithHexString:@"333333"]]; // black will not trigger a pushed status - } - - self.window = [[[InterceptorWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; - self.window.backgroundColor = [UIColor whiteColor]; - - self.window.rootViewController = self.rootNavigationController; - [self.window makeKeyAndVisible]; - - - // ****** Analytics Setup - [BakerAnalyticsEvents sharedInstance]; // Initialization - [[NSNotificationCenter defaultCenter] postNotificationName:@"BakerApplicationStart" object:self]; // -> Baker Analytics Event - - return YES; -} - -#ifdef BAKER_NEWSSTAND -- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken -{ - NSString *apnsToken = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]]; - apnsToken = [apnsToken stringByReplacingOccurrencesOfString:@" " withString:@""]; - - NSLog(@"[AppDelegate] My token (as NSData) is: %@", deviceToken); - NSLog(@"[AppDelegate] My token (as NSString) is: %@", apnsToken); - - [[NSUserDefaults standardUserDefaults] setObject:apnsToken forKey:@"apns_token"]; - [[NSUserDefaults standardUserDefaults] synchronize]; - - BakerAPI *api = [BakerAPI sharedInstance]; - [api postAPNSToken:apnsToken]; -} -#endif - -- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error -{ - NSLog(@"[AppDelegate] Push Notification - Device Token, review: %@", error); -} - -- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo -{ - #ifdef BAKER_NEWSSTAND - NSDictionary *aps = [userInfo objectForKey:@"aps"]; - if (aps && [aps objectForKey:@"content-available"]) { - [self applicationWillHandleNewsstandNotificationOfContent:[userInfo objectForKey:@"content-name"]]; - } - #endif -} -- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler -{ - #ifdef BAKER_NEWSSTAND - NSDictionary *aps = [userInfo objectForKey:@"aps"]; - if (aps && [aps objectForKey:@"content-available"]) { - [self applicationWillHandleNewsstandNotificationOfContent:[userInfo objectForKey:@"content-name"]]; - } - #endif -} -- (void)applicationWillHandleNewsstandNotificationOfContent:(NSString *)contentName -{ - #ifdef BAKER_NEWSSTAND - IssuesManager *issuesManager = [IssuesManager sharedInstance]; - PurchasesManager *purchasesManager = [PurchasesManager sharedInstance]; - __block BakerIssue *targetIssue = nil; - - [issuesManager refresh:^(BOOL status) { - if (contentName) { - for (BakerIssue *issue in issuesManager.issues) { - if ([issue.ID isEqualToString:contentName]) { - targetIssue = issue; - break; - } - } - } else { - targetIssue = [issuesManager.issues objectAtIndex:0]; - } - - [purchasesManager retrievePurchasesFor:[issuesManager productIDs] withCallback:^(NSDictionary *_purchases) { - - NSString *targetStatus = [targetIssue getStatus]; - NSLog(@"[AppDelegate] Push Notification - Target status: %@", targetStatus); - - if ([targetStatus isEqualToString:@"remote"] || [targetStatus isEqualToString:@"purchased"]) { - [targetIssue download]; - } else if ([targetStatus isEqualToString:@"purchasable"] || [targetStatus isEqualToString:@"unpriced"]) { - NSLog(@"[AppDelegate] Push Notification - You are not entitled to download issue '%@', issue not purchased yet", targetIssue.ID); - } else if (![targetStatus isEqualToString:@"remote"]) { - NSLog(@"[AppDelegate] Push Notification - Issue '%@' in download or already downloaded", targetIssue.ID); - } - }]; - }]; - #endif -} -- (void)applicationWillResignActive:(UIApplication *)application -{ - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. - [[NSNotificationCenter defaultCenter] postNotificationName:@"applicationWillResignActiveNotification" object:nil]; -} - -- (void)applicationDidEnterBackground:(UIApplication *)application -{ - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - - #ifdef BAKER_NEWSSTAND - // Everything that happened while the application was opened can be considered as "seen" - [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; - #endif -} - -- (void)applicationWillEnterForeground:(UIApplication *)application -{ - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. -} - -- (void)applicationDidBecomeActive:(UIApplication *)application -{ - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - - #ifdef BAKER_NEWSSTAND - // Opening the application means all new items can be considered as "seen". - [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; - #endif -} - -- (void)applicationWillTerminate:(UIApplication *)application -{ - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. -} - -@end diff --git a/Baker/AppDelegate.h b/Baker/BKRAppDelegate.h similarity index 81% rename from Baker/AppDelegate.h rename to Baker/BKRAppDelegate.h index 6a680718..e4898178 100644 --- a/Baker/AppDelegate.h +++ b/Baker/BKRAppDelegate.h @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -31,13 +32,13 @@ #import -#import "InterceptorWindow.h" -#import "ShelfViewController.h" +#import "BKRInterceptorWindow.h" +#import "BKRShelfViewController.h" -@interface AppDelegate : UIResponder +@interface BKRAppDelegate : UIResponder -@property (strong, nonatomic) InterceptorWindow *window; -@property (strong, nonatomic) UIViewController *rootViewController; -@property (strong, nonatomic) UINavigationController *rootNavigationController; +@property (nonatomic, strong) BKRInterceptorWindow *window; +@property (nonatomic, strong) UIViewController *rootViewController; +@property (nonatomic, strong) UINavigationController *rootNavigationController; @end diff --git a/Baker/BKRAppDelegate.m b/Baker/BKRAppDelegate.m new file mode 100644 index 00000000..d091af09 --- /dev/null +++ b/Baker/BKRAppDelegate.m @@ -0,0 +1,271 @@ +// +// AppDelegate.m +// Baker +// +// ========================================================================================== +// +// Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout +// 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 Baker Framework nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written +// permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#import "BKRAppDelegate.h" +#import "BKRCustomNavigationController.h" +#import "BKRCustomNavigationBar.h" +#import "BKRIssuesManager.h" +#import "BKRBakerAPI.h" +#import "UIColor+BakerExtensions.h" +#import "BKRUtils.h" + +#import "BKRSettings.h" +#import "BKRBookViewController.h" +#import "BKRAnalyticsEvents.h" + +#pragma mark - Initialization + +@implementation BKRAppDelegate + ++ (void)initialize { + // Set user agent (the only problem is that we can't modify the User-Agent later in the program) + // We use a more browser-like User-Agent in order to allow browser detection scripts to run (like Tumult Hype). + NSDictionary *userAgent = @{@"UserAgent": @"Mozilla/5.0 (compatible; BakerFramework) AppleWebKit/533.00+ (KHTML, like Gecko) Mobile"}; + [[NSUserDefaults standardUserDefaults] registerDefaults:userAgent]; +} + +- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { + + if ([BKRSettings sharedSettings].isNewsstand) { + [self configureNewsstandApp:application options:launchOptions]; + } else { + [self configureStandAloneApp:application options:launchOptions]; + } + + self.rootNavigationController = [[BKRCustomNavigationController alloc] initWithRootViewController:self.rootViewController]; + + [self configureNavigationBar]; + [self configureAnalytics]; + + self.window = [[BKRInterceptorWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + self.window.backgroundColor = [UIColor whiteColor]; + self.window.rootViewController = self.rootNavigationController; + [self.window makeKeyAndVisible]; + + return YES; +} + +- (void)configureNewsstandApp:(UIApplication*)application options:(NSDictionary*)launchOptions { + + NSLog(@"====== Baker Newsstand Mode enabled ======"); + [BKRBakerAPI generateUUIDOnce]; + + // Let the device know we want to handle Newsstand push notifications + if ([application respondsToSelector:@selector(registerUserNotificationSettings:)]) { + UIUserNotificationSettings *notificationSettings = [UIUserNotificationSettings settingsForTypes:(UIRemoteNotificationTypeNewsstandContentAvailability|UIRemoteNotificationTypeBadge|UIRemoteNotificationTypeSound|UIRemoteNotificationTypeAlert) categories:nil]; + [application registerUserNotificationSettings:notificationSettings]; + } else { + [application registerForRemoteNotificationTypes:(UIRemoteNotificationTypeNewsstandContentAvailability|UIRemoteNotificationTypeBadge|UIRemoteNotificationTypeSound|UIRemoteNotificationTypeAlert)]; + } + +#ifdef DEBUG + // For debug only... so that you can download multiple issues per day during development + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"NKDontThrottleNewsstandContentNotifications"]; + [[NSUserDefaults standardUserDefaults] synchronize]; +#endif + + // Check if the app is runnig in response to a notification + NSDictionary *payload = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]; + if (payload) { + NSDictionary *aps = payload[@"aps"]; + if (aps && aps[@"content-available"]) { + + __block UIBackgroundTaskIdentifier backgroundTask = [application beginBackgroundTaskWithExpirationHandler:^{ + [application endBackgroundTask:backgroundTask]; + backgroundTask = UIBackgroundTaskInvalid; + }]; + + // Credit where credit is due. This semaphore solution found here: + // http://stackoverflow.com/a/4326754/2998 + dispatch_semaphore_t sema = NULL; + sema = dispatch_semaphore_create(0); + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ + [self applicationWillHandleNewsstandNotificationOfContent:payload[@"content-name"]]; + [application endBackgroundTask:backgroundTask]; + backgroundTask = UIBackgroundTaskInvalid; + dispatch_semaphore_signal(sema); + }); + + dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); + } + } + + self.rootViewController = [[BKRShelfViewController alloc] init]; + +} + +- (void)configureStandAloneApp:(UIApplication*)application options:(NSDictionary*)launchOptions { + + NSLog(@"====== Baker Standalone Mode enabled ======"); + NSArray *books = [BKRIssuesManager localBooksList]; + if (books.count == 1) { + BKRBook *book = [books[0] bakerBook]; + self.rootViewController = [[BKRBookViewController alloc] initWithBook:book]; + } else { + self.rootViewController = [[BKRShelfViewController alloc] initWithBooks:books]; + } + +} + +- (void)configureNavigationBar { + BKRCustomNavigationBar *navigationBar = (BKRCustomNavigationBar*)self.rootNavigationController.navigationBar; + navigationBar.tintColor = [UIColor bkrColorWithHexString:[BKRSettings sharedSettings].issuesActionBackgroundColor]; + navigationBar.barTintColor = [UIColor bkrColorWithHexString:@"ffffff"]; + navigationBar.titleTextAttributes = @{NSForegroundColorAttributeName: [UIColor bkrColorWithHexString:@"000000"]}; + [navigationBar setBackgroundImage:[UIImage imageNamed:@"navigation-bar-bg"] forBarMetrics:UIBarMetricsDefault]; +} + +- (void)configureAnalytics { + [BKRAnalyticsEvents sharedInstance]; // Initialization + [[NSNotificationCenter defaultCenter] postNotificationName:@"BakerApplicationStart" object:self]; // -> Baker Analytics Event +} + +#pragma mark - Push Notifications + +- (void)application:(UIApplication*)application didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings { + [application registerForRemoteNotifications]; +} + +- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error { + NSLog(@"[AppDelegate] Push Notification - Device Token, review: %@", error); +} + +- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken { + + if (![BKRSettings sharedSettings].isNewsstand) { + return; + } + + NSString *apnsToken = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]]; + apnsToken = [apnsToken stringByReplacingOccurrencesOfString:@" " withString:@""]; + + NSLog(@"[AppDelegate] My token (as NSData) is: %@", deviceToken); + NSLog(@"[AppDelegate] My token (as NSString) is: %@", apnsToken); + + [[NSUserDefaults standardUserDefaults] setObject:apnsToken forKey:@"apns_token"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + + BKRBakerAPI *api = [BKRBakerAPI sharedInstance]; + [api postAPNSToken:apnsToken]; + +} + +- (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo { + + if (![BKRSettings sharedSettings].isNewsstand) { + return; + } + + NSDictionary *aps = userInfo[@"aps"]; + if (aps && aps[@"content-available"]) { + [self applicationWillHandleNewsstandNotificationOfContent:userInfo[@"content-name"]]; + } + +} + +/* +- (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo fetchCompletionHandler:(void(^)(UIBackgroundFetchResult result))handler { + + if (![BKRSettings sharedSettings].isNewsstand) { + return; + } + + NSDictionary *aps = userInfo[@"aps"]; + if (aps && aps[@"content-available"]) { + [self applicationWillHandleNewsstandNotificationOfContent:userInfo[@"content-name"]]; + } + +} +*/ + +- (void)applicationWillHandleNewsstandNotificationOfContent:(NSString*)contentName { + + if (![BKRSettings sharedSettings].isNewsstand) { + return; + } + + BKRIssuesManager *issuesManager = [BKRIssuesManager sharedInstance]; + BKRPurchasesManager *purchasesManager = [BKRPurchasesManager sharedInstance]; + __block BKRIssue *targetIssue = nil; + + [issuesManager refresh:^(BOOL status) { + if (contentName) { + for (BKRIssue *issue in issuesManager.issues) { + if ([issue.ID isEqualToString:contentName]) { + targetIssue = issue; + break; + } + } + } else { + targetIssue = (issuesManager.issues)[0]; + } + + [purchasesManager retrievePurchasesFor:issuesManager.productIDs withCallback:^(NSDictionary *_purchases) { + + NSString *targetStatus = [targetIssue getStatus]; + NSLog(@"[AppDelegate] Push Notification - Target status: %@", targetStatus); + + if ([targetStatus isEqualToString:@"remote"] || [targetStatus isEqualToString:@"purchased"]) { + [targetIssue download]; + } else if ([targetStatus isEqualToString:@"purchasable"] || [targetStatus isEqualToString:@"unpriced"]) { + NSLog(@"[AppDelegate] Push Notification - You are not entitled to download issue '%@', issue not purchased yet", targetIssue.ID); + } else if (![targetStatus isEqualToString:@"remote"]) { + NSLog(@"[AppDelegate] Push Notification - Issue '%@' in download or already downloaded", targetIssue.ID); + } + }]; + }]; + +} + +#pragma mark - Application Lifecycle + +- (void)applicationWillResignActive:(UIApplication*)application { + [[NSNotificationCenter defaultCenter] postNotificationName:@"applicationWillResignActiveNotification" object:nil]; +} + +- (void)applicationDidEnterBackground:(UIApplication*)application { + [self resetApplicationBadge]; +} + +- (void)applicationDidBecomeActive:(UIApplication*)application { + [self resetApplicationBadge]; +} + +- (void)resetApplicationBadge { + if (![BKRSettings sharedSettings].isNewsstand) { + return; + } + [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; +} + +@end diff --git a/Baker/Baker-Info.plist b/Baker/Baker-Info.plist index 0e21a471..dc8b6268 100644 --- a/Baker/Baker-Info.plist +++ b/Baker/Baker-Info.plist @@ -31,11 +31,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 4.2 + 4.3 CFBundleSignature ???? CFBundleVersion - 4.2 + 4.3 LSRequiresIPhoneOS UIBackgroundModes @@ -49,6 +49,8 @@ armv7 + UIStatusBarHidden + UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/Baker/Baker-Prefix.pch b/Baker/Baker-Prefix.pch index 13d3a857..697b53ba 100644 --- a/Baker/Baker-Prefix.pch +++ b/Baker/Baker-Prefix.pch @@ -4,8 +4,8 @@ #import -#ifndef __IPHONE_3_0 -#warning "This project uses features only available in iOS SDK 3.0 and later." +#ifndef __IPHONE_8_1 + #error "Baker expects you to use iOS SDK 8.1. #endif #ifdef __OBJC__ diff --git a/Baker/BakerAssets.xcassets/AppIcon.appiconset/Contents.json b/Baker/BakerAssets.xcassets/AppIcon.appiconset/Contents.json index 4886f96c..777db218 100644 --- a/Baker/BakerAssets.xcassets/AppIcon.appiconset/Contents.json +++ b/Baker/BakerAssets.xcassets/AppIcon.appiconset/Contents.json @@ -33,6 +33,12 @@ "filename" : "iPhone App iOS7@2x.png", "scale" : "2x" }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "iPhone-App-iOS7@3x.png", + "scale" : "3x" + }, { "idiom" : "ipad", "size" : "29x29", diff --git a/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPad App iOS6.png b/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPad App iOS6.png index aab1e12f..44fc1a76 100644 Binary files a/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPad App iOS6.png and b/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPad App iOS6.png differ diff --git a/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPad App iOS6@2x.png b/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPad App iOS6@2x.png index 2f129b05..d98b22b6 100644 Binary files a/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPad App iOS6@2x.png and b/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPad App iOS6@2x.png differ diff --git a/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPad App iOS7.png b/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPad App iOS7.png index 1a97d56c..a8958056 100644 Binary files a/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPad App iOS7.png and b/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPad App iOS7.png differ diff --git a/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPad App iOS7@2x.png b/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPad App iOS7@2x.png index 02168545..8c43da6f 100644 Binary files a/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPad App iOS7@2x.png and b/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPad App iOS7@2x.png differ diff --git a/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPhone App iOS6.png b/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPhone App iOS6.png index ac5ce31a..152fbda1 100644 Binary files a/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPhone App iOS6.png and b/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPhone App iOS6.png differ diff --git a/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPhone App iOS6@2x.png b/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPhone App iOS6@2x.png index 21332eb7..8f81d88e 100644 Binary files a/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPhone App iOS6@2x.png and b/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPhone App iOS6@2x.png differ diff --git a/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPhone App iOS7@2x.png b/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPhone App iOS7@2x.png index dc941f35..c967a4d3 100644 Binary files a/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPhone App iOS7@2x.png and b/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPhone App iOS7@2x.png differ diff --git a/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPhone-App-iOS7@3x.png b/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPhone-App-iOS7@3x.png new file mode 100644 index 00000000..25fb7c7b Binary files /dev/null and b/Baker/BakerAssets.xcassets/AppIcon.appiconset/iPhone-App-iOS7@3x.png differ diff --git a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/Contents.json b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/Contents.json index fab9d9d3..60ae2711 100644 --- a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/Contents.json +++ b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/Contents.json @@ -1,5 +1,32 @@ { "images" : [ + { + "extent" : "full-screen", + "idiom" : "iphone", + "subtype" : "736h", + "filename" : "iPhone-Portrait-iOS7-R55.png", + "minimum-system-version" : "8.0", + "orientation" : "portrait", + "scale" : "3x" + }, + { + "extent" : "full-screen", + "idiom" : "iphone", + "subtype" : "736h", + "filename" : "iPhone-Landscape-iOS7-R55.png", + "minimum-system-version" : "8.0", + "orientation" : "landscape", + "scale" : "3x" + }, + { + "extent" : "full-screen", + "idiom" : "iphone", + "subtype" : "667h", + "filename" : "iPhone-Portrait-iOS7-R47.png", + "minimum-system-version" : "8.0", + "orientation" : "portrait", + "scale" : "2x" + }, { "orientation" : "portrait", "idiom" : "iphone", diff --git a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Landscape iOS6 no status bar.png b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Landscape iOS6 no status bar.png index d1b20fb1..928ac7fe 100644 Binary files a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Landscape iOS6 no status bar.png and b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Landscape iOS6 no status bar.png differ diff --git a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Landscape iOS6 no status bar@2x-1.png b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Landscape iOS6 no status bar@2x-1.png index 46024f5d..90766dba 100644 Binary files a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Landscape iOS6 no status bar@2x-1.png and b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Landscape iOS6 no status bar@2x-1.png differ diff --git a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Landscape iOS7.png b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Landscape iOS7.png index 89a2fc85..70bf2a1e 100644 Binary files a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Landscape iOS7.png and b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Landscape iOS7.png differ diff --git a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Landscape iOS7@2x.png b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Landscape iOS7@2x.png index 797725e6..a793aeab 100644 Binary files a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Landscape iOS7@2x.png and b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Landscape iOS7@2x.png differ diff --git a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Portrait iOS6 no status bar.png b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Portrait iOS6 no status bar.png index a4969194..84d9d5b4 100644 Binary files a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Portrait iOS6 no status bar.png and b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Portrait iOS6 no status bar.png differ diff --git a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Portrait iOS6 no status bar@2x.png b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Portrait iOS6 no status bar@2x.png index 217a96bf..013364e5 100644 Binary files a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Portrait iOS6 no status bar@2x.png and b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Portrait iOS6 no status bar@2x.png differ diff --git a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Portrait iOS7.png b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Portrait iOS7.png index ece39d23..fb60835e 100644 Binary files a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Portrait iOS7.png and b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Portrait iOS7.png differ diff --git a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Portrait iOS7@2x.png b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Portrait iOS7@2x.png index adc28d1e..b0e47d9e 100644 Binary files a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Portrait iOS7@2x.png and b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPad Portrait iOS7@2x.png differ diff --git a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPhone Portrait iOS7 R4.png b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPhone Portrait iOS7 R4.png index ea0df7db..bea0a179 100644 Binary files a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPhone Portrait iOS7 R4.png and b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPhone Portrait iOS7 R4.png differ diff --git a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPhone Portrait iOS7@2x.png b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPhone Portrait iOS7@2x.png index ea0c3492..e510e5e1 100644 Binary files a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPhone Portrait iOS7@2x.png and b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPhone Portrait iOS7@2x.png differ diff --git a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPhone-Landscape-iOS7-R55.png b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPhone-Landscape-iOS7-R55.png new file mode 100644 index 00000000..48eff502 Binary files /dev/null and b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPhone-Landscape-iOS7-R55.png differ diff --git a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPhone-Portrait-iOS7-R47.png b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPhone-Portrait-iOS7-R47.png new file mode 100644 index 00000000..fda3b2d1 Binary files /dev/null and b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPhone-Portrait-iOS7-R47.png differ diff --git a/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPhone-Portrait-iOS7-R55.png b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPhone-Portrait-iOS7-R55.png new file mode 100644 index 00000000..2eb3b482 Binary files /dev/null and b/Baker/BakerAssets.xcassets/LaunchImage.launchimage/iPhone-Portrait-iOS7-R55.png differ diff --git a/Baker/BakerAssets.xcassets/back.imageset/Contents.json b/Baker/BakerAssets.xcassets/back.imageset/Contents.json index 36f504fc..0eaf7cb1 100644 --- a/Baker/BakerAssets.xcassets/back.imageset/Contents.json +++ b/Baker/BakerAssets.xcassets/back.imageset/Contents.json @@ -9,6 +9,10 @@ "idiom" : "universal", "scale" : "2x", "filename" : "back@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" } ], "info" : { diff --git a/Baker/BakerAssets.xcassets/forward.imageset/Contents.json b/Baker/BakerAssets.xcassets/forward.imageset/Contents.json index e721b14f..590096e0 100644 --- a/Baker/BakerAssets.xcassets/forward.imageset/Contents.json +++ b/Baker/BakerAssets.xcassets/forward.imageset/Contents.json @@ -9,6 +9,10 @@ "idiom" : "universal", "scale" : "2x", "filename" : "forward@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" } ], "info" : { diff --git a/Baker/BakerAssets.xcassets/navigation-bar-bg-ios6.imageset/Contents.json b/Baker/BakerAssets.xcassets/navigation-bar-bg-ios6.imageset/Contents.json index 5f159b7c..8e889985 100644 --- a/Baker/BakerAssets.xcassets/navigation-bar-bg-ios6.imageset/Contents.json +++ b/Baker/BakerAssets.xcassets/navigation-bar-bg-ios6.imageset/Contents.json @@ -9,6 +9,10 @@ "idiom" : "universal", "scale" : "2x", "filename" : "navigation-bar-bg-ios6@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" } ], "info" : { diff --git a/Baker/BakerAssets.xcassets/navigation-bar-bg.imageset/Contents.json b/Baker/BakerAssets.xcassets/navigation-bar-bg.imageset/Contents.json index 45147761..52ebad33 100644 --- a/Baker/BakerAssets.xcassets/navigation-bar-bg.imageset/Contents.json +++ b/Baker/BakerAssets.xcassets/navigation-bar-bg.imageset/Contents.json @@ -9,6 +9,10 @@ "idiom" : "universal", "scale" : "2x", "filename" : "navigation-bar-bg@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" } ], "info" : { diff --git a/Baker/BakerAssets.xcassets/newsstand-app-icon.imageset/Contents.json b/Baker/BakerAssets.xcassets/newsstand-app-icon.imageset/Contents.json index 0ed40486..83fc0756 100644 --- a/Baker/BakerAssets.xcassets/newsstand-app-icon.imageset/Contents.json +++ b/Baker/BakerAssets.xcassets/newsstand-app-icon.imageset/Contents.json @@ -9,6 +9,10 @@ "idiom" : "universal", "scale" : "2x", "filename" : "newsstand-app-icon@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" } ], "info" : { diff --git a/Baker/BakerAssets.xcassets/newsstand-app-icon.imageset/newsstand-app-icon.png b/Baker/BakerAssets.xcassets/newsstand-app-icon.imageset/newsstand-app-icon.png index 56152fbc..cd18257a 100644 Binary files a/Baker/BakerAssets.xcassets/newsstand-app-icon.imageset/newsstand-app-icon.png and b/Baker/BakerAssets.xcassets/newsstand-app-icon.imageset/newsstand-app-icon.png differ diff --git a/Baker/BakerAssets.xcassets/newsstand-app-icon.imageset/newsstand-app-icon@2x.png b/Baker/BakerAssets.xcassets/newsstand-app-icon.imageset/newsstand-app-icon@2x.png index bb3e3a57..4c830b29 100644 Binary files a/Baker/BakerAssets.xcassets/newsstand-app-icon.imageset/newsstand-app-icon@2x.png and b/Baker/BakerAssets.xcassets/newsstand-app-icon.imageset/newsstand-app-icon@2x.png differ diff --git a/Baker/BakerAssets.xcassets/shelf-background.imageset/Contents.json b/Baker/BakerAssets.xcassets/shelf-background.imageset/Contents.json new file mode 100644 index 00000000..6466baff --- /dev/null +++ b/Baker/BakerAssets.xcassets/shelf-background.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "shelf-pattern.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "shelf-pattern@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "shelf-pattern@2x-1.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Baker/BakerAssets.xcassets/shelf-background.imageset/shelf-pattern.png b/Baker/BakerAssets.xcassets/shelf-background.imageset/shelf-pattern.png new file mode 100644 index 00000000..63b6fa13 Binary files /dev/null and b/Baker/BakerAssets.xcassets/shelf-background.imageset/shelf-pattern.png differ diff --git a/Baker/BakerAssets.xcassets/shelf-background.imageset/shelf-pattern@2x-1.png b/Baker/BakerAssets.xcassets/shelf-background.imageset/shelf-pattern@2x-1.png new file mode 100644 index 00000000..171541a4 Binary files /dev/null and b/Baker/BakerAssets.xcassets/shelf-background.imageset/shelf-pattern@2x-1.png differ diff --git a/Baker/BakerAssets.xcassets/shelf-background.imageset/shelf-pattern@2x.png b/Baker/BakerAssets.xcassets/shelf-background.imageset/shelf-pattern@2x.png new file mode 100644 index 00000000..171541a4 Binary files /dev/null and b/Baker/BakerAssets.xcassets/shelf-background.imageset/shelf-pattern@2x.png differ diff --git a/Baker/BakerAssets.xcassets/shelf-bg-landscape.imageset/Contents.json b/Baker/BakerAssets.xcassets/shelf-bg-landscape.imageset/Contents.json deleted file mode 100644 index d174779b..00000000 --- a/Baker/BakerAssets.xcassets/shelf-bg-landscape.imageset/Contents.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "scale" : "1x", - "filename" : "shelf-bg-landscape~iphone.png" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "filename" : "shelf-bg-landscape@2x~iphone.png" - }, - { - "idiom" : "iphone", - "filename" : "shelf-bg-landscape-568h.png", - "subtype" : "retina4", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "filename" : "shelf-bg-landscape~ipad.png" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "filename" : "shelf-bg-landscape@2x~ipad.png" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Baker/BakerAssets.xcassets/shelf-bg-landscape.imageset/shelf-bg-landscape-568h.png b/Baker/BakerAssets.xcassets/shelf-bg-landscape.imageset/shelf-bg-landscape-568h.png deleted file mode 100644 index 31a7d9b4..00000000 Binary files a/Baker/BakerAssets.xcassets/shelf-bg-landscape.imageset/shelf-bg-landscape-568h.png and /dev/null differ diff --git a/Baker/BakerAssets.xcassets/shelf-bg-landscape.imageset/shelf-bg-landscape@2x~ipad.png b/Baker/BakerAssets.xcassets/shelf-bg-landscape.imageset/shelf-bg-landscape@2x~ipad.png deleted file mode 100644 index 4dc8fa1c..00000000 Binary files a/Baker/BakerAssets.xcassets/shelf-bg-landscape.imageset/shelf-bg-landscape@2x~ipad.png and /dev/null differ diff --git a/Baker/BakerAssets.xcassets/shelf-bg-landscape.imageset/shelf-bg-landscape@2x~iphone.png b/Baker/BakerAssets.xcassets/shelf-bg-landscape.imageset/shelf-bg-landscape@2x~iphone.png deleted file mode 100644 index eb2a30f5..00000000 Binary files a/Baker/BakerAssets.xcassets/shelf-bg-landscape.imageset/shelf-bg-landscape@2x~iphone.png and /dev/null differ diff --git a/Baker/BakerAssets.xcassets/shelf-bg-landscape.imageset/shelf-bg-landscape~ipad.png b/Baker/BakerAssets.xcassets/shelf-bg-landscape.imageset/shelf-bg-landscape~ipad.png deleted file mode 100644 index 206f1124..00000000 Binary files a/Baker/BakerAssets.xcassets/shelf-bg-landscape.imageset/shelf-bg-landscape~ipad.png and /dev/null differ diff --git a/Baker/BakerAssets.xcassets/shelf-bg-landscape.imageset/shelf-bg-landscape~iphone.png b/Baker/BakerAssets.xcassets/shelf-bg-landscape.imageset/shelf-bg-landscape~iphone.png deleted file mode 100644 index 27b1873d..00000000 Binary files a/Baker/BakerAssets.xcassets/shelf-bg-landscape.imageset/shelf-bg-landscape~iphone.png and /dev/null differ diff --git a/Baker/BakerAssets.xcassets/shelf-bg-portrait.imageset/shelf-bg-portrait-568h.png b/Baker/BakerAssets.xcassets/shelf-bg-portrait.imageset/shelf-bg-portrait-568h.png deleted file mode 100644 index ea47cd63..00000000 Binary files a/Baker/BakerAssets.xcassets/shelf-bg-portrait.imageset/shelf-bg-portrait-568h.png and /dev/null differ diff --git a/Baker/BakerAssets.xcassets/shelf-bg-portrait.imageset/shelf-bg-portrait@2x~ipad.png b/Baker/BakerAssets.xcassets/shelf-bg-portrait.imageset/shelf-bg-portrait@2x~ipad.png deleted file mode 100644 index 75c359bf..00000000 Binary files a/Baker/BakerAssets.xcassets/shelf-bg-portrait.imageset/shelf-bg-portrait@2x~ipad.png and /dev/null differ diff --git a/Baker/BakerAssets.xcassets/shelf-bg-portrait.imageset/shelf-bg-portrait@2x~iphone.png b/Baker/BakerAssets.xcassets/shelf-bg-portrait.imageset/shelf-bg-portrait@2x~iphone.png deleted file mode 100644 index c740e1fc..00000000 Binary files a/Baker/BakerAssets.xcassets/shelf-bg-portrait.imageset/shelf-bg-portrait@2x~iphone.png and /dev/null differ diff --git a/Baker/BakerAssets.xcassets/shelf-bg-portrait.imageset/shelf-bg-portrait~ipad.png b/Baker/BakerAssets.xcassets/shelf-bg-portrait.imageset/shelf-bg-portrait~ipad.png deleted file mode 100644 index 443b9d01..00000000 Binary files a/Baker/BakerAssets.xcassets/shelf-bg-portrait.imageset/shelf-bg-portrait~ipad.png and /dev/null differ diff --git a/Baker/BakerAssets.xcassets/shelf-bg-portrait.imageset/shelf-bg-portrait~iphone.png b/Baker/BakerAssets.xcassets/shelf-bg-portrait.imageset/shelf-bg-portrait~iphone.png deleted file mode 100644 index 2d0630b0..00000000 Binary files a/Baker/BakerAssets.xcassets/shelf-bg-portrait.imageset/shelf-bg-portrait~iphone.png and /dev/null differ diff --git a/Baker/BakerAssets.xcassets/shelf-bg-portrait.imageset/Contents.json b/Baker/BakerAssets.xcassets/shelf-header.imageset/Contents.json similarity index 54% rename from Baker/BakerAssets.xcassets/shelf-bg-portrait.imageset/Contents.json rename to Baker/BakerAssets.xcassets/shelf-header.imageset/Contents.json index 71e4db34..6ea00902 100644 --- a/Baker/BakerAssets.xcassets/shelf-bg-portrait.imageset/Contents.json +++ b/Baker/BakerAssets.xcassets/shelf-header.imageset/Contents.json @@ -3,28 +3,27 @@ { "idiom" : "iphone", "scale" : "1x", - "filename" : "shelf-bg-portrait~iphone.png" + "filename" : "shelf-header.png" }, { "idiom" : "iphone", "scale" : "2x", - "filename" : "shelf-bg-portrait@2x~iphone.png" + "filename" : "shelf-header@2x.png" }, { "idiom" : "iphone", - "filename" : "shelf-bg-portrait-568h.png", - "subtype" : "retina4", - "scale" : "2x" + "scale" : "3x", + "filename" : "shelf-header@2x-1.png" }, { "idiom" : "ipad", "scale" : "1x", - "filename" : "shelf-bg-portrait~ipad.png" + "filename" : "shelf-header-1.png" }, { "idiom" : "ipad", "scale" : "2x", - "filename" : "shelf-bg-portrait@2x~ipad.png" + "filename" : "shelf-header@2x-2.png" } ], "info" : { diff --git a/Baker/BakerAssets.xcassets/shelf-header.imageset/shelf-header-1.png b/Baker/BakerAssets.xcassets/shelf-header.imageset/shelf-header-1.png new file mode 100644 index 00000000..5a8071d8 Binary files /dev/null and b/Baker/BakerAssets.xcassets/shelf-header.imageset/shelf-header-1.png differ diff --git a/Baker/BakerAssets.xcassets/shelf-header.imageset/shelf-header.png b/Baker/BakerAssets.xcassets/shelf-header.imageset/shelf-header.png new file mode 100644 index 00000000..5a8071d8 Binary files /dev/null and b/Baker/BakerAssets.xcassets/shelf-header.imageset/shelf-header.png differ diff --git a/Baker/BakerAssets.xcassets/shelf-header.imageset/shelf-header@2x-1.png b/Baker/BakerAssets.xcassets/shelf-header.imageset/shelf-header@2x-1.png new file mode 100644 index 00000000..68b44034 Binary files /dev/null and b/Baker/BakerAssets.xcassets/shelf-header.imageset/shelf-header@2x-1.png differ diff --git a/Baker/BakerAssets.xcassets/shelf-header.imageset/shelf-header@2x-2.png b/Baker/BakerAssets.xcassets/shelf-header.imageset/shelf-header@2x-2.png new file mode 100644 index 00000000..68b44034 Binary files /dev/null and b/Baker/BakerAssets.xcassets/shelf-header.imageset/shelf-header@2x-2.png differ diff --git a/Baker/BakerAssets.xcassets/shelf-header.imageset/shelf-header@2x.png b/Baker/BakerAssets.xcassets/shelf-header.imageset/shelf-header@2x.png new file mode 100644 index 00000000..68b44034 Binary files /dev/null and b/Baker/BakerAssets.xcassets/shelf-header.imageset/shelf-header@2x.png differ diff --git a/Baker/en.lproj/Localizable.strings b/Baker/en.lproj/Localizable.strings index cc8636ec..c6060700 100644 --- a/Baker/en.lproj/Localizable.strings +++ b/Baker/en.lproj/Localizable.strings @@ -103,3 +103,11 @@ // (These are just examples: you'll need to provide your own product IDs and names) //"com.example.MyBook.subscription.3months" = "3 Months Subscription"; //"com.example.MyBook.subscription.6months" = "Half Year Subscription"; + +// Categories +"ALL_CATEGORIES_TITLE" = "All Categories"; + +//Social share button +//Im' reading the ISSUE_NAME from APPNAME. Checkit at BOOK.URL +"SOCIAL_SHARE_BUTTON_MESSAGE" = "I'm reading \"%@\" from \"%@\". Check it at %@"; + diff --git a/Baker/main.m b/Baker/main.m index a3c74428..ac6c1174 100644 --- a/Baker/main.m +++ b/Baker/main.m @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -31,11 +32,10 @@ #import -#import "AppDelegate.h" +#import "BKRAppDelegate.h" -int main(int argc, char *argv[]) -{ +int main(int argc, char *argv[]) { @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + return UIApplicationMain(argc, argv, nil, NSStringFromClass([BKRAppDelegate class])); } } diff --git a/Baker/newsstand-app-icon.png b/Baker/newsstand-app-icon.png new file mode 100644 index 00000000..a8958056 Binary files /dev/null and b/Baker/newsstand-app-icon.png differ diff --git a/Baker/newsstand-app-icon@2x.png b/Baker/newsstand-app-icon@2x.png new file mode 100644 index 00000000..c967a4d3 Binary files /dev/null and b/Baker/newsstand-app-icon@2x.png differ diff --git a/Baker/newsstand-app-icon@3x.png b/Baker/newsstand-app-icon@3x.png new file mode 100644 index 00000000..25fb7c7b Binary files /dev/null and b/Baker/newsstand-app-icon@3x.png differ diff --git a/Baker/settings.plist b/Baker/settings.plist new file mode 100644 index 00000000..b235b208 --- /dev/null +++ b/Baker/settings.plist @@ -0,0 +1,93 @@ + + + + + useiTunesConnectLocalizations + + isNewsstand + + newsstandLatestIssueCover + + newsstandManifestUrl + http://bakerframework.com/demo/shelf.json + purchaseConfirmationUrl + + purchasesUrl + + postApnsTokenUrl + + freeSubscriptionProductId + + autoRenewableSubscriptionProductIds + + requestTimeout + 15 + issuesCoverBackgroundColor + #ffffff + issuesShelfOptions + + backgroundFillStyle + Color + backgroundFillGradientStart + #FFFFFF + backgroundFillGradientStop + #EEEEEE + backgroundFillColor + #FFFFFF + headerSticky + + headerStretch + + headerBackgroundColor + transparent + headerImageFill + + headerHeightPadLandscape + 215 + headerHeightPadPortrait + 215 + headerHeightPhoneLandscape + 107 + headerHeightPhonePortrait + 107 + + issuesTitleFont + Helvetica + issuesTitleFontSize + 15 + issuesTitleColor + #000000 + issuesInfoFont + Helvetica + issuesInfoFontSize + 15 + issuesInfoColor + #929292 + issuesPriceColor + #bc242a + issuesActionFont + Helvetica-Bold + issuesActionFontSize + 11 + issuesActionBackgroundColor + #bc242a + issuesActionButtonColor + #ffffff + issuesArchiveFont + Helvetica-Bold + issuesArchiveFontSize + 11 + issuesArchiveBackgroundColor + #bc242a + issuesArchiveButtonColor + #ffffff + issuesLoadingLabelColor + #bc242a + issuesLoadingSpinnerColor + #929292 + issuesProgressbarTintColor + #bc242a + showSocialShareButton + + + diff --git a/BakerComponents/BKRCategoryFilterItem.h b/BakerComponents/BKRCategoryFilterItem.h new file mode 100644 index 00000000..af4e5224 --- /dev/null +++ b/BakerComponents/BKRCategoryFilterItem.h @@ -0,0 +1,52 @@ +// +// BKRCategoryFilterItem.h +// Baker +// +// ========================================================================================== +// +// Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout, Tobias Strebitzer +// 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 Baker Framework nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written +// permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#import + +@protocol BKRCategoryFilterItemDelegate +@required +- (void)categoryFilterItem:(id)categoryFilterItem clickedAction:(NSString *)action; +@end + +@interface BKRCategoryFilterItem : UIBarButtonItem { + id delegate; +} + +@property (retain) id delegate; +@property (nonatomic, strong) UIActionSheet *categoriesActionSheet; +@property (nonatomic, strong) NSArray *categoriesActionSheetActions; +@property (nonatomic, strong) NSArray *categories; + +-(id)initWithCategories:(NSArray *)aCategories delegate:(NSObject *)aDelegate; +-(id)initWithDelegate:(NSObject *)aDelegate; + +@end \ No newline at end of file diff --git a/BakerComponents/BKRCategoryFilterItem.m b/BakerComponents/BKRCategoryFilterItem.m new file mode 100644 index 00000000..426ae7c9 --- /dev/null +++ b/BakerComponents/BKRCategoryFilterItem.m @@ -0,0 +1,106 @@ +// +// BKRCategoryFilterItem.m +// Baker +// +// ========================================================================================== +// +// Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout, Tobias Strebitzer +// 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 Baker Framework nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written +// permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#import "BKRCategoryFilterItem.h" + +@implementation BKRCategoryFilterItem { + +} + +@synthesize delegate; + +-(id)initWithCategories:(NSArray *)aCategories delegate:(NSObject *)aDelegate { + + // Initialize dropdown + if(self = [super initWithTitle:NSLocalizedString(@"ALL_CATEGORIES_TITLE", nil) style:UIBarButtonItemStylePlain target:self action:@selector(categoryFilterItemTouched:)]) { + + // Set categories + self.categories = aCategories; + + // Set delegate + self.delegate = aDelegate; + } + return self; +} + +-(id)initWithDelegate:(NSObject *)aDelegate { + return [self initWithCategories:[NSArray array] delegate:aDelegate]; +} + +- (IBAction)categoryFilterItemTouched:(UIBarButtonItem *)sender { + if (self.categoriesActionSheet.visible) { + [self.categoriesActionSheet dismissWithClickedButtonIndex:(self.categoriesActionSheet.numberOfButtons - 1) animated:YES]; + } else { + self.categoriesActionSheet = [self buildCategoriesActionSheet]; + [self.categoriesActionSheet showFromBarButtonItem:sender animated:YES]; + } +} + +- (UIActionSheet *)buildCategoriesActionSheet { + UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:nil + delegate:self + cancelButtonTitle:nil + destructiveButtonTitle:nil + otherButtonTitles:nil]; + NSMutableArray *actions = [NSMutableArray array]; + + // Prepend "All Categories" item + [sheet addButtonWithTitle:NSLocalizedString(@"ALL_CATEGORIES_TITLE", nil)]; + [actions addObject:@"reset-filter"]; + + // Add categories + for (NSString *categoryName in self.categories) { + [sheet addButtonWithTitle:categoryName]; + [actions addObject:categoryName]; + } + + self.categoriesActionSheetActions = actions; + + return sheet; +} + + +#pragma mark - Action Sheet Delegates + +- (void) actionSheet:(UIActionSheet*)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex { + if (actionSheet == self.categoriesActionSheet && buttonIndex > -1) { + NSString *action = [self.categoriesActionSheetActions objectAtIndex:buttonIndex]; + if ([action isEqualToString:@"reset-filter"]) { + [self setTitle:NSLocalizedString(@"ALL_CATEGORIES_TITLE", nil)]; + } else { + [self setTitle:action]; + } + [self.delegate categoryFilterItem:self clickedAction:action]; + } +} + +@end diff --git a/BakerShelf/BakerAPI.h b/BakerShelf/BKRBakerAPI.h similarity index 81% rename from BakerShelf/BakerAPI.h rename to BakerShelf/BKRBakerAPI.h index 703da897..88717b76 100644 --- a/BakerShelf/BakerAPI.h +++ b/BakerShelf/BKRBakerAPI.h @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -31,11 +32,11 @@ #import -@interface BakerAPI : NSObject +@interface BKRBakerAPI : NSObject #pragma mark - Singleton -+ (BakerAPI *)sharedInstance; ++ (BKRBakerAPI*)sharedInstance; #pragma mark - Shelf @@ -48,21 +49,21 @@ - (void)getPurchasesJSON:(void (^)(NSData*)) callback ; - (BOOL)canPostPurchaseReceipt; -- (BOOL)postPurchaseReceipt:(NSString *)receipt ofType:(NSString *)type; +- (BOOL)postPurchaseReceipt:(NSString*)receipt ofType:(NSString*)type; #pragma mark - APNS - (BOOL)canPostAPNSToken; -- (BOOL)postAPNSToken:(NSString *)apnsToken; +- (BOOL)postAPNSToken:(NSString*)apnsToken; #pragma mark - User ID + (BOOL)generateUUIDOnce; -+ (NSString *)UUID; ++ (NSString*)UUID; #pragma mark - Helpers -- (NSURLRequest *)requestForURL:(NSURL *)url method:(NSString *)method; -- (NSURLRequest *)requestForURL:(NSURL *)url parameters:(NSDictionary *)parameters method:(NSString *)method cachePolicy:(NSURLRequestCachePolicy)cachePolicy; +- (NSURLRequest*)requestForURL:(NSURL*)url method:(NSString*)method; +- (NSURLRequest*)requestForURL:(NSURL*)url parameters:(NSDictionary*)parameters method:(NSString*)method cachePolicy:(NSURLRequestCachePolicy)cachePolicy; @end diff --git a/BakerShelf/BakerAPI.m b/BakerShelf/BKRBakerAPI.m similarity index 68% rename from BakerShelf/BakerAPI.m rename to BakerShelf/BKRBakerAPI.m index 18b6e6ee..7f32d8e7 100644 --- a/BakerShelf/BakerAPI.m +++ b/BakerShelf/BKRBakerAPI.m @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -29,21 +30,21 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -#import "BakerAPI.h" -#import "Constants.h" -#import "Utils.h" +#import "BKRBakerAPI.h" +#import "BKRUtils.h" +#import "BKRSettings.h" -#import "NSMutableURLRequest+WebServiceClient.h" -#import "NSURL+Extensions.h" -#import "NSString+UUID.h" +#import "NSMutableURLRequest+BakerExtensions.h" +#import "NSURL+BakerExtensions.h" +#import "NSString+BakerExtensions.h" -@implementation BakerAPI +@implementation BKRBakerAPI #pragma mark - Singleton -+ (BakerAPI *)sharedInstance { ++ (BKRBakerAPI*)sharedInstance { static dispatch_once_t once; - static BakerAPI *sharedInstance; + static BKRBakerAPI *sharedInstance; dispatch_once(&once, ^{ sharedInstance = [[self alloc] init]; }); @@ -107,12 +108,10 @@ - (void)getPurchasesJSON:(void (^)(NSData*)) callback { - (BOOL)canPostPurchaseReceipt { return ([self purchaseConfirmationURL] != nil); } -- (BOOL)postPurchaseReceipt:(NSString *)receipt ofType:(NSString *)type { +- (BOOL)postPurchaseReceipt:(NSString*)receipt ofType:(NSString*)type { if ([self canPostPurchaseReceipt]) { - NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys: - type, @"type", - receipt, @"receipt_data", - nil]; + NSDictionary *params = @{@"type": type, + @"receipt_data": receipt}; return [self postParams:params toURL:[self purchaseConfirmationURL]]; } @@ -124,9 +123,9 @@ - (BOOL)postPurchaseReceipt:(NSString *)receipt ofType:(NSString *)type { - (BOOL)canPostAPNSToken { return ([self postAPNSTokenURL] != nil); } -- (BOOL)postAPNSToken:(NSString *)apnsToken { +- (BOOL)postAPNSToken:(NSString*)apnsToken { if ([self canPostAPNSToken]) { - NSDictionary *params = [NSDictionary dictionaryWithObject:apnsToken forKey:@"apns_token"]; + NSDictionary *params = @{@"apns_token": apnsToken}; return [self postParams:params toURL:[self postAPNSTokenURL]]; } @@ -137,7 +136,7 @@ - (BOOL)postAPNSToken:(NSString *)apnsToken { + (BOOL)generateUUIDOnce { if (![self UUID]) { - [[NSUserDefaults standardUserDefaults] setObject:[NSString uuid] forKey:@"UUID"]; + [[NSUserDefaults standardUserDefaults] setObject:[NSString bkrUUID] forKey:@"UUID"]; [[NSUserDefaults standardUserDefaults] synchronize]; return YES; } else { @@ -145,44 +144,51 @@ + (BOOL)generateUUIDOnce { } } -+ (NSString *)UUID { ++ (NSString*)UUID { return [[NSUserDefaults standardUserDefaults] stringForKey:@"UUID"]; } #pragma mark - Helpers -- (NSURLRequest *)requestForURL:(NSURL *)url method:(NSString *)method { - return [self requestForURL:url parameters:[NSDictionary dictionary] method:method cachePolicy:NSURLRequestUseProtocolCachePolicy]; +- (NSURLRequest*)requestForURL:(NSURL*)url method:(NSString*)method { + return [self requestForURL:url parameters:@{} method:method cachePolicy:NSURLRequestUseProtocolCachePolicy]; } -- (NSURLRequest *)requestForURL:(NSURL *)url parameters:(NSDictionary *)parameters method:(NSString *)method cachePolicy:(NSURLRequestCachePolicy)cachePolicy { +- (NSURLRequest*)requestForURL:(NSURL*)url parameters:(NSDictionary*)parameters method:(NSString*)method cachePolicy:(NSURLRequestCachePolicy)cachePolicy { NSMutableDictionary *requestParams = [NSMutableDictionary dictionaryWithDictionary:parameters]; - [requestParams setObject:[Utils appID] forKey:@"app_id"]; - [requestParams setObject:[BakerAPI UUID] forKey:@"user_id"]; + requestParams[@"app_id"] = [BKRUtils appID]; + requestParams[@"user_id"] = [BKRBakerAPI UUID]; #if DEBUG - [requestParams setObject:@"debug" forKey:@"environment"]; + requestParams[@"environment"] = @"debug"; #else [requestParams setObject:@"production" forKey:@"environment"]; #endif + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad){ + requestParams[@"devicetype"] = @"tablet"; + } + else{ + requestParams[@"devicetype"] = @"phone"; + } NSURL *requestURL = [self replaceParameters:requestParams inURL:url]; NSMutableURLRequest *request = nil; if ([method isEqualToString:@"GET"]) { NSString *queryString = [self queryStringFromParameters:requestParams]; - requestURL = [requestURL URLByAppendingQueryString:queryString]; - request = [NSURLRequest requestWithURL:requestURL cachePolicy:cachePolicy timeoutInterval:REQUEST_TIMEOUT]; + requestURL = [requestURL bkrURLByAppendingQueryString:queryString]; + request = [[NSURLRequest requestWithURL:requestURL cachePolicy:cachePolicy timeoutInterval:[BKRSettings sharedSettings].requestTimeout] mutableCopy]; } else if ([method isEqualToString:@"POST"]) { - request = [[[NSMutableURLRequest alloc] initWithURL:requestURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:REQUEST_TIMEOUT] autorelease]; + request = [[NSMutableURLRequest alloc] initWithURL:requestURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:[BKRSettings sharedSettings].requestTimeout]; [request setHTTPMethod:@"POST"]; [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; - [request setFormPostParameters:requestParams]; + [request bkrSetFormPostParameters:requestParams]; } return request; } -- (BOOL)postParams:(NSDictionary *)params toURL:(NSURL *)url { +- (BOOL)postParams:(NSDictionary*)params toURL:(NSURL*)url { NSError *error = nil; NSHTTPURLResponse *response = nil; NSURLRequest *request = [self requestForURL:url parameters:params method:@"POST" cachePolicy:NSURLRequestUseProtocolCachePolicy]; @@ -195,18 +201,18 @@ - (BOOL)postParams:(NSDictionary *)params toURL:(NSURL *)url { } else if ([response statusCode] == 200) { return YES; } else { - NSLog(@"[ERROR] Failed POST request to %@: response was %d %@", + NSLog(@"[ERROR] Failed POST request to %@: response was %ld %@", [request URL], - [response statusCode], + (long)[response statusCode], [NSHTTPURLResponse localizedStringForStatusCode:[response statusCode]]); return NO; } } -- (NSData *)getFromURL:(NSURL *)url cachePolicy:(NSURLRequestCachePolicy)cachePolicy { +- (NSData*)getFromURL:(NSURL*)url cachePolicy:(NSURLRequestCachePolicy)cachePolicy { NSError *error = nil; NSHTTPURLResponse *response = nil; - NSURLRequest *request = [self requestForURL:url parameters:[NSDictionary dictionary] method:@"GET" cachePolicy:cachePolicy]; + NSURLRequest *request = [self requestForURL:url parameters:@{} method:@"GET" cachePolicy:cachePolicy]; NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; @@ -216,16 +222,16 @@ - (NSData *)getFromURL:(NSURL *)url cachePolicy:(NSURLRequestCachePolicy)cachePo } else if ([response statusCode] == 200) { return data; } else { - NSLog(@"[ERROR] Failed GET request to %@: response was %d %@", + NSLog(@"[ERROR] Failed GET request to %@: response was %ld %@", [request URL], - [response statusCode], + (long)[response statusCode], [NSHTTPURLResponse localizedStringForStatusCode:[response statusCode]]); return nil; } } -- (NSURL *)replaceParameters:(NSMutableDictionary *)parameters inURL:(NSURL *)url { - __block NSMutableString *urlString = [NSMutableString stringWithString:[url absoluteString]]; +- (NSURL*)replaceParameters:(NSMutableDictionary*)parameters inURL:(NSURL*)url { + NSMutableString *urlString = [NSMutableString stringWithString:[url absoluteString]]; NSDictionary *allParameters = [NSDictionary dictionaryWithDictionary:parameters]; [allParameters enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { NSString *keyToReplace = [@":" stringByAppendingString:key]; @@ -238,8 +244,8 @@ - (NSURL *)replaceParameters:(NSMutableDictionary *)parameters inURL:(NSURL *)ur return [NSURL URLWithString:urlString]; } -- (NSString *)queryStringFromParameters:(NSDictionary *)parameters { - __block NSMutableString *queryString = [NSMutableString stringWithString:@""]; +- (NSString*)queryStringFromParameters:(NSDictionary*)parameters { + NSMutableString *queryString = [NSMutableString stringWithString:@""]; if ([parameters count] > 0) { [parameters enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { NSString *queryParameter = [NSString stringWithFormat:@"%@=%@&", key, obj]; @@ -251,36 +257,31 @@ - (NSString *)queryStringFromParameters:(NSDictionary *)parameters { return queryString; } -- (NSURL *)manifestURL { - #ifdef BAKER_NEWSSTAND - if ([NEWSSTAND_MANIFEST_URL length] > 0) { - return [NSURL URLWithString:NEWSSTAND_MANIFEST_URL]; +- (NSURL*)manifestURL { + if ([BKRSettings sharedSettings].isNewsstand && [BKRSettings sharedSettings].newsstandManifestUrl.length > 0) { + return [NSURL URLWithString:[BKRSettings sharedSettings].newsstandManifestUrl]; } - #endif return nil; } -- (NSURL *)purchasesURL { - #ifdef BAKER_NEWSSTAND - if ([PURCHASES_URL length] > 0) { - return [NSURL URLWithString:PURCHASES_URL]; + +- (NSURL*)purchasesURL { + if ([BKRSettings sharedSettings].isNewsstand && [BKRSettings sharedSettings].purchasesUrl.length > 0) { + return [NSURL URLWithString:[BKRSettings sharedSettings].purchasesUrl]; } - #endif return nil; } -- (NSURL *)purchaseConfirmationURL { - #ifdef BAKER_NEWSSTAND - if ([PURCHASE_CONFIRMATION_URL length] > 0) { - return [NSURL URLWithString:PURCHASE_CONFIRMATION_URL]; + +- (NSURL*)purchaseConfirmationURL { + if ([BKRSettings sharedSettings].isNewsstand && [BKRSettings sharedSettings].purchaseConfirmationUrl.length > 0) { + return [NSURL URLWithString:[BKRSettings sharedSettings].purchaseConfirmationUrl]; } - #endif return nil; } -- (NSURL *)postAPNSTokenURL { - #ifdef BAKER_NEWSSTAND - if ([POST_APNS_TOKEN_URL length] > 0) { - return [NSURL URLWithString:POST_APNS_TOKEN_URL]; + +- (NSURL*)postAPNSTokenURL { + if ([BKRSettings sharedSettings].isNewsstand && [BKRSettings sharedSettings].postApnsTokenUrl.length > 0) { + return [NSURL URLWithString:[BKRSettings sharedSettings].postApnsTokenUrl]; } - #endif return nil; } diff --git a/BakerShelf/UICustomNavigationBar.h b/BakerShelf/BKRCustomNavigationBar.h similarity index 93% rename from BakerShelf/UICustomNavigationBar.h rename to BakerShelf/BKRCustomNavigationBar.h index 5d842ae1..4eb00274 100644 --- a/BakerShelf/UICustomNavigationBar.h +++ b/BakerShelf/BKRCustomNavigationBar.h @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -31,8 +32,7 @@ #import -@interface UICustomNavigationBar : UINavigationBar -{ +@interface BKRCustomNavigationBar : UINavigationBar { UIImageView *backgroundImageView; NSMutableDictionary *backgroundImages; } diff --git a/BakerShelf/UICustomNavigationBar.m b/BakerShelf/BKRCustomNavigationBar.m similarity index 80% rename from BakerShelf/UICustomNavigationBar.m rename to BakerShelf/BKRCustomNavigationBar.m index 55d08fbc..04cea0d9 100644 --- a/BakerShelf/UICustomNavigationBar.m +++ b/BakerShelf/BKRCustomNavigationBar.m @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -29,27 +30,18 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -#import "UICustomNavigationBar.h" +#import "BKRCustomNavigationBar.h" -@implementation UICustomNavigationBar +@implementation BKRCustomNavigationBar -- (void)dealloc -{ - [backgroundImages release]; - [backgroundImageView release]; - - [super dealloc]; -} - -- (NSMutableDictionary *)backgroundImages -{ +- (NSMutableDictionary*)backgroundImages { if (!backgroundImages) { backgroundImages = [[NSMutableDictionary alloc] init]; } return backgroundImages; } -- (UIImageView *)backgroundImageView -{ + +- (UIImageView*)backgroundImageView { if (!backgroundImageView) { backgroundImageView = [[UIImageView alloc] initWithFrame:[self bounds]]; [backgroundImageView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; @@ -57,25 +49,25 @@ - (UIImageView *)backgroundImageView } return backgroundImageView; } -- (void)setBackgroundImage:(UIImage *)backgroundImage forBarMetrics:(UIBarMetrics)barMetrics -{ + +- (void)setBackgroundImage:(UIImage*)backgroundImage forBarMetrics:(UIBarMetrics)barMetrics { if ([UINavigationBar instancesRespondToSelector:@selector(setBackgroundImage:forBarMetrics:)]) { [super setBackgroundImage:backgroundImage forBarMetrics:barMetrics]; } else { - [[self backgroundImages] setObject:backgroundImage forKey:[NSNumber numberWithInt:barMetrics]]; + [self backgroundImages][[NSNumber numberWithInt:barMetrics]] = backgroundImage; [self updateBackgroundImage]; } } -- (void)updateBackgroundImage -{ + +- (void)updateBackgroundImage { UIBarMetrics metrics = UIBarMetricsLandscapePhone; if ([self bounds].size.height > 40) { metrics = UIBarMetricsDefault; } - UIImage *image = [[self backgroundImages] objectForKey:[NSNumber numberWithInt:metrics]]; + UIImage *image = [self backgroundImages][[NSNumber numberWithInt:metrics]]; if (!image && metrics != UIBarMetricsDefault) { - image = [[self backgroundImages] objectForKey:[NSNumber numberWithInt:UIBarMetricsDefault]]; + image = [self backgroundImages][[NSNumber numberWithInt:UIBarMetricsDefault]]; } if (image) { @@ -83,10 +75,8 @@ - (void)updateBackgroundImage } } -- (void)layoutSubviews -{ +- (void)layoutSubviews { [super layoutSubviews]; - if (backgroundImageView) { [self updateBackgroundImage]; [self sendSubviewToBack:backgroundImageView]; diff --git a/BakerShelf/UICustomNavigationController.h b/BakerShelf/BKRCustomNavigationController.h similarity index 92% rename from BakerShelf/UICustomNavigationController.h rename to BakerShelf/BKRCustomNavigationController.h index 69143f3b..36d94581 100644 --- a/BakerShelf/UICustomNavigationController.h +++ b/BakerShelf/BKRCustomNavigationController.h @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -31,6 +32,6 @@ #import -@interface UICustomNavigationController : UINavigationController +@interface BKRCustomNavigationController : UINavigationController @end diff --git a/BakerShelf/lib/NSString+UUID.m b/BakerShelf/BKRCustomNavigationController.m similarity index 79% rename from BakerShelf/lib/NSString+UUID.m rename to BakerShelf/BKRCustomNavigationController.m index 5cd87d65..a8c3f2e4 100644 --- a/BakerShelf/lib/NSString+UUID.m +++ b/BakerShelf/BKRCustomNavigationController.m @@ -1,11 +1,11 @@ // -// NSString+UUID.m +// UICustomNavigationController.m // Baker -// See: http://oleb.net/blog/2011/09/how-to-replace-the-udid/ // // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -30,19 +30,17 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -#import "NSString+UUID.h" +#import "BKRCustomNavigationController.h" +#import "BKRCustomNavigationBar.h" -@implementation NSString (UUID) +@implementation BKRCustomNavigationController -+ (NSString *)uuid -{ - NSString *uuidString = nil; - CFUUIDRef uuid = CFUUIDCreate(NULL); - if (uuid) { - uuidString = (NSString *)CFUUIDCreateString(NULL, uuid); - CFRelease(uuid); - } - return [uuidString autorelease]; +- (NSUInteger)supportedInterfaceOrientations { + return [self.topViewController supportedInterfaceOrientations]; } -@end +- (BOOL)shouldAutorotate { + return [self.topViewController shouldAutorotate]; +} + +@end \ No newline at end of file diff --git a/BakerShelf/BakerIssue.h b/BakerShelf/BKRIssue.h similarity index 59% rename from BakerShelf/BakerIssue.h rename to BakerShelf/BKRIssue.h index 49c68643..5c1da1a3 100644 --- a/BakerShelf/BakerIssue.h +++ b/BakerShelf/BKRIssue.h @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -29,15 +30,12 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -#import "Constants.h" #import -#ifdef BAKER_NEWSSTAND #import -#import "PurchasesManager.h" -#endif +#import "BKRPurchasesManager.h" -#import "BakerBook.h" +#import "BKRBook.h" typedef enum transientStates { BakerIssueTransientStatusNone, @@ -47,45 +45,40 @@ typedef enum transientStates { BakerIssueTransientStatusUnpriced } BakerIssueTransientStatus; -#ifdef BAKER_NEWSSTAND -@interface BakerIssue : NSObject { - PurchasesManager *purchasesManager; +@interface BKRIssue : NSObject { + BKRPurchasesManager *purchasesManager; } -#else -@interface BakerIssue : NSObject -#endif -@property (copy, nonatomic) NSString *ID; -@property (copy, nonatomic) NSString *title; -@property (copy, nonatomic) NSString *info; -@property (copy, nonatomic) NSString *date; -@property (copy, nonatomic) NSURL *url; -@property (copy, nonatomic) NSString *path; +@property (nonatomic, copy) NSString *ID; +@property (nonatomic, copy) NSString *title; +@property (nonatomic, copy) NSString *info; +@property (nonatomic, copy) NSString *date; +@property (nonatomic, copy) NSURL *url; +@property (nonatomic, copy) NSString *path; +@property (nonatomic, copy) NSArray *categories; -@property (copy, nonatomic) NSString *coverPath; -@property (copy, nonatomic) NSURL *coverURL; +@property (nonatomic, copy) NSString *coverPath; +@property (nonatomic, copy) NSURL *coverURL; -@property (copy, nonatomic) NSString *productID; -@property (copy, nonatomic) NSString *price; +@property (nonatomic, copy) NSString *productID; +@property (nonatomic, copy) NSString *price; -@property (retain, nonatomic) BakerBook *bakerBook; +@property (nonatomic, strong) BKRBook *bakerBook; -@property (assign, nonatomic) BakerIssueTransientStatus transientStatus; +@property (nonatomic, assign) BakerIssueTransientStatus transientStatus; -@property (copy, nonatomic) NSString *notificationDownloadStartedName; -@property (copy, nonatomic) NSString *notificationDownloadProgressingName; -@property (copy, nonatomic) NSString *notificationDownloadFinishedName; -@property (copy, nonatomic) NSString *notificationDownloadErrorName; -@property (copy, nonatomic) NSString *notificationUnzipErrorName; +@property (nonatomic, copy) NSString *notificationDownloadStartedName; +@property (nonatomic, copy) NSString *notificationDownloadProgressingName; +@property (nonatomic, copy) NSString *notificationDownloadFinishedName; +@property (nonatomic, copy) NSString *notificationDownloadErrorName; +@property (nonatomic, copy) NSString *notificationUnzipErrorName; --(id)initWithBakerBook:(BakerBook *)bakerBook; --(void)getCoverWithCache:(bool)cache andBlock:(void(^)(UIImage *img))completionBlock; --(NSString *)getStatus; +- (id)initWithBakerBook:(BKRBook*)bakerBook; +- (void)getCoverWithCache:(bool)cache andBlock:(void(^)(UIImage *img))completionBlock; +- (NSString*)getStatus; -#ifdef BAKER_NEWSSTAND --(id)initWithIssueData:(NSDictionary *)issueData; --(void)download; --(void)downloadWithAsset:(NKAssetDownload *)asset; -#endif +- (id)initWithIssueData:(NSDictionary*)issueData; +- (void)download; +- (void)downloadWithAsset:(NKAssetDownload*)asset; @end diff --git a/BakerShelf/BakerIssue.m b/BakerShelf/BKRIssue.m similarity index 51% rename from BakerShelf/BakerIssue.m rename to BakerShelf/BKRIssue.m index c5e3fae5..35de6a9b 100755 --- a/BakerShelf/BakerIssue.m +++ b/BakerShelf/BKRIssue.m @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -29,58 +30,43 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -#import "BakerIssue.h" -#import "BakerAPI.h" - -#import "SSZipArchive.h" -#import "Reachability.h" -#import "Utils.h" -#import "NSURL+Extensions.h" - -@implementation BakerIssue - -@synthesize ID; -@synthesize title; -@synthesize info; -@synthesize date; -@synthesize url; -@synthesize path; -@synthesize bakerBook; -@synthesize coverPath; -@synthesize coverURL; -@synthesize productID; -@synthesize price; -@synthesize transientStatus; - -@synthesize notificationDownloadStartedName; -@synthesize notificationDownloadProgressingName; -@synthesize notificationDownloadFinishedName; -@synthesize notificationDownloadErrorName; -@synthesize notificationUnzipErrorName; - --(id)initWithBakerBook:(BakerBook *)book { - self = [super init]; - if (self) { - self.ID = book.ID; - self.title = book.title; - self.info = @""; - self.date = book.date; - self.url = [NSURL URLWithString:book.url]; - self.path = book.path; - self.productID = @""; - self.price = nil; +#import "BKRIssue.h" +#import "BKRBakerAPI.h" +#import "BKRSettings.h" + +#import "BKRZipArchive.h" +#import "BKRReachability.h" +#import "BKRUtils.h" +#import "NSURL+BakerExtensions.h" +#import "NSObject+BakerExtensions.h" + +@implementation BKRIssue - self.bakerBook = book; +#pragma mark - Initialization - self.coverPath = @""; +- (id)initWithBakerBook:(BKRBook*)book { + self = [super init]; + if (self) { + _ID = book.ID; + _title = book.title; + _info = @""; + _date = book.date; + _url = [NSURL URLWithString:book.url]; + _path = book.path; + _categories = book.categories; + _productID = @""; + _price = nil; + _bakerBook = book; + + _coverPath = @""; if (book.cover == nil) { // TODO: set path to a default cover (right now a blank box will be displayed) NSLog(@"Cover not specified for %@, probably missing from book.json", book.ID); } else { - self.coverPath = [book.path stringByAppendingPathComponent:book.cover]; + _coverPath = [book.path stringByAppendingPathComponent:book.cover]; } - self.transientStatus = BakerIssueTransientStatusNone; + _transientStatus = BakerIssueTransientStatusNone; [self setNotificationDownloadNames]; } @@ -88,32 +74,33 @@ -(id)initWithBakerBook:(BakerBook *)book { } - (void)setNotificationDownloadNames { - self.notificationDownloadStartedName = [NSString stringWithFormat:@"notification_download_started_%@", self.ID]; + self.notificationDownloadStartedName = [NSString stringWithFormat:@"notification_download_started_%@", self.ID]; self.notificationDownloadProgressingName = [NSString stringWithFormat:@"notification_download_progressing_%@", self.ID]; - self.notificationDownloadFinishedName = [NSString stringWithFormat:@"notification_download_finished_%@", self.ID]; - self.notificationDownloadErrorName = [NSString stringWithFormat:@"notification_download_error_%@", self.ID]; - self.notificationUnzipErrorName = [NSString stringWithFormat:@"notification_unzip_error_%@", self.ID]; + self.notificationDownloadFinishedName = [NSString stringWithFormat:@"notification_download_finished_%@", self.ID]; + self.notificationDownloadErrorName = [NSString stringWithFormat:@"notification_download_error_%@", self.ID]; + self.notificationUnzipErrorName = [NSString stringWithFormat:@"notification_unzip_error_%@", self.ID]; } -#ifdef BAKER_NEWSSTAND --(id)initWithIssueData:(NSDictionary *)issueData { +#pragma mark - Newsstand + +- (id)initWithIssueData:(NSDictionary*)issueData { self = [super init]; if (self) { - self.ID = [issueData objectForKey:@"name"]; - self.title = [issueData objectForKey:@"title"]; - self.info = [issueData objectForKey:@"info"]; - self.date = [issueData objectForKey:@"date"]; - self.coverURL = [NSURL URLWithString:[issueData objectForKey:@"cover"]]; - self.url = [NSURL URLWithString:[issueData objectForKey:@"url"]]; - if ([issueData objectForKey:@"product_id"] != [NSNull null]) { - self.productID = [issueData objectForKey:@"product_id"]; + self.ID = issueData[@"name"]; + self.title = issueData[@"title"]; + self.info = issueData[@"info"]; + self.date = issueData[@"date"]; + self.categories = issueData[@"categories"]; + self.coverURL = [NSURL URLWithString:issueData[@"cover"]]; + self.url = [NSURL URLWithString:issueData[@"url"]]; + if (issueData[@"product_id"] != [NSNull null]) { + self.productID = issueData[@"product_id"]; } self.price = nil; - purchasesManager = [PurchasesManager sharedInstance]; + purchasesManager = [BKRPurchasesManager sharedInstance]; - NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - self.coverPath = [cachePath stringByAppendingPathComponent:self.ID]; + self.coverPath = [self.bkrCachePath stringByAppendingPathComponent:self.ID]; NKLibrary *nkLib = [NKLibrary sharedLibrary]; NKIssue *nkIssue = [nkLib issueWithName:self.ID]; @@ -132,7 +119,7 @@ -(id)initWithIssueData:(NSDictionary *)issueData { return self; } --(NSString *)nkIssueContentStatusToString:(NKIssueContentStatus) contentStatus{ +- (NSString*)nkIssueContentStatusToString:(NKIssueContentStatus) contentStatus{ if (contentStatus == NKIssueContentStatusNone) { return @"remote"; } else if (contentStatus == NKIssueContentStatusDownloading) { @@ -142,10 +129,11 @@ -(NSString *)nkIssueContentStatusToString:(NKIssueContentStatus) contentStatus{ } return @""; } + - (void)download { - Reachability* reach = [Reachability reachabilityWithHostname:@"www.google.com"]; + BKRReachability *reach = [BKRReachability reachabilityWithHostname:@"www.google.com"]; if ([reach isReachable]) { - BakerAPI *api = [BakerAPI sharedInstance]; + BKRBakerAPI *api = [BKRBakerAPI sharedInstance]; NSURLRequest *req = [api requestForURL:self.url method:@"GET"]; NKLibrary *nkLib = [NKLibrary sharedLibrary]; @@ -154,36 +142,34 @@ - (void)download { NKAssetDownload *assetDownload = [nkIssue addAssetWithRequest:req]; [self downloadWithAsset:assetDownload]; } else { - [[NSNotificationCenter defaultCenter] postNotificationName:notificationDownloadErrorName object:self userInfo:nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:self.notificationDownloadErrorName object:self userInfo:nil]; } } -- (void)downloadWithAsset:(NKAssetDownload *)asset { + +- (void)downloadWithAsset:(NKAssetDownload*)asset { [asset downloadWithDelegate:self]; - [[NSNotificationCenter defaultCenter] postNotificationName:notificationDownloadStartedName object:self userInfo:nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:self.notificationDownloadStartedName object:self userInfo:nil]; } #pragma mark - Newsstand download management -- (void)connection:(NSURLConnection *)connection didWriteData:(long long)bytesWritten totalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long)expectedTotalBytes { - NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithLongLong:totalBytesWritten], @"totalBytesWritten", - [NSNumber numberWithLongLong:expectedTotalBytes], @"expectedTotalBytes", - nil]; - [[NSNotificationCenter defaultCenter] postNotificationName:notificationDownloadProgressingName object:self userInfo:userInfo]; +- (void)connection:(NSURLConnection*)connection didWriteData:(long long)bytesWritten totalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long)expectedTotalBytes { + NSDictionary *userInfo = @{@"totalBytesWritten": @(totalBytesWritten), + @"expectedTotalBytes": @(expectedTotalBytes)}; + [[NSNotificationCenter defaultCenter] postNotificationName:self.notificationDownloadProgressingName object:self userInfo:userInfo]; } -- (void)connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *)destinationURL { - #ifdef BAKER_NEWSSTAND - [self unpackAssetDownload:connection.newsstandAssetDownload toURL:destinationURL]; - #endif +- (void)connectionDidFinishDownloading:(NSURLConnection*)connection destinationURL:(NSURL*)destinationURL { + if ([BKRSettings sharedSettings].isNewsstand) { + [self unpackAssetDownload:connection.newsstandAssetDownload toURL:destinationURL]; + } } -#ifdef BAKER_NEWSSTAND -- (void)unpackAssetDownload:(NKAssetDownload *)newsstandAssetDownload toURL:(NSURL *)destinationURL { +- (void)unpackAssetDownload:(NKAssetDownload*)newsstandAssetDownload toURL:(NSURL*)destinationURL { UIApplication *application = [UIApplication sharedApplication]; - NKIssue *nkIssue = newsstandAssetDownload.issue; - NSString *destinationPath = [[nkIssue contentURL] path]; + NKIssue *nkIssue = newsstandAssetDownload.issue; + NSString *destinationPath = [[nkIssue contentURL] path]; __block UIBackgroundTaskIdentifier backgroundTask = [application beginBackgroundTaskWithExpirationHandler:^{ [application endBackgroundTask:backgroundTask]; @@ -193,11 +179,11 @@ - (void)unpackAssetDownload:(NKAssetDownload *)newsstandAssetDownload toURL:(NSU dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"[BakerShelf] Newsstand - File is being unzipped to %@", destinationPath); BOOL unzipSuccessful = NO; - unzipSuccessful = [SSZipArchive unzipFileAtPath:[destinationURL path] toDestination:destinationPath]; + unzipSuccessful = [BKRZipArchive unzipFileAtPath:[destinationURL path] toDestination:destinationPath]; if (!unzipSuccessful) { NSLog(@"[BakerShelf] Newsstand - Unable to unzip file: %@. The file may not be a valid HPUB archive.", [destinationURL path]); dispatch_async(dispatch_get_main_queue(), ^(void) { - [[NSNotificationCenter defaultCenter] postNotificationName:notificationUnzipErrorName object:self userInfo:nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:self.notificationUnzipErrorName object:self userInfo:nil]; }); } @@ -211,7 +197,7 @@ - (void)unpackAssetDownload:(NKAssetDownload *)newsstandAssetDownload toURL:(NSU if (unzipSuccessful) { // Notification and UI update have to be handled on the main thread dispatch_async(dispatch_get_main_queue(), ^(void) { - [[NSNotificationCenter defaultCenter] postNotificationName:notificationDownloadFinishedName object:self userInfo:nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:self.notificationDownloadFinishedName object:self userInfo:nil]; }); } @@ -221,6 +207,7 @@ - (void)unpackAssetDownload:(NKAssetDownload *)newsstandAssetDownload toURL:(NSU backgroundTask = UIBackgroundTaskInvalid; }); } + - (void)updateNewsstandIcon { [[UIApplication sharedApplication] setApplicationIconBadgeNumber:1]; @@ -229,24 +216,20 @@ - (void)updateNewsstandIcon { [[UIApplication sharedApplication] setNewsstandIconImage:coverImage]; } } -#endif -- (void)connectionDidResumeDownloading:(NSURLConnection *)connection totalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long)expectedTotalBytes { - // Nothing to do for now +- (void)connectionDidResumeDownloading:(NSURLConnection*)connection totalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long)expectedTotalBytes { } -- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { +- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error { NSLog(@"Connection error when trying to download %@: %@", [connection currentRequest].URL, [error localizedDescription]); [connection cancel]; - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:error forKey:@"error"]; - [[NSNotificationCenter defaultCenter] postNotificationName:notificationDownloadErrorName object:self userInfo:userInfo]; + NSDictionary *userInfo = @{@"error": error}; + [[NSNotificationCenter defaultCenter] postNotificationName:self.notificationDownloadErrorName object:self userInfo:userInfo]; } -#endif - --(void)getCoverWithCache:(bool)cache andBlock:(void(^)(UIImage *img))completionBlock { +- (void)getCoverWithCache:(bool)cache andBlock:(void(^)(UIImage *img))completionBlock { UIImage *image = [UIImage imageWithContentsOfFile:self.coverPath]; if (cache && image) { completionBlock(image); @@ -266,58 +249,39 @@ -(void)getCoverWithCache:(bool)cache andBlock:(void(^)(UIImage *img))completionB } } --(NSString *)getStatus { -#ifdef BAKER_NEWSSTAND - switch (self.transientStatus) { - case BakerIssueTransientStatusDownloading: - return @"downloading"; - break; - case BakerIssueTransientStatusOpening: - return @"opening"; - break; - case BakerIssueTransientStatusPurchasing: - return @"purchasing"; - break; - default: - break; - } +- (NSString*)getStatus { + if ([BKRSettings sharedSettings].isNewsstand) { + switch (self.transientStatus) { + case BakerIssueTransientStatusDownloading: + return @"downloading"; + break; + case BakerIssueTransientStatusOpening: + return @"opening"; + break; + case BakerIssueTransientStatusPurchasing: + return @"purchasing"; + break; + default: + break; + } - NKLibrary *nkLib = [NKLibrary sharedLibrary]; - NKIssue *nkIssue = [nkLib issueWithName:self.ID]; - NSString *nkIssueStatus = [self nkIssueContentStatusToString:[nkIssue status]]; - if ([nkIssueStatus isEqualToString:@"remote"] && self.productID) { - if ([purchasesManager isPurchased:self.productID]) { - return @"purchased"; - } else if (self.price) { - return @"purchasable"; + NKLibrary *nkLib = [NKLibrary sharedLibrary]; + NKIssue *nkIssue = [nkLib issueWithName:self.ID]; + NSString *nkIssueStatus = [self nkIssueContentStatusToString:[nkIssue status]]; + if ([nkIssueStatus isEqualToString:@"remote"] && self.productID) { + if ([purchasesManager isPurchased:self.productID]) { + return @"purchased"; + } else if (self.price) { + return @"purchasable"; + } else { + return @"unpriced"; + } } else { - return @"unpriced"; + return nkIssueStatus; } } else { - return nkIssueStatus; + return @"bundled"; } -#else - return @"bundled"; -#endif -} - --(void)dealloc { - [ID release]; - [title release]; - [info release]; - [date release]; - [url release]; - [path release]; - [bakerBook release]; - [coverPath release]; - [coverURL release]; - - [notificationUnzipErrorName release]; - [notificationDownloadErrorName release]; - [notificationDownloadFinishedName release]; - [notificationDownloadProgressingName release]; - - [super dealloc]; } @end diff --git a/BakerShelf/IssueViewController.h b/BakerShelf/BKRIssueViewController.h similarity index 64% rename from BakerShelf/IssueViewController.h rename to BakerShelf/BKRIssueViewController.h index 633edacd..5d685f7b 100644 --- a/BakerShelf/IssueViewController.h +++ b/BakerShelf/BKRIssueViewController.h @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -30,32 +31,27 @@ // #import -#import "BakerIssue.h" -#ifdef BAKER_NEWSSTAND -#import "PurchasesManager.h" -#endif +#import "BKRIssue.h" +#import "BKRPurchasesManager.h" -@interface IssueViewController : UIViewController { +@interface BKRIssueViewController : UIViewController { NSString *currentAction; BOOL purchaseDelayed; - #ifdef BAKER_NEWSSTAND - PurchasesManager *purchasesManager; - #endif + BKRPurchasesManager *purchasesManager; } -@property (strong, nonatomic) BakerIssue *issue; -@property (strong, nonatomic) UIButton *actionButton; -@property (strong, nonatomic) UIButton *archiveButton; -@property (strong, nonatomic) UIProgressView *progressBar; -@property (strong, nonatomic) UIActivityIndicatorView *spinner; -@property (strong, nonatomic) UILabel *loadingLabel; -@property (strong, nonatomic) UILabel *priceLabel; +@property (nonatomic, strong) BKRIssue *issue; +@property (nonatomic, strong) UIButton *actionButton; +@property (nonatomic, strong) UIButton *archiveButton; +@property (nonatomic, strong) UIProgressView *progressBar; +@property (nonatomic, strong) UIActivityIndicatorView *spinner; +@property (nonatomic, strong) UILabel *loadingLabel; -@property (strong, nonatomic) UIButton *issueCover; -@property (strong, nonatomic) UILabel *titleLabel; -@property (strong, nonatomic) UILabel *infoLabel; +@property (nonatomic, strong) UIButton *issueCover; +@property (nonatomic, strong) UILabel *titleLabel; +@property (nonatomic, strong) UILabel *infoLabel; -@property (copy, nonatomic) NSString *currentStatus; +@property (nonatomic, copy) NSString *currentStatus; #pragma mark - Structs typedef struct { @@ -66,38 +62,32 @@ typedef struct { } UI; #pragma mark - Init -- (id)initWithBakerIssue:(BakerIssue *)bakerIssue; +- (id)initWithBakerIssue:(BKRIssue*)bakerIssue; #pragma mark - View Lifecycle - (void)refresh; -- (void)refresh:(NSString *)status; +- (void)refresh:(NSString*)status; +- (void)refresh:(NSString*)status cache:(BOOL)cache; +- (void)refreshWithCache:(BOOL)cache; - (void)refreshContentWithCache:(bool)cache; -- (void)preferredContentSizeChanged:(NSNotification *)notification; +- (void)preferredContentSizeChanged:(NSNotification*)notification; #pragma mark - Issue management -- (void)actionButtonPressed:(UIButton *)sender; -#ifdef BAKER_NEWSSTAND +- (void)actionButtonPressed:(UIButton*)sender; - (void)download; -- (void)setPrice:(NSString *)price; +- (void)setPrice:(NSString*)price; - (void)buy; -#endif - (void)read; #pragma mark - Newsstand archive management -#ifdef BAKER_NEWSSTAND -- (void)archiveButtonPressed:(UIButton *)sender; -#endif +- (void)archiveButtonPressed:(UIButton*)sender; #pragma mark - Helper methods + (UI)getIssueContentMeasures; + (int)getIssueCellHeight; -+ (CGSize)getIssueCellSize; ++ (CGSize)getIssueCellSizeForOrientation:(UIInterfaceOrientation)orientation; @end -#ifdef BAKER_NEWSSTAND -@interface alertView: UIAlertView { - -} +@interface alertView: UIAlertView @end -#endif diff --git a/BakerShelf/BKRIssueViewController.m b/BakerShelf/BKRIssueViewController.m new file mode 100644 index 00000000..bb219302 --- /dev/null +++ b/BakerShelf/BKRIssueViewController.m @@ -0,0 +1,649 @@ +// +// IssueViewController.m +// Baker +// +// ========================================================================================== +// +// Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout +// 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 Baker Framework nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written +// permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#import + +#import "BKRSettings.h" +#import "BKRIssueViewController.h" +#import "BKRZipArchive.h" +#import "BKRPurchasesManager.h" + +#import "UIColor+BakerExtensions.h" +#import "UIScreen+BakerExtensions.h" +#import "BKRUtils.h" + +@implementation BKRIssueViewController + +#pragma mark - Init + +- (id)initWithBakerIssue:(BKRIssue*)bakerIssue { + self = [super init]; + if (self) { + _issue = bakerIssue; + _currentStatus = nil; + + purchaseDelayed = NO; + + if ([BKRSettings sharedSettings].isNewsstand) { + purchasesManager = [BKRPurchasesManager sharedInstance]; + [self addPurchaseObserver:@selector(handleIssueRestored:) name:@"notification_issue_restored"]; + + [self addIssueObserver:@selector(handleDownloadStarted:) name:self.issue.notificationDownloadStartedName]; + [self addIssueObserver:@selector(handleDownloadProgressing:) name:self.issue.notificationDownloadProgressingName]; + [self addIssueObserver:@selector(handleDownloadFinished:) name:self.issue.notificationDownloadFinishedName]; + [self addIssueObserver:@selector(handleDownloadError:) name:self.issue.notificationDownloadErrorName]; + [self addIssueObserver:@selector(handleUnzipError:) name:self.issue.notificationUnzipErrorName]; + } + } + return self; +} + +#pragma mark - View Lifecycle + +- (void)viewDidLoad { + [super viewDidLoad]; + + CGSize cellSize = [BKRIssueViewController getIssueCellSizeForOrientation:self.interfaceOrientation]; + + self.view.frame = CGRectMake(0, 0, cellSize.width, cellSize.height); + self.view.backgroundColor = [UIColor clearColor]; + self.view.tag = 42; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(preferredContentSizeChanged:) name:UIContentSizeCategoryDidChangeNotification object:nil]; + + UI ui = [BKRIssueViewController getIssueContentMeasures]; + + self.issueCover = [UIButton buttonWithType:UIButtonTypeCustom]; + self.issueCover.frame = CGRectMake(ui.cellPadding, ui.cellPadding, ui.thumbWidth, ui.thumbHeight); + + self.issueCover.backgroundColor = [UIColor bkrColorWithHexString:[BKRSettings sharedSettings].issuesCoverBackgroundColor]; + self.issueCover.adjustsImageWhenHighlighted = NO; + self.issueCover.adjustsImageWhenDisabled = NO; + + self.issueCover.layer.shadowOpacity = 0.5; + self.issueCover.layer.shadowOffset = CGSizeMake(0, 2); + self.issueCover.layer.shouldRasterize = YES; + self.issueCover.layer.rasterizationScale = [UIScreen mainScreen].scale; + + [self.issueCover addTarget:self action:@selector(actionButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; + [self.view addSubview:self.issueCover]; + + // SETUP TITLE LABEL + self.titleLabel = [[UILabel alloc] init]; + self.titleLabel.textColor = [UIColor bkrColorWithHexString:[BKRSettings sharedSettings].issuesTitleColor]; + self.titleLabel.backgroundColor = [UIColor clearColor]; + self.titleLabel.lineBreakMode = NSLineBreakByTruncatingTail; + self.titleLabel.textAlignment = NSTextAlignmentLeft; + + [self.view addSubview:self.titleLabel]; + + // SETUP INFO LABEL + self.infoLabel = [[UILabel alloc] init]; + self.infoLabel.textColor = [UIColor bkrColorWithHexString:[BKRSettings sharedSettings].issuesInfoColor]; + self.infoLabel.backgroundColor = [UIColor clearColor]; + self.infoLabel.lineBreakMode = NSLineBreakByTruncatingTail; + self.infoLabel.textAlignment = NSTextAlignmentLeft; + + [self.view addSubview:self.infoLabel]; + + // SETUP ACTION BUTTON + self.actionButton = [UIButton buttonWithType:UIButtonTypeCustom]; + self.actionButton.backgroundColor = [UIColor bkrColorWithHexString:[BKRSettings sharedSettings].issuesActionBackgroundColor]; + + [self.actionButton setTitle:NSLocalizedString(@"ACTION_DOWNLOADED_TEXT", nil) forState:UIControlStateNormal]; + [self.actionButton setTitleColor:[UIColor bkrColorWithHexString:[BKRSettings sharedSettings].issuesActionButtonColor] forState:UIControlStateNormal]; + [self.actionButton addTarget:self action:@selector(actionButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; + + [self.view addSubview:self.actionButton]; + + // SETUP ARCHIVE BUTTON + self.archiveButton = [UIButton buttonWithType:UIButtonTypeCustom]; + self.archiveButton.backgroundColor = [UIColor bkrColorWithHexString:[BKRSettings sharedSettings].issuesArchiveBackgroundColor]; + + [self.archiveButton setTitle:NSLocalizedString(@"ARCHIVE_TEXT", nil) forState:UIControlStateNormal]; + [self.archiveButton setTitleColor:[UIColor bkrColorWithHexString:[BKRSettings sharedSettings].issuesArchiveButtonColor] forState:UIControlStateNormal]; + + if ([BKRSettings sharedSettings].isNewsstand) { + [self.archiveButton addTarget:self action:@selector(archiveButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; + [self.view addSubview:self.archiveButton]; + } + + // SETUP DOWN/LOADING SPINNER AND LABEL + self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + self.spinner.color = [UIColor bkrColorWithHexString:[BKRSettings sharedSettings].issuesLoadingSpinnerColor]; + self.spinner.backgroundColor = [UIColor clearColor]; + self.spinner.hidesWhenStopped = YES; + + self.loadingLabel = [[UILabel alloc] init]; + self.loadingLabel.textColor = [UIColor bkrColorWithHexString:[BKRSettings sharedSettings].issuesLoadingLabelColor]; + self.loadingLabel.backgroundColor = [UIColor clearColor]; + self.loadingLabel.textAlignment = NSTextAlignmentLeft; + self.loadingLabel.text = NSLocalizedString(@"DOWNLOADING_TEXT", nil); + + [self.view addSubview:self.spinner]; + [self.view addSubview:self.loadingLabel]; + + // SETUP PROGRESS BAR + self.progressBar = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault]; + self.progressBar.progressTintColor = [UIColor bkrColorWithHexString:[BKRSettings sharedSettings].issuesProgressbarTintColor]; + + [self.view addSubview:self.progressBar]; + + if ([BKRSettings sharedSettings].isNewsstand) { + // RESUME PENDING NEWSSTAND DOWNLOAD + NKLibrary *nkLib = [NKLibrary sharedLibrary]; + for (NKAssetDownload *asset in [nkLib downloadingAssets]) { + if ([asset.issue.name isEqualToString:self.issue.ID]) { + NSLog(@"[BakerShelf] Resuming abandoned Newsstand download: %@", asset.issue.name); + [self.issue downloadWithAsset:asset]; + } + } + } + + [self refreshContentWithCache:NO]; +} + +- (void)refreshContentWithCache:(bool)cache { + UIFont *titleFont = [UIFont fontWithName:[BKRSettings sharedSettings].issuesTitleFont + size:[BKRSettings sharedSettings].issuesTitleFontSize + ]; + UIFont *infoFont = [UIFont fontWithName:[BKRSettings sharedSettings].issuesInfoFont + size:[BKRSettings sharedSettings].issuesInfoFontSize + ]; + UIFont *actionFont = [UIFont fontWithName:[BKRSettings sharedSettings].issuesActionFont + size:[BKRSettings sharedSettings].issuesActionFontSize + ]; + UIFont *archiveFont = [UIFont fontWithName:[BKRSettings sharedSettings].issuesArchiveFont + size:[BKRSettings sharedSettings].issuesArchiveFontSize + ]; + + UI ui = [BKRIssueViewController getIssueContentMeasures]; + int heightOffset = ui.cellPadding; + uint textLineheight = [@"The brown fox jumps over the lazy dog" boundingRectWithSize:CGSizeMake(MAXFLOAT,MAXFLOAT) + options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine + attributes:@{NSFontAttributeName: infoFont} + context:nil].size.height; + + // SETUP COVER IMAGE + [self.issue getCoverWithCache:cache andBlock:^(UIImage *image) { + [self.issueCover setBackgroundImage:image forState:UIControlStateNormal]; + }]; + + CGFloat labelWidth = self.view.frame.size.width - ui.contentOffset - 60; + + // SETUP TITLE LABEL + self.titleLabel.font = titleFont; + self.titleLabel.frame = CGRectMake(ui.contentOffset, heightOffset, labelWidth, 60); + self.titleLabel.numberOfLines = 3; + self.titleLabel.text = self.issue.title; + [self.titleLabel sizeToFit]; + + heightOffset = heightOffset + self.titleLabel.frame.size.height + 5; + + // SETUP INFO LABEL + self.infoLabel.font = infoFont; + self.infoLabel.frame = CGRectMake(ui.contentOffset, heightOffset, labelWidth, 60); + self.infoLabel.numberOfLines = 3; + self.infoLabel.text = self.issue.info; + [self.infoLabel sizeToFit]; + +// heightOffset = heightOffset + self.infoLabel.frame.size.height + 5; + + heightOffset = 130 + textLineheight + 10; + + // SETUP ACTION BUTTON + NSString *status = [self.issue getStatus]; + if ([status isEqualToString:@"remote"] || [status isEqualToString:@"purchasable"] || [status isEqualToString:@"purchased"]) { + self.actionButton.frame = CGRectMake(ui.contentOffset, heightOffset, 110, 30); + } else if ([status isEqualToString:@"downloaded"] || [status isEqualToString:@"bundled"]) { + self.actionButton.frame = CGRectMake(ui.contentOffset, heightOffset, 80, 30); + } + self.actionButton.titleLabel.font = actionFont; + + // SETUP ARCHIVE BUTTON + self.archiveButton.frame = CGRectMake(ui.contentOffset + 80 + 10, heightOffset, 80, 30); + self.archiveButton.titleLabel.font = archiveFont; + + // SETUP DOWN/LOADING SPINNER AND LABEL + self.spinner.frame = CGRectMake(ui.contentOffset, heightOffset, 30, 30); + self.loadingLabel.frame = CGRectMake(ui.contentOffset + self.spinner.frame.size.width + 10, heightOffset, 135, 30); + self.loadingLabel.font = actionFont; + +// heightOffset = heightOffset + self.loadingLabel.frame.size.height + 5; + + // SETUP PROGRESS BAR + self.progressBar.frame = CGRectMake(ui.contentOffset, 136, labelWidth, 30); +} + +- (void)preferredContentSizeChanged:(NSNotification*)notification { + [self refreshContentWithCache:YES]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + [self refresh]; +} + +- (void)refresh { + [self refresh:[self.issue getStatus]]; +} + +- (void)refresh:(NSString*)status { + [self refresh:[self.issue getStatus] cache:YES]; +} + +- (void)refreshWithCache:(BOOL)cache { + [self refresh:[self.issue getStatus] cache:cache]; +} + +- (void)refresh:(NSString*)status cache:(BOOL)cache { + // NSLog(@"[BakerShelf] Shelf UI - Refreshing %@ item with status from <%@> to <%@>", self.issue.ID, self.currentStatus, status); + if ([status isEqualToString:@"remote"]) { + [self.actionButton setTitle:NSLocalizedString(@"FREE_TEXT", nil) forState:UIControlStateNormal]; + [self.spinner stopAnimating]; + + self.actionButton.hidden = NO; + self.archiveButton.hidden = YES; + self.progressBar.hidden = YES; + self.loadingLabel.hidden = YES; + } else if ([status isEqualToString:@"connecting"]) { + NSLog(@"[BakerShelf] '%@' is Connecting...", self.issue.ID); + [self.spinner startAnimating]; + + self.actionButton.hidden = YES; + self.archiveButton.hidden = YES; + self.progressBar.progress = 0; + self.loadingLabel.text = NSLocalizedString(@"CONNECTING_TEXT", nil); + self.loadingLabel.hidden = NO; + self.progressBar.hidden = YES; + } else if ([status isEqualToString:@"downloading"]) { + NSLog(@"[BakerShelf] '%@' is Downloading...", self.issue.ID); + [self.spinner startAnimating]; + + self.actionButton.hidden = YES; + self.archiveButton.hidden = YES; + self.progressBar.progress = 0; + self.loadingLabel.text = NSLocalizedString(@"DOWNLOADING_TEXT", nil); + self.loadingLabel.hidden = NO; + self.progressBar.hidden = NO; + } else if ([status isEqualToString:@"downloaded"]) { + NSLog(@"[BakerShelf] '%@' is Ready to be Read.", self.issue.ID); + [self.actionButton setTitle:NSLocalizedString(@"ACTION_DOWNLOADED_TEXT", nil) forState:UIControlStateNormal]; + [self.spinner stopAnimating]; + + self.actionButton.hidden = NO; + self.archiveButton.hidden = NO; + self.loadingLabel.hidden = YES; + self.progressBar.hidden = YES; + } else if ([status isEqualToString:@"bundled"]) { + [self.actionButton setTitle:NSLocalizedString(@"ACTION_DOWNLOADED_TEXT", nil) forState:UIControlStateNormal]; + [self.spinner stopAnimating]; + + self.actionButton.hidden = NO; + self.archiveButton.hidden = YES; + self.loadingLabel.hidden = YES; + self.progressBar.hidden = YES; + } else if ([status isEqualToString:@"opening"]) { + [self.spinner startAnimating]; + + self.actionButton.hidden = YES; + self.archiveButton.hidden = YES; + self.loadingLabel.text = NSLocalizedString(@"OPENING_TEXT", nil); + self.loadingLabel.hidden = NO; + self.progressBar.hidden = YES; + } else if ([status isEqualToString:@"purchasable"]) { + [self.actionButton setTitle:self.issue.price forState:UIControlStateNormal]; + [self.spinner stopAnimating]; + + self.actionButton.hidden = NO; + self.archiveButton.hidden = YES; + self.progressBar.hidden = YES; + self.loadingLabel.hidden = YES; + } else if ([status isEqualToString:@"purchasing"]) { + NSLog(@"[BakerShelf] '%@' is being Purchased...", self.issue.ID); + [self.spinner startAnimating]; + + self.loadingLabel.text = NSLocalizedString(@"BUYING_TEXT", nil); + + self.actionButton.hidden = YES; + self.archiveButton.hidden = YES; + self.progressBar.hidden = YES; + self.loadingLabel.hidden = NO; + } else if ([status isEqualToString:@"purchased"]) { + NSLog(@"[BakerShelf] '%@' is Purchased.", self.issue.ID); + + [self.actionButton setTitle:NSLocalizedString(@"ACTION_REMOTE_TEXT", nil) forState:UIControlStateNormal]; + [self.spinner stopAnimating]; + + self.actionButton.hidden = NO; + self.archiveButton.hidden = YES; + self.progressBar.hidden = YES; + self.loadingLabel.hidden = YES; + } else if ([status isEqualToString:@"unpriced"]) { + [self.spinner startAnimating]; + + self.loadingLabel.text = NSLocalizedString(@"RETRIEVING_TEXT", nil); + + self.actionButton.hidden = YES; + self.archiveButton.hidden = YES; + self.progressBar.hidden = YES; + self.loadingLabel.hidden = NO; + } + + [self refreshContentWithCache:cache]; + + self.currentStatus = status; +} + +#pragma mark - Issue management + +- (void)actionButtonPressed:(UIButton*)sender { + NSString *status = [self.issue getStatus]; + if ([status isEqualToString:@"remote"] || [status isEqualToString:@"purchased"]) { + if ([BKRSettings sharedSettings].isNewsstand) { + [[NSNotificationCenter defaultCenter] postNotificationName:@"BakerIssueDownload" object:self]; // -> Baker Analytics Event + [self download]; + } + } else if ([status isEqualToString:@"downloaded"] || [status isEqualToString:@"bundled"]) { + [[NSNotificationCenter defaultCenter] postNotificationName:@"BakerIssueOpen" object:self]; // -> Baker Analytics Event + [self read]; + } else if ([status isEqualToString:@"downloading"]) { + // TODO: assuming it is supported by NewsstandKit, implement a "Cancel" operation + } else if ([status isEqualToString:@"purchasable"]) { + if ([BKRSettings sharedSettings].isNewsstand) { + [[NSNotificationCenter defaultCenter] postNotificationName:@"BakerIssuePurchase" object:self]; // -> Baker Analytics Event + [self buy]; + } + } +} + +- (void)download { + [self.issue download]; +} + +- (void)buy { + [self addPurchaseObserver:@selector(handleIssuePurchased:) name:@"notification_issue_purchased"]; + [self addPurchaseObserver:@selector(handleIssuePurchaseFailed:) name:@"notification_issue_purchase_failed"]; + + if (![purchasesManager purchase:self.issue.productID]) { + // Still retrieving SKProduct: delay purchase + purchaseDelayed = YES; + + [self removePurchaseObserver:@"notification_issue_purchased"]; + [self removePurchaseObserver:@"notification_issue_purchase_failed"]; + + [purchasesManager retrievePriceFor:self.issue.productID]; + + self.issue.transientStatus = BakerIssueTransientStatusUnpriced; + [self refresh]; + } else { + self.issue.transientStatus = BakerIssueTransientStatusPurchasing; + [self refresh]; + } +} + +- (void)handleIssuePurchased:(NSNotification*)notification { + SKPaymentTransaction *transaction = notification.userInfo[@"transaction"]; + + if ([transaction.payment.productIdentifier isEqualToString:self.issue.productID]) { + + [self removePurchaseObserver:@"notification_issue_purchased"]; + [self removePurchaseObserver:@"notification_issue_purchase_failed"]; + + [purchasesManager markAsPurchased:transaction.payment.productIdentifier]; + + if ([purchasesManager finishTransaction:transaction]) { + if (!transaction.originalTransaction) { + // Do not show alert on restoring a transaction + [BKRUtils showAlertWithTitle:NSLocalizedString(@"ISSUE_PURCHASE_SUCCESSFUL_TITLE", nil) + message:[NSString stringWithFormat:NSLocalizedString(@"ISSUE_PURCHASE_SUCCESSFUL_MESSAGE", nil), self.issue.title] + buttonTitle:NSLocalizedString(@"ISSUE_PURCHASE_SUCCESSFUL_CLOSE", nil)]; + } + } else { + [BKRUtils showAlertWithTitle:NSLocalizedString(@"TRANSACTION_RECORDING_FAILED_TITLE", nil) + message:NSLocalizedString(@"TRANSACTION_RECORDING_FAILED_MESSAGE", nil) + buttonTitle:NSLocalizedString(@"TRANSACTION_RECORDING_FAILED_CLOSE", nil)]; + } + + self.issue.transientStatus = BakerIssueTransientStatusNone; + + [purchasesManager retrievePurchasesFor:[NSSet setWithObject:self.issue.productID] withCallback:^(NSDictionary *purchases) { + [self refresh]; + }]; + } +} + +- (void)handleIssuePurchaseFailed:(NSNotification*)notification { + SKPaymentTransaction *transaction = (notification.userInfo)[@"transaction"]; + + if ([transaction.payment.productIdentifier isEqualToString:self.issue.productID]) { + // Show an error, unless it was the user who cancelled the transaction + if (transaction.error.code != SKErrorPaymentCancelled) { + [BKRUtils showAlertWithTitle:NSLocalizedString(@"ISSUE_PURCHASE_FAILED_TITLE", nil) + message:[transaction.error localizedDescription] + buttonTitle:NSLocalizedString(@"ISSUE_PURCHASE_FAILED_CLOSE", nil)]; + } + + [self removePurchaseObserver:@"notification_issue_purchased"]; + [self removePurchaseObserver:@"notification_issue_purchase_failed"]; + + self.issue.transientStatus = BakerIssueTransientStatusNone; + [self refresh]; + } +} + +- (void)handleIssueRestored:(NSNotification*)notification { + SKPaymentTransaction *transaction = (notification.userInfo)[@"transaction"]; + + if ([transaction.payment.productIdentifier isEqualToString:self.issue.productID]) { + [purchasesManager markAsPurchased:transaction.payment.productIdentifier]; + + if (![purchasesManager finishTransaction:transaction]) { + NSLog(@"[BakerShelf] Could not confirm purchase restore with remote server for %@", transaction.payment.productIdentifier); + } + + self.issue.transientStatus = BakerIssueTransientStatusNone; + [self refresh]; + } +} + +- (void)setPrice:(NSString*)price { + self.issue.price = price; + if (purchaseDelayed) { + purchaseDelayed = NO; + [self buy]; + } else { + [self refresh]; + } +} + +- (void)read { + self.issue.transientStatus = BakerIssueTransientStatusOpening; + [self refresh]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"read_issue_request" object:self]; +} + +#pragma mark - Newsstand download management + +- (void)handleDownloadStarted:(NSNotification*)notification { + [self refresh]; +} + +- (void)handleDownloadProgressing:(NSNotification*)notification { + float bytesWritten = [(notification.userInfo)[@"totalBytesWritten"] floatValue]; + float bytesExpected = [(notification.userInfo)[@"expectedTotalBytes"] floatValue]; + + if ([self.currentStatus isEqualToString:@"connecting"]) { + self.issue.transientStatus = BakerIssueTransientStatusDownloading; + [self refresh]; + } + [self.progressBar setProgress:(bytesWritten / bytesExpected) animated:YES]; +} + +- (void)handleDownloadFinished:(NSNotification*)notification { + self.issue.transientStatus = BakerIssueTransientStatusNone; + [self refresh]; +} + +- (void)handleDownloadError:(NSNotification*)notification { + [BKRUtils showAlertWithTitle:NSLocalizedString(@"DOWNLOAD_FAILED_TITLE", nil) + message:NSLocalizedString(@"DOWNLOAD_FAILED_MESSAGE", nil) + buttonTitle:NSLocalizedString(@"DOWNLOAD_FAILED_CLOSE", nil)]; + + self.issue.transientStatus = BakerIssueTransientStatusNone; + [self refresh]; +} + +- (void)handleUnzipError:(NSNotification*)notification { + [BKRUtils showAlertWithTitle:NSLocalizedString(@"UNZIP_FAILED_TITLE", nil) + message:NSLocalizedString(@"UNZIP_FAILED_MESSAGE", nil) + buttonTitle:NSLocalizedString(@"UNZIP_FAILED_CLOSE", nil)]; + + self.issue.transientStatus = BakerIssueTransientStatusNone; + [self refresh]; +} + +#pragma mark - Newsstand archive management + +- (void)archiveButtonPressed:(UIButton*)sender { + UIAlertView *updateAlert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"ARCHIVE_ALERT_TITLE", nil) + message:NSLocalizedString(@"ARCHIVE_ALERT_MESSAGE", nil) + delegate:self + cancelButtonTitle:NSLocalizedString(@"ARCHIVE_ALERT_BUTTON_CANCEL", nil) + otherButtonTitles:NSLocalizedString(@"ARCHIVE_ALERT_BUTTON_OK", nil), nil + ]; + [updateAlert show]; +} + +- (void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { + if (buttonIndex == 1){ + [[NSNotificationCenter defaultCenter] postNotificationName:@"BakerIssueArchive" object:self]; // -> Baker Analytics Event + + NKLibrary *nkLib = [NKLibrary sharedLibrary]; + NKIssue *nkIssue = [nkLib issueWithName:self.issue.ID]; + NSString *name = nkIssue.name; + NSDate *date = nkIssue.date; + + [nkLib removeIssue:nkIssue]; + + nkIssue = [nkLib addIssueWithName:name date:date]; + self.issue.path = [[nkIssue contentURL] path]; + + [self refresh]; + } +} + +#pragma mark - Helper methods + +- (void)addPurchaseObserver:(SEL)notificationSelector name:(NSString*)notificationName { + if ([BKRSettings sharedSettings].isNewsstand) { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:notificationSelector + name:notificationName + object:purchasesManager]; + } +} + +- (void)removePurchaseObserver:(NSString*)notificationName { + if ([BKRSettings sharedSettings].isNewsstand) { + [[NSNotificationCenter defaultCenter] removeObserver:self + name:notificationName + object:purchasesManager]; + } +} + +- (void)addIssueObserver:(SEL)notificationSelector name:(NSString*)notificationName { + if ([BKRSettings sharedSettings].isNewsstand) { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:notificationSelector + name:notificationName + object:nil]; + } +} + ++ (UI)getIssueContentMeasures { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + UI iPad = { + .cellPadding = 30, + .thumbWidth = 135, + .thumbHeight = 180, + .contentOffset = 184 + }; + return iPad; + } else { + UI iPhone = { + .cellPadding = 22, + .thumbWidth = 87, + .thumbHeight = 116, + .contentOffset = 128 + }; + return iPhone; + } +} + ++ (int)getIssueCellHeight { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + return 240; + } else { + return 190; + } +} + ++ (CGSize)getIssueCellSizeForOrientation:(UIInterfaceOrientation)orientation { + + CGFloat screenWidth = [[UIScreen mainScreen] bkrWidthForOrientation:orientation]; + int cellHeight = [BKRIssueViewController getIssueCellHeight]; + + if (screenWidth > 700) { + return CGSizeMake(screenWidth/2, cellHeight); + } else { + return CGSizeMake(screenWidth, cellHeight); + } + + /* + CGRect screenRect = [UIScreen mainScreen].bounds; + CGFloat screenWidth = screenRect.size.width; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + return CGSizeMake(screenWidth / 2, [IssueViewController getIssueCellHeight]); + //return CGSizeMake((MIN(screenRect.size.width, screenRect.size.height) - 10) / 2, [IssueViewController getIssueCellHeight]); + } else { + if (screenWidth > 700) { + return CGSizeMake((screenWidth - 10) / 2, [IssueViewController getIssueCellHeight]); + } else { + return CGSizeMake(MIN(screenRect.size.width, screenRect.size.height) - 10, [IssueViewController getIssueCellHeight]); + } + } + */ + +} + +@end diff --git a/BakerShelf/IssuesManager.h b/BakerShelf/BKRIssuesManager.h similarity index 79% rename from BakerShelf/IssuesManager.h rename to BakerShelf/BKRIssuesManager.h index c4535ff6..1fcf8c60 100644 --- a/BakerShelf/IssuesManager.h +++ b/BakerShelf/BKRIssuesManager.h @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -31,23 +32,22 @@ #import #import -#import "BakerIssue.h" +#import "BKRIssue.h" -@interface IssuesManager : NSObject +@interface BKRIssuesManager : NSObject -@property (copy, nonatomic) NSArray *issues; -@property (retain, nonatomic) NSString *shelfManifestPath; +@property (nonatomic, copy) NSArray *issues; +@property (nonatomic, strong) NSString *shelfManifestPath; +@property (nonatomic, copy) NSArray *categories; #pragma mark - Singleton -+ (IssuesManager *)sharedInstance; ++ (BKRIssuesManager*)sharedInstance; -#ifdef BAKER_NEWSSTAND --(void)refresh:(void (^)(BOOL)) callback; --(NSSet *)productIDs; --(BOOL)hasProductIDs; --(BakerIssue *)latestIssue; -#endif -+ (NSArray *)localBooksList; +- (void)refresh:(void (^)(BOOL))callback; +- (NSSet*)productIDs; +- (BOOL)hasProductIDs; +- (BKRIssue*)latestIssue; ++ (NSArray*)localBooksList; @end diff --git a/BakerShelf/IssuesManager.m b/BakerShelf/BKRIssuesManager.m similarity index 65% rename from BakerShelf/IssuesManager.m rename to BakerShelf/BKRIssuesManager.m index 83451435..bdcc2faa 100644 --- a/BakerShelf/IssuesManager.m +++ b/BakerShelf/BKRIssuesManager.m @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -29,24 +30,21 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -#import "IssuesManager.h" -#import "BakerIssue.h" -#import "Utils.h" -#import "BakerAPI.h" +#import "BKRIssuesManager.h" +#import "BKRIssue.h" +#import "BKRUtils.h" +#import "BKRBakerAPI.h" +#import "BKRSettings.h" +#import "NSObject+BakerExtensions.h" -@implementation IssuesManager +@implementation BKRIssuesManager -@synthesize issues; -@synthesize shelfManifestPath; - --(id)init { +- (id)init { self = [super init]; if (self) { - self.issues = nil; - - NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - self.shelfManifestPath = [cachePath stringByAppendingPathComponent:@"shelf.json"]; + _issues = nil; + _shelfManifestPath = [self.bkrCachePath stringByAppendingPathComponent:@"shelf.json"]; } return self; @@ -54,18 +52,17 @@ -(id)init { #pragma mark - Singleton -+ (IssuesManager *)sharedInstance { ++ (BKRIssuesManager*)sharedInstance { static dispatch_once_t once; - static IssuesManager *sharedInstance; + static BKRIssuesManager *sharedInstance; dispatch_once(&once, ^{ sharedInstance = [[self alloc] init]; }); return sharedInstance; } -#ifdef BAKER_NEWSSTAND --(void)refresh:(void (^)(BOOL)) callback { - [self getShelfJSON:^(NSData* json) { +- (void)refresh:(void (^)(BOOL))callback { + [self getShelfJSON:^(NSData *json) { if (json) { NSError* error = nil; NSArray* jsonArr = [NSJSONSerialization JSONObjectWithData:json @@ -75,17 +72,34 @@ -(void)refresh:(void (^)(BOOL)) callback { [self updateNewsstandIssuesList:jsonArr]; NSMutableArray *tmpIssues = [NSMutableArray array]; + NSMutableArray *tmpCategories = [NSMutableArray array]; [jsonArr enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - BakerIssue *issue = [[[BakerIssue alloc] initWithIssueData:obj] autorelease]; + BKRIssue *issue = [[BKRIssue alloc] initWithIssueData:obj]; + + // Append categories + [issue.categories enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + if(![tmpCategories containsObject:obj]) { + [tmpCategories addObject:obj]; + } + }]; + + // Add issue to temporary issue list [tmpIssues addObject:issue]; }]; + // Sort categories + [tmpCategories sortUsingSelector:@selector(compare:)]; + _categories = tmpCategories; + + // Sort issues self.issues = [tmpIssues sortedArrayUsingComparator:^NSComparisonResult(id a, id b) { - NSDate *first = [Utils dateWithFormattedString:[(BakerIssue*)a date]]; - NSDate *second = [Utils dateWithFormattedString:[(BakerIssue*)b date]]; + NSDate *first = [BKRUtils dateWithFormattedString:[(BKRIssue*)a date]]; + NSDate *second = [BKRUtils dateWithFormattedString:[(BKRIssue*)b date]]; return [second compare:first]; }]; + [self updateNewsstandIcon]; + if (callback) { callback(YES); } @@ -98,15 +112,40 @@ -(void)refresh:(void (^)(BOOL)) callback { }]; } --(void)getShelfJSON:(void (^)(NSData*)) callback { - BakerAPI *api = [BakerAPI sharedInstance]; - [api getShelfJSON:^(NSData* json) { +- (void)updateNewsstandIcon { + + if ([BKRSettings sharedSettings].newsstandLatestIssueCover) { + BKRIssue *latestIssue = nil; + for (BKRIssue *issue in self.issues) { + if ([[issue getStatus] isEqualToString:@"downloaded"]) { + return; + } + latestIssue = issue; + break; + } + + NSLog(@"Setting newsstand cover icon from latest issue: %@ at %@", latestIssue.title, latestIssue.date); + [latestIssue getCoverWithCache:YES andBlock:^(UIImage *image) { + if (image) { + [[UIApplication sharedApplication] setNewsstandIconImage:image]; + } + }]; + } else { + UIImage *image = [UIImage imageNamed:@"newsstand-app-icon"]; + [[UIApplication sharedApplication] setNewsstandIconImage:image]; + } + +} + +- (void)getShelfJSON:(void(^)(NSData*))callback { + BKRBakerAPI *api = [BKRBakerAPI sharedInstance]; + [api getShelfJSON:^(NSData *json) { NSError *cachedShelfError = nil; if (json) { // Cache the shelf manifest [[NSFileManager defaultManager] createFileAtPath:self.shelfManifestPath contents:nil attributes:nil]; - NSError* error = nil; + NSError *error = nil; [json writeToFile:self.shelfManifestPath options:NSDataWritingAtomic error:&error]; @@ -132,13 +171,13 @@ -(void)getShelfJSON:(void (^)(NSData*)) callback { }]; } --(void)updateNewsstandIssuesList:(NSArray *)issuesList { +- (void)updateNewsstandIssuesList:(NSArray*)issuesList { NKLibrary *nkLib = [NKLibrary sharedLibrary]; NSMutableArray *discardedIssues = [NSMutableArray arrayWithArray:[nkLib issues]]; for (NSDictionary *issue in issuesList) { - NSDate *date = [Utils dateWithFormattedString:[issue objectForKey:@"date"]]; - NSString *name = [issue objectForKey:@"name"]; + NSDate *date = [BKRUtils dateWithFormattedString:issue[@"date"]]; + NSString *name = issue[@"name"]; NKIssue *nkIssue = [nkLib issueWithName:name]; if(!nkIssue) { @@ -161,9 +200,9 @@ -(void)updateNewsstandIssuesList:(NSArray *)issuesList { } } --(NSSet *)productIDs { +- (NSSet*)productIDs { NSMutableSet *set = [NSMutableSet set]; - for (BakerIssue *issue in self.issues) { + for (BKRIssue *issue in self.issues) { if (issue.productID) { [set addObject:issue.productID]; } @@ -172,26 +211,25 @@ -(NSSet *)productIDs { } - (BOOL)hasProductIDs { - return [[self productIDs] count] > 0; + return self.productIDs.count > 0; } -- (BakerIssue *)latestIssue { - return [issues objectAtIndex:0]; +- (BKRIssue*)latestIssue { + return self.issues[0]; } -#endif -+ (NSArray *)localBooksList { - NSMutableArray *booksList = [NSMutableArray array]; ++ (NSArray*)localBooksList { + NSMutableArray *booksList = [NSMutableArray array]; NSFileManager *localFileManager = [NSFileManager defaultManager]; - NSString *booksDir = [[NSBundle mainBundle] pathForResource:@"books" ofType:nil]; + NSString *booksDir = [[NSBundle mainBundle] pathForResource:@"books" ofType:nil]; NSArray *dirContents = [localFileManager contentsOfDirectoryAtPath:booksDir error:nil]; for (NSString *file in dirContents) { NSString *manifestFile = [booksDir stringByAppendingPathComponent:[file stringByAppendingPathComponent:@"book.json"]]; if ([localFileManager fileExistsAtPath:manifestFile]) { - BakerBook *book = [[[BakerBook alloc] initWithBookPath:[booksDir stringByAppendingPathComponent:file] bundled:YES] autorelease]; + BKRBook *book = [[BKRBook alloc] initWithBookPath:[booksDir stringByAppendingPathComponent:file] bundled:YES]; if (book) { - BakerIssue *issue = [[[BakerIssue alloc] initWithBakerBook:book] autorelease]; + BKRIssue *issue = [[BKRIssue alloc] initWithBakerBook:book]; [booksList addObject:issue]; } else { NSLog(@"[BakerShelf] ERROR: Book %@ could not be initialized. Is 'book.json' correct and valid?", file); @@ -204,11 +242,4 @@ + (NSArray *)localBooksList { return [NSArray arrayWithArray:booksList]; } --(void)dealloc { - [issues release]; - [shelfManifestPath release]; - - [super dealloc]; -} - @end diff --git a/BakerShelf/PurchasesManager.h b/BakerShelf/BKRPurchasesManager.h similarity index 63% rename from BakerShelf/PurchasesManager.h rename to BakerShelf/BKRPurchasesManager.h index e31abe6a..a979735a 100644 --- a/BakerShelf/PurchasesManager.h +++ b/BakerShelf/BKRPurchasesManager.h @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -29,54 +30,52 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -#import "Constants.h" #import #import -#ifdef BAKER_NEWSSTAND -@interface PurchasesManager : NSObject { +@interface BKRPurchasesManager : NSObject { NSMutableDictionary *_purchases; BOOL _enableProductRequestFailureNotifications; } -@property (retain, nonatomic) NSMutableDictionary *products; -@property (retain, nonatomic) NSNumberFormatter *numberFormatter; -@property (nonatomic) BOOL subscribed; +@property (nonatomic, strong) NSMutableDictionary *products; +@property (nonatomic, strong) NSNumberFormatter *numberFormatter; +@property (nonatomic, assign) BOOL subscribed; #pragma mark - Singleton -+ (PurchasesManager *)sharedInstance; ++ (BKRPurchasesManager*)sharedInstance; #pragma mark - Purchased flag -- (BOOL)isMarkedAsPurchased:(NSString *)productID; -- (void)markAsPurchased:(NSString *)productID; +- (BOOL)isMarkedAsPurchased:(NSString*)productID; +- (void)markAsPurchased:(NSString*)productID; -#pragma mark - Prices +#pragma mark - Prices and display information -- (void)retrievePricesFor:(NSSet *)productIDs; -- (void)retrievePricesFor:(NSSet *)productIDs andEnableFailureNotifications:(BOOL)enable; +- (void)retrievePricesFor:(NSSet*)productIDs; +- (void)retrievePricesFor:(NSSet*)productIDs andEnableFailureNotifications:(BOOL)enable; -- (void)retrievePriceFor:(NSString *)productID; -- (void)retrievePriceFor:(NSString *)productID andEnableFailureNotification:(BOOL)enable; +- (void)retrievePriceFor:(NSString*)productID; +- (void)retrievePriceFor:(NSString*)productID andEnableFailureNotification:(BOOL)enable; -- (NSString *)priceFor:(NSString *)productID; +- (NSString*)priceFor:(NSString*)productID; +- (NSString*)displayTitleFor:(NSString*)productID; #pragma mark - Purchases -- (BOOL)purchase:(NSString *)productID; -- (BOOL)finishTransaction:(SKPaymentTransaction *)transaction; +- (BOOL)purchase:(NSString*)productID; +- (BOOL)finishTransaction:(SKPaymentTransaction*)transaction; - (void)restore; -- (void)retrievePurchasesFor:(NSSet *)productIDs withCallback:(void (^)(NSDictionary*))callback; -- (BOOL)isPurchased:(NSString *)productID; +- (void)retrievePurchasesFor:(NSSet*)productIDs withCallback:(void (^)(NSDictionary*))callback; +- (BOOL)isPurchased:(NSString*)productID; #pragma mark - Products -- (SKProduct *)productFor:(NSString *)productID; +- (SKProduct*)productFor:(NSString*)productID; #pragma mark - Subscriptions - (BOOL)hasSubscriptions; @end -#endif diff --git a/BakerShelf/PurchasesManager.m b/BakerShelf/BKRPurchasesManager.m similarity index 68% rename from BakerShelf/PurchasesManager.m rename to BakerShelf/BKRPurchasesManager.m index d6849b1c..ff0b9151 100644 --- a/BakerShelf/PurchasesManager.m +++ b/BakerShelf/BKRPurchasesManager.m @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -29,26 +30,23 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -#import "PurchasesManager.h" -#import "BakerAPI.h" +#import "BKRPurchasesManager.h" +#import "BKRBakerAPI.h" +#import "BKRSettings.h" -#import "NSData+Base64.h" -#import "NSMutableURLRequest+WebServiceClient.h" -#import "Utils.h" -#import "NSURL+Extensions.h" +#import "NSData+BakerExtensions.h" +#import "NSMutableURLRequest+BakerExtensions.h" +#import "BKRUtils.h" +#import "NSURL+BakerExtensions.h" -#ifdef BAKER_NEWSSTAND -@implementation PurchasesManager +@implementation BKRPurchasesManager -@synthesize products; -@synthesize subscribed; - --(id)init { +- (id)init { self = [super init]; if (self) { - self.products = [[[NSMutableDictionary alloc] init] autorelease]; - self.subscribed = NO; + _products = [[NSMutableDictionary alloc] init]; + _subscribed = NO; _purchases = [[NSMutableDictionary alloc] init]; @@ -64,9 +62,9 @@ -(id)init { #pragma mark - Singleton -+ (PurchasesManager *)sharedInstance { ++ (BKRPurchasesManager*)sharedInstance { static dispatch_once_t once; - static PurchasesManager *sharedInstance; + static BKRPurchasesManager *sharedInstance; dispatch_once(&once, ^{ sharedInstance = [[self alloc] init]; }); @@ -75,21 +73,21 @@ + (PurchasesManager *)sharedInstance { #pragma mark - Purchased flag -- (BOOL)isMarkedAsPurchased:(NSString *)productID { +- (BOOL)isMarkedAsPurchased:(NSString*)productID { return [[NSUserDefaults standardUserDefaults] boolForKey:productID]; } -- (void)markAsPurchased:(NSString *)productID { +- (void)markAsPurchased:(NSString*)productID { [[NSUserDefaults standardUserDefaults] setBool:YES forKey:productID]; [[NSUserDefaults standardUserDefaults] synchronize]; } #pragma mark - Prices -- (void)retrievePricesFor:(NSSet *)productIDs { +- (void)retrievePricesFor:(NSSet*)productIDs { [self retrievePricesFor:productIDs andEnableFailureNotifications:YES]; } -- (void)retrievePricesFor:(NSSet *)productIDs andEnableFailureNotifications:(BOOL)enable { +- (void)retrievePricesFor:(NSSet*)productIDs andEnableFailureNotifications:(BOOL)enable { if ([productIDs count] > 0) { _enableProductRequestFailureNotifications = enable; @@ -101,15 +99,15 @@ - (void)retrievePricesFor:(NSSet *)productIDs andEnableFailureNotifications:(BOO } } -- (void)retrievePriceFor:(NSString *)productID { +- (void)retrievePriceFor:(NSString*)productID { [self retrievePriceFor:productID andEnableFailureNotification:YES]; } -- (void)retrievePriceFor:(NSString *)productID andEnableFailureNotification:(BOOL)enable { +- (void)retrievePriceFor:(NSString*)productID andEnableFailureNotification:(BOOL)enable { NSSet *productIDs = [NSSet setWithObject:productID]; [self retrievePricesFor:productIDs andEnableFailureNotifications:enable]; } -- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { +- (void)productsRequest:(SKProductsRequest*)request didReceiveResponse:(SKProductsResponse*)response { [self logProducts:response.products]; for (NSString *productID in response.invalidProductIdentifiers) { @@ -118,36 +116,38 @@ - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProdu NSMutableSet *ids = [NSMutableSet setWithCapacity:response.products.count]; for (SKProduct *skProduct in response.products) { - [self.products setObject:skProduct forKey:skProduct.productIdentifier]; + (self.products)[skProduct.productIdentifier] = skProduct; [ids addObject:skProduct.productIdentifier]; } - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:ids forKey:@"ids"]; + NSDictionary *userInfo = @{@"ids": ids}; [[NSNotificationCenter defaultCenter] postNotificationName:@"notification_products_retrieved" object:self userInfo:userInfo]; - [request release]; } -- (void)logProducts:(NSArray *)skProducts { - NSLog(@"Received %d products from App Store", [skProducts count]); +- (void)logProducts:(NSArray*)skProducts { + NSLog(@"Received %lu products from App Store", (unsigned long)[skProducts count]); for (SKProduct *skProduct in skProducts) { NSLog(@"- %@", skProduct.productIdentifier); } } -- (void)request:(SKRequest *)request didFailWithError:(NSError *)error { +- (void)request:(SKRequest*)request didFailWithError:(NSError*)error { NSLog(@"App Store request failure: %@", error); if (_enableProductRequestFailureNotifications) { - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:error forKey:@"error"]; + NSDictionary *userInfo = @{@"error": error}; [[NSNotificationCenter defaultCenter] postNotificationName:@"notification_products_request_failed" object:self userInfo:userInfo]; } + + // @TODO: REMOVE CODE + NSDictionary *userInfo = @{@"ids": @[]}; + [[NSNotificationCenter defaultCenter] postNotificationName:@"notification_products_retrieved" object:self userInfo:userInfo]; - [request release]; } -- (NSString *)priceFor:(NSString *)productID { - SKProduct *product = [products objectForKey:productID]; +- (NSString*)priceFor:(NSString*)productID { + SKProduct *product = self.products[productID]; if (product) { [_numberFormatter setLocale:product.priceLocale]; return [_numberFormatter stringFromNumber:product.price]; @@ -155,9 +155,20 @@ - (NSString *)priceFor:(NSString *)productID { return nil; } +- (NSString*)displayTitleFor:(NSString*)productID { + + if ([BKRSettings sharedSettings].useiTunesConnectLocalizations) { + SKProduct *product = self.products[productID]; + if(product) { + return product.localizedTitle; + } + } + return NSLocalizedString(productID, nil); +} + #pragma mark - Purchases -- (BOOL)purchase:(NSString *)productID { +- (BOOL)purchase:(NSString*)productID { SKProduct *product = [self productFor:productID]; if (product) { SKPayment *payment = [SKPayment paymentWithProduct:product]; @@ -171,7 +182,7 @@ - (BOOL)purchase:(NSString *)productID { } } -- (BOOL)finishTransaction:(SKPaymentTransaction *)transaction { +- (BOOL)finishTransaction:(SKPaymentTransaction*)transaction { if ([self recordTransaction:transaction]) { [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; return YES; @@ -180,33 +191,40 @@ - (BOOL)finishTransaction:(SKPaymentTransaction *)transaction { } } -- (BOOL)recordTransaction:(SKPaymentTransaction *)transaction { +- (BOOL)recordTransaction:(SKPaymentTransaction*)transaction { [[NSUserDefaults standardUserDefaults] setObject:transaction.transactionIdentifier forKey:@"receipt"]; - BakerAPI *api = [BakerAPI sharedInstance]; + BKRBakerAPI *api = [BKRBakerAPI sharedInstance]; if ([api canPostPurchaseReceipt]) { - NSString *receipt = [transaction.transactionReceipt base64EncodedString]; - NSString *type = [self transactionType:transaction]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + NSString *receipt = [transaction.transactionReceipt bkrBase64EncodedString]; +#pragma clang diagnostic pop +// NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; +// NSString *receipt = [[NSData dataWithContentsOfURL:receiptURL] bkrBase64EncodedString]; +// NSLog(@"receipt1: %@", receipt1); +// NSLog(@"receipt: %@", receipt); + NSString *type = [self transactionType:transaction]; return [api postPurchaseReceipt:receipt ofType:type]; } return YES; } -- (NSString *)transactionType:(SKPaymentTransaction *)transaction { +- (NSString*)transactionType:(SKPaymentTransaction*)transaction { NSString *productID = transaction.payment.productIdentifier; - if ([productID isEqualToString:FREE_SUBSCRIPTION_PRODUCT_ID]) { + if ([productID isEqualToString:[BKRSettings sharedSettings].freeSubscriptionProductId]) { return @"free-subscription"; - } else if ([AUTO_RENEWABLE_SUBSCRIPTION_PRODUCT_IDS containsObject:productID]) { + } else if ([[BKRSettings sharedSettings].autoRenewableSubscriptionProductIds containsObject:productID]) { return @"auto-renewable-subscription"; } else { return @"issue"; } } -- (void)retrievePurchasesFor:(NSSet *)productIDs withCallback:(void (^)(NSDictionary*))callback { - BakerAPI *api = [BakerAPI sharedInstance]; +- (void)retrievePurchasesFor:(NSSet*)productIDs withCallback:(void (^)(NSDictionary*))callback { + BKRBakerAPI *api = [BKRBakerAPI sharedInstance]; if ([api canGetPurchasesJSON]) { [api getPurchasesJSON:^(NSData* jsonResponse) { @@ -218,11 +236,11 @@ - (void)retrievePurchasesFor:(NSSet *)productIDs withCallback:(void (^)(NSDictio // TODO: handle error if (purchasesResponse) { - NSArray *purchasedIssues = [purchasesResponse objectForKey:@"issues"]; - self.subscribed = [[purchasesResponse objectForKey:@"subscribed"] boolValue]; + NSArray *purchasedIssues = purchasesResponse[@"issues"]; + self.subscribed = [purchasesResponse[@"subscribed"] boolValue]; [productIDs enumerateObjectsUsingBlock:^(id obj, BOOL *stop) { - [_purchases setObject:[NSNumber numberWithBool:[purchasedIssues containsObject:obj]] forKey:obj]; + _purchases[obj] = @([purchasedIssues containsObject:obj]); }]; } else { NSLog(@"ERROR: Could not parse response from purchases API call. Received: %@", jsonResponse); @@ -238,8 +256,8 @@ - (void)retrievePurchasesFor:(NSSet *)productIDs withCallback:(void (^)(NSDictio } } -- (BOOL)isPurchased:(NSString *)productID { - id purchased = [_purchases objectForKey:productID]; +- (BOOL)isPurchased:(NSString*)productID { + id purchased = _purchases[productID]; if (purchased) { return [purchased boolValue]; } else { @@ -249,7 +267,7 @@ - (BOOL)isPurchased:(NSString *)productID { #pragma mark - Payment queue -- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { +- (void)paymentQueue:(SKPaymentQueue*)queue updatedTransactions:(NSArray*)transactions { [self logTransactions:transactions]; BOOL isRestoring = NO; @@ -278,8 +296,8 @@ - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)tran } } -- (void)logTransactions:(NSArray *)transactions { - NSLog(@"Received %d transactions from App Store", [transactions count]); +- (void)logTransactions:(NSArray*)transactions { + NSLog(@"Received %lu transactions from App Store", (unsigned long)[transactions count]); for(SKPaymentTransaction *transaction in transactions) { switch (transaction.transactionState) { case SKPaymentTransactionStatePurchasing: @@ -301,11 +319,11 @@ - (void)logTransactions:(NSArray *)transactions { } } -- (void)completeTransaction:(SKPaymentTransaction *)transaction { - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:transaction forKey:@"transaction"]; +- (void)completeTransaction:(SKPaymentTransaction*)transaction { + NSDictionary *userInfo = @{@"transaction": transaction}; NSString *productId = transaction.payment.productIdentifier; - if ([productId isEqualToString:FREE_SUBSCRIPTION_PRODUCT_ID] || [AUTO_RENEWABLE_SUBSCRIPTION_PRODUCT_IDS containsObject:productId]) { + if ([productId isEqualToString:[BKRSettings sharedSettings].freeSubscriptionProductId] || [[BKRSettings sharedSettings].autoRenewableSubscriptionProductIds containsObject:productId]) { [[NSNotificationCenter defaultCenter] postNotificationName:@"notification_subscription_purchased" object:self userInfo:userInfo]; } else if ([self productFor:productId]) { [[NSNotificationCenter defaultCenter] postNotificationName:@"notification_issue_purchased" object:self userInfo:userInfo]; @@ -314,11 +332,11 @@ - (void)completeTransaction:(SKPaymentTransaction *)transaction { } } -- (void)restoreTransaction:(SKPaymentTransaction *)transaction { - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:transaction forKey:@"transaction"]; +- (void)restoreTransaction:(SKPaymentTransaction*)transaction { + NSDictionary *userInfo = @{@"transaction": transaction}; NSString *productId = transaction.payment.productIdentifier; - if ([productId isEqualToString:FREE_SUBSCRIPTION_PRODUCT_ID] || [AUTO_RENEWABLE_SUBSCRIPTION_PRODUCT_IDS containsObject:productId]) { + if ([productId isEqualToString:[BKRSettings sharedSettings].freeSubscriptionProductId] || [[BKRSettings sharedSettings].autoRenewableSubscriptionProductIds containsObject:productId]) { [[NSNotificationCenter defaultCenter] postNotificationName:@"notification_subscription_restored" object:self userInfo:userInfo]; } else if ([self productFor:productId]) { [[NSNotificationCenter defaultCenter] postNotificationName:@"notification_issue_restored" object:self userInfo:userInfo]; @@ -328,13 +346,13 @@ - (void)restoreTransaction:(SKPaymentTransaction *)transaction { } } --(void)failedTransaction:(SKPaymentTransaction *)transaction { +- (void)failedTransaction:(SKPaymentTransaction*)transaction { NSLog(@"Payment transaction failure: %@", transaction.error); - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:transaction forKey:@"transaction"]; + NSDictionary *userInfo = @{@"transaction": transaction}; NSString *productId = transaction.payment.productIdentifier; - if ([productId isEqualToString:FREE_SUBSCRIPTION_PRODUCT_ID] || [AUTO_RENEWABLE_SUBSCRIPTION_PRODUCT_IDS containsObject:productId]) { + if ([productId isEqualToString:[BKRSettings sharedSettings].freeSubscriptionProductId] || [[BKRSettings sharedSettings].autoRenewableSubscriptionProductIds containsObject:productId]) { [[NSNotificationCenter defaultCenter] postNotificationName:@"notification_subscription_failed" object:self userInfo:userInfo]; } else { [[NSNotificationCenter defaultCenter] postNotificationName:@"notification_issue_purchase_failed" object:self userInfo:userInfo]; @@ -347,39 +365,31 @@ - (void)restore { [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; } -- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue { +- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue*)queue { [[NSNotificationCenter defaultCenter] postNotificationName:@"notification_restore_finished" object:self userInfo:nil]; } -- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error { +- (void)paymentQueue:(SKPaymentQueue*)queue restoreCompletedTransactionsFailedWithError:(NSError*)error { NSLog(@"Transaction restore failure: %@", error); - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:error forKey:@"error"]; + NSDictionary *userInfo = @{@"error": error}; [[NSNotificationCenter defaultCenter] postNotificationName:@"notification_restore_failed" object:self userInfo:userInfo]; } #pragma mark - Products -- (SKProduct *)productFor:(NSString *)productID { - return [self.products objectForKey:productID]; +- (SKProduct*)productFor:(NSString*)productID { + return (self.products)[productID]; } #pragma mark - Subscriptions - (BOOL)hasSubscriptions { - return [FREE_SUBSCRIPTION_PRODUCT_ID length] > 0 || [AUTO_RENEWABLE_SUBSCRIPTION_PRODUCT_IDS count] > 0; + return [[BKRSettings sharedSettings].freeSubscriptionProductId length] > 0 || [[BKRSettings sharedSettings].autoRenewableSubscriptionProductIds count] > 0; } #pragma mark - Memory management --(void)dealloc { - [products release]; - [_numberFormatter release]; - [_purchases release]; - - [super dealloc]; -} @end -#endif diff --git a/BakerShelf/BKRShelfHeaderView.h b/BakerShelf/BKRShelfHeaderView.h new file mode 100644 index 00000000..181889f5 --- /dev/null +++ b/BakerShelf/BKRShelfHeaderView.h @@ -0,0 +1,38 @@ +// +// BakerAPI.h +// Baker +// +// ========================================================================================== +// +// Copyright (c) 2014, Pieter Claerhout +// 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 Baker Framework nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written +// permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#import + +@interface BKRShelfHeaderView : UICollectionReusableView + +@property (nonatomic, strong) UIImageView *headerImage; + +@end diff --git a/BakerShelf/BKRShelfHeaderView.m b/BakerShelf/BKRShelfHeaderView.m new file mode 100644 index 00000000..f2c75051 --- /dev/null +++ b/BakerShelf/BKRShelfHeaderView.m @@ -0,0 +1,59 @@ +// +// BakerAPI.h +// Baker +// +// ========================================================================================== +// +// Copyright (c) 2014, Pieter Claerhout +// 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 Baker Framework nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written +// permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#import "BKRShelfHeaderView.h" +#import "BKRSettings.h" +#import "BKRUtils.h" + +@implementation BKRShelfHeaderView + +- (id)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + + _headerImage = [[UIImageView alloc] initWithFrame:self.frame]; + _headerImage.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth; + [_headerImage setClipsToBounds:YES]; + _headerImage.image = [UIImage imageNamed:@"shelf-header"]; + if([[BKRSettings sharedSettings].issuesShelfOptions[@"headerImageFill"] boolValue] == TRUE) { + _headerImage.contentMode = UIViewContentModeScaleAspectFill; + }else{ + _headerImage.contentMode = UIViewContentModeScaleAspectFit; + } + _headerImage.backgroundColor = [BKRUtils colorWithHexString:[BKRSettings sharedSettings].issuesShelfOptions[@"headerBackgroundColor"]]; + + [self addSubview:_headerImage]; + + } + return self; +} + +@end diff --git a/BakerShelf/ShelfStatus.h b/BakerShelf/BKRShelfStatus.h similarity index 85% rename from BakerShelf/ShelfStatus.h rename to BakerShelf/BKRShelfStatus.h index ee65e6fb..702c9a2d 100644 --- a/BakerShelf/ShelfStatus.h +++ b/BakerShelf/BKRShelfStatus.h @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -30,15 +31,15 @@ // #import -#import "JSONStatus.h" +#import "BKRJSONStatus.h" -@interface ShelfStatus : JSONStatus +@interface BKRShelfStatus : BKRJSONStatus -@property (strong, nonatomic) NSMutableDictionary *prices; +@property (nonatomic, strong) NSMutableDictionary *prices; - (id)init; - (void)save; -- (NSString *)priceFor:(NSString *)productID; -- (void)setPrice:(NSString *)price for:(NSString *)productID; +- (NSString*)priceFor:(NSString*)productID; +- (void)setPrice:(NSString*)price for:(NSString*)productID; @end diff --git a/BakerShelf/ShelfStatus.m b/BakerShelf/BKRShelfStatus.m similarity index 71% rename from BakerShelf/ShelfStatus.m rename to BakerShelf/BKRShelfStatus.m index 63a84263..5b5b0de9 100644 --- a/BakerShelf/ShelfStatus.m +++ b/BakerShelf/BKRShelfStatus.m @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -29,27 +30,25 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -#import "ShelfStatus.h" +#import "BKRShelfStatus.h" +#import "NSObject+BakerExtensions.h" -@implementation ShelfStatus - -@synthesize prices; +@implementation BKRShelfStatus - (id)init { - NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - NSString *statusPath = [[cachePath stringByAppendingPathComponent:@"shelf-status"] stringByAppendingPathExtension:@"json"]; + NSString *statusPath = [[self.bkrCachePath stringByAppendingPathComponent:@"shelf-status"] stringByAppendingPathExtension:@"json"]; self = [super initWithJSONPath:statusPath]; if (self) { - self.prices = [[[NSMutableDictionary alloc] init] autorelease]; + _prices = [[NSMutableDictionary alloc] init]; } return self; } -- (NSDictionary *)load { +- (NSDictionary*)load { NSDictionary *jsonDict = [super load]; - NSDictionary *jsonPrices = [jsonDict objectForKey:@"prices"]; + NSDictionary *jsonPrices = jsonDict[@"prices"]; [jsonPrices enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { [self setPrice:obj for:key]; }]; @@ -58,23 +57,16 @@ - (NSDictionary *)load { } - (void)save { - NSDictionary *jsonDict = [NSDictionary dictionaryWithObjectsAndKeys:prices, @"prices", nil]; - + NSDictionary *jsonDict = @{@"prices": self.prices}; [super save:jsonDict]; } -- (NSString *)priceFor:(NSString *)productID { - return [prices objectForKey:productID]; +- (NSString*)priceFor:(NSString*)productID { + return self.prices[productID]; } -- (void)setPrice:(NSString *)price for:(NSString *)productID { - [prices setObject:price forKey:productID]; -} - -- (void)dealloc { - [prices release]; - - [super dealloc]; +- (void)setPrice:(NSString*)price for:(NSString*)productID { + self.prices[productID] = price; } @end diff --git a/BakerShelf/ShelfViewController.h b/BakerShelf/BKRShelfViewController.h similarity index 50% rename from BakerShelf/ShelfViewController.h rename to BakerShelf/BKRShelfViewController.h index 31578e2a..a94a12bc 100644 --- a/BakerShelf/ShelfViewController.h +++ b/BakerShelf/BKRShelfViewController.h @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -32,66 +33,67 @@ #import #import -#import "BakerIssue.h" -#import "IssuesManager.h" -#import "ShelfStatus.h" -#import "BakerAPI.h" -#ifdef BAKER_NEWSSTAND -#import "PurchasesManager.h" -#endif - -@interface ShelfViewController : UIViewController { - BakerAPI *api; - IssuesManager *issuesManager; - NSMutableArray *notRecognisedTransactions; - __weak UIPopoverController *infoPopover; +#import "BKRIssue.h" +#import "BKRIssuesManager.h" +#import "BKRShelfStatus.h" +#import "BKRBakerAPI.h" +#import "BKRPurchasesManager.h" +#import "BKRCategoryFilterItem.h" + +@class BKRShelfHeaderView; - #ifdef BAKER_NEWSSTAND - PurchasesManager *purchasesManager; - #endif +@interface BKRShelfViewController : UIViewController { + BKRBakerAPI *api; + BKRIssuesManager *issuesManager; + NSMutableArray *notRecognisedTransactions; + UIPopoverController *infoPopover; + BKRPurchasesManager *purchasesManager; + UIInterfaceOrientation realInterfaceOrientation; } -@property (copy, nonatomic) NSArray *issues; -@property (copy, nonatomic) NSArray *supportedOrientation; +@property (nonatomic, copy) NSArray *issues; +@property (nonatomic, copy) NSArray *supportedOrientation; -@property (retain, nonatomic) NSMutableArray *issueViewControllers; -@property (retain, nonatomic) ShelfStatus *shelfStatus; +@property (nonatomic, strong) NSMutableArray *issueViewControllers; +@property (nonatomic, strong) BKRShelfStatus *shelfStatus; -@property (strong, nonatomic) UICollectionView *gridView; -@property (strong, nonatomic) UIImageView *background; -@property (strong, nonatomic) UIBarButtonItem *refreshButton; -@property (strong, nonatomic) UIBarButtonItem *subscribeButton; +@property (nonatomic, strong) UICollectionView *gridView; +@property (nonatomic, strong) CAGradientLayer *gradientLayer; +@property (nonatomic, strong) BKRShelfHeaderView *headerView; +@property (nonatomic, strong) UIBarButtonItem *refreshButton; +@property (nonatomic, strong) UIBarButtonItem *subscribeButton; +@property (strong, nonatomic) UIBarButtonItem *infoItem; +@property (strong, nonatomic) BKRCategoryFilterItem *categoryItem; -@property (strong, nonatomic) UIActionSheet *subscriptionsActionSheet; -@property (strong, nonatomic) NSArray *subscriptionsActionSheetActions; -@property (strong, nonatomic) UIAlertView *blockingProgressView; +@property (nonatomic, strong) UIActionSheet *subscriptionsActionSheet; +@property (nonatomic, strong) NSArray *subscriptionsActionSheetActions; +@property (nonatomic, strong) UIAlertView *blockingProgressView; -@property (copy, nonatomic) NSString *bookToBeProcessed; +@property (nonatomic, copy) NSString *bookToBeProcessed; #pragma mark - Init - (id)init; -- (id)initWithBooks:(NSArray *)currentBooks; +- (id)initWithBooks:(NSArray*)currentBooks; #pragma mark - Shelf data source -#ifdef BAKER_NEWSSTAND -- (void)handleRefresh:(NSNotification *)notification; +- (void)handleRefresh:(NSNotification*)notification; #pragma mark - Store Kit -- (void)handleSubscription:(NSNotification *)notification; -#endif +- (void)handleSubscription:(NSNotification*)notification; #pragma mark - Navigation management -- (void)readIssue:(BakerIssue *)issue; -- (void)handleReadIssue:(NSNotification *)notification; -- (void)receiveBookProtocolNotification:(NSNotification *)notification; +- (void)readIssue:(BKRIssue*)issue; +- (void)handleReadIssue:(NSNotification*)notification; +- (void)receiveBookProtocolNotification:(NSNotification*)notification; - (void)handleBookToBeProcessed; -- (void)pushViewControllerWithBook:(BakerBook *)book; +- (void)pushViewControllerWithBook:(BKRBook*)book; #pragma mark - Buttons management --(void)setrefreshButtonEnabled:(BOOL)enabled; --(void)setSubscribeButtonEnabled:(BOOL)enabled; +- (void)setrefreshButtonEnabled:(BOOL)enabled; +- (void)setSubscribeButtonEnabled:(BOOL)enabled; +- (void)handleSubscribeButtonPressed:(NSNotification *)notification; #pragma mark - Helper methods -+ (int)getBannerHeight; +- (int)getBannerHeight; @end diff --git a/BakerShelf/BKRShelfViewController.m b/BakerShelf/BKRShelfViewController.m new file mode 100644 index 00000000..5a6fb28d --- /dev/null +++ b/BakerShelf/BKRShelfViewController.m @@ -0,0 +1,848 @@ +// +// ShelfViewController.m +// Baker +// +// ========================================================================================== +// +// Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout +// 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 Baker Framework nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written +// permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#import "BKRShelfViewController.h" +#import "BKRCustomNavigationBar.h" + +#import "BKRBookViewController.h" +#import "BKRIssueViewController.h" +#import "BKRShelfHeaderView.h" +#import "BKRShelfViewLayout.h" +#import "BKRSettings.h" + +#import "NSData+BakerExtensions.h" +#import "NSString+BakerExtensions.h" +#import "BKRUtils.h" + +#import "MBProgressHUD.h" + +@interface BKRShelfViewController () + +@property (nonatomic, strong) UICollectionViewFlowLayout *layout; + +@end + +@implementation BKRShelfViewController + +#pragma mark - Init + +- (id)init { + self = [super init]; + if (self) { + if ([BKRSettings sharedSettings].isNewsstand) { + purchasesManager = [BKRPurchasesManager sharedInstance]; + [self addPurchaseObserver:@selector(handleProductsRetrieved:) + name:@"notification_products_retrieved"]; + [self addPurchaseObserver:@selector(handleProductsRequestFailed:) + name:@"notification_products_request_failed"]; + [self addPurchaseObserver:@selector(handleSubscriptionPurchased:) + name:@"notification_subscription_purchased"]; + [self addPurchaseObserver:@selector(handleSubscriptionFailed:) + name:@"notification_subscription_failed"]; + [self addPurchaseObserver:@selector(handleSubscriptionRestored:) + name:@"notification_subscription_restored"]; + [self addPurchaseObserver:@selector(handleRestoreFailed:) + name:@"notification_restore_failed"]; + [self addPurchaseObserver:@selector(handleMultipleRestores:) + name:@"notification_multiple_restores"]; + [self addPurchaseObserver:@selector(handleRestoredIssueNotRecognised:) + name:@"notification_restored_issue_not_recognised"]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(receiveBookProtocolNotification:) + name:@"notification_book_protocol" + object:nil]; + + [[SKPaymentQueue defaultQueue] addTransactionObserver:purchasesManager]; + } + + api = [BKRBakerAPI sharedInstance]; + issuesManager = [BKRIssuesManager sharedInstance]; + notRecognisedTransactions = [[NSMutableArray alloc] init]; + + _shelfStatus = [[BKRShelfStatus alloc] init]; + _issueViewControllers = [[NSMutableArray alloc] init]; + _supportedOrientation = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UISupportedInterfaceOrientations"]; + _bookToBeProcessed = nil; + + if ([BKRSettings sharedSettings].isNewsstand) { + [self handleRefresh:nil]; + } + } + return self; +} + +- (id)initWithBooks:(NSArray*)currentBooks { + self = [self init]; + if (self) { + self.issues = currentBooks; + + NSMutableArray *controllers = [NSMutableArray array]; + for (BKRIssue *issue in self.issues) { + BKRIssueViewController *controller = [self createIssueViewControllerWithIssue:issue]; + [controllers addObject:controller]; + } + self.issueViewControllers = [NSMutableArray arrayWithArray:controllers]; + } + return self; +} + +#pragma mark - View lifecycle + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.navigationItem.title = NSLocalizedString(@"SHELF_NAVIGATION_TITLE", nil); + + self.layout = [[BKRShelfViewLayout alloc] initWithSticky:[[BKRSettings sharedSettings].issuesShelfOptions[@"headerSticky"] boolValue] + stretch:[[BKRSettings sharedSettings].issuesShelfOptions[@"headerStretch"] boolValue]]; + + [self.layout setHeaderReferenceSize:[self getBannerSize]]; + self.layout.minimumInteritemSpacing = 0; + self.layout.minimumLineSpacing = 0; + + self.gridView = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:self.layout]; + self.gridView.dataSource = self; + self.gridView.delegate = self; + self.gridView.backgroundColor = [UIColor clearColor]; + self.gridView.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth; + + [self.gridView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cellIdentifier"]; + [self.gridView registerClass:[BKRShelfHeaderView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"headerIdentifier"]; + + NSString *backgroundFillStyle = [BKRSettings sharedSettings].issuesShelfOptions[@"backgroundFillStyle"]; + if([backgroundFillStyle isEqualToString:@"Gradient"]) { + self.gradientLayer = [BKRUtils gradientLayerFromHexString:[BKRSettings sharedSettings].issuesShelfOptions[@"backgroundFillGradientStart"] + toHexString:[BKRSettings sharedSettings].issuesShelfOptions[@"backgroundFillGradientStop"]]; + self.gradientLayer.frame = self.gridView.bounds; + self.gridView.backgroundColor = [UIColor clearColor]; + self.gridView.backgroundView = [[UIView alloc] init]; + [self.gridView.backgroundView.layer insertSublayer:self.gradientLayer atIndex:0]; + self.gridView.backgroundColor = [BKRUtils colorWithHexString:[BKRSettings sharedSettings].issuesShelfOptions[@"backgroundFillColor"]]; + }else if([backgroundFillStyle isEqualToString:@"Image"]) { + UIImage *backgroundImage = [UIImage imageNamed:@"shelf-background"]; + UIImageView *backgroundView = [[UIImageView alloc] initWithImage:backgroundImage]; + self.gridView.backgroundView = backgroundView; + }else if([backgroundFillStyle isEqualToString:@"Pattern"]) { + self.gridView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"shelf-background"]]; + }else if([backgroundFillStyle isEqualToString:@"Color"]) { + self.gridView.backgroundColor = [BKRUtils colorWithHexString:[BKRSettings sharedSettings].issuesShelfOptions[@"backgroundFillColor"]]; + } + + [self.view addSubview:self.gridView]; + + [self willRotateToInterfaceOrientation:self.interfaceOrientation duration:0]; + [self.gridView reloadData]; + + if ([BKRSettings sharedSettings].isNewsstand) { + self.refreshButton = [[UIBarButtonItem alloc] + initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh + target:self + action:@selector(handleRefresh:)]; + + self.subscribeButton = [[UIBarButtonItem alloc] + initWithTitle: NSLocalizedString(@"SUBSCRIBE_BUTTON_TEXT", nil) + style:UIBarButtonItemStylePlain + target:self + action:@selector(handleSubscribeButtonPressed:)]; + + self.categoryItem = [[BKRCategoryFilterItem alloc] initWithCategories:issuesManager.categories delegate:self]; + + self.blockingProgressView = [[UIAlertView alloc] + initWithTitle:@"Processing..." + message:@"\n" + delegate:nil + cancelButtonTitle:nil + otherButtonTitles:nil]; + UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; + spinner.center = CGPointMake(139.5, 75.5); // .5 so it doesn't blur + [self.blockingProgressView addSubview:spinner]; + [spinner startAnimating]; + + NSMutableSet *subscriptions = [NSMutableSet setWithArray:[BKRSettings sharedSettings].autoRenewableSubscriptionProductIds]; + if ([[BKRSettings sharedSettings].freeSubscriptionProductId length] > 0 && ![purchasesManager isPurchased:[BKRSettings sharedSettings].freeSubscriptionProductId]) { + [subscriptions addObject:[BKRSettings sharedSettings].freeSubscriptionProductId]; + } + [purchasesManager retrievePricesFor:subscriptions andEnableFailureNotifications:NO]; + + } +} + +- (void)viewWillAppear:(BOOL)animated { + + [super viewWillAppear:animated]; + [self.navigationController.navigationBar setTranslucent:NO]; + [self willRotateToInterfaceOrientation:self.interfaceOrientation duration:0]; + + for (BKRIssueViewController *controller in self.issueViewControllers) { + controller.issue.transientStatus = BakerIssueTransientStatusNone; + [controller refresh]; + } + + if ([BKRSettings sharedSettings].isNewsstand) { + + NSMutableArray *buttonItems = [NSMutableArray arrayWithObject:self.refreshButton]; + if ([purchasesManager hasSubscriptions] || [issuesManager hasProductIDs]) { + [buttonItems addObject:self.subscribeButton]; + } + self.navigationItem.leftBarButtonItems = buttonItems; + + // Remove limbo transactions + // take current payment queue + SKPaymentQueue* currentQueue = [SKPaymentQueue defaultQueue]; + // finish ALL transactions in queue + [currentQueue.transactions enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + [currentQueue finishTransaction:(SKPaymentTransaction*)obj]; + }]; + + [[SKPaymentQueue defaultQueue] addTransactionObserver:purchasesManager]; + } + + // Add info button + UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeInfoLight]; + [infoButton addTarget:self action:@selector(handleInfoButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; + self.infoItem = [[UIBarButtonItem alloc] initWithCustomView:infoButton]; + + // Remove file info.html if you don't want the info button to be added to the shelf navigation bar + NSString *infoPath = [[NSBundle mainBundle] pathForResource:@"info" ofType:@"html" inDirectory:@"info"]; + if ([[NSFileManager defaultManager] fileExistsAtPath:infoPath]) { + self.navigationItem.rightBarButtonItem = self.infoItem; + } +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + if (self.bookToBeProcessed) { + [self handleBookToBeProcessed]; + } +} + +- (NSUInteger)supportedInterfaceOrientations { + return UIInterfaceOrientationMaskAll; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + return [self.supportedOrientation indexOfObject:[NSString bkrStringFromInterfaceOrientation:interfaceOrientation]] != NSNotFound; +} + +- (BOOL)shouldAutorotate { + return YES; +} + +- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { + realInterfaceOrientation = toInterfaceOrientation; + [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; +} + +- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { + [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration]; + + // Update label widths + [self.issueViewControllers enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + [(BKRIssueViewController*)obj refreshContentWithCache:NO]; + }]; +} + +- (void)viewDidLayoutSubviews { + // Update gradient background (if set) + if(self.gradientLayer) { + [self.gradientLayer setFrame:self.gridView.bounds]; + } + + // Update header size + [self.layout setHeaderReferenceSize:[self getBannerSize]]; + + // Invalidate layout + [self.layout invalidateLayout]; + +} + +- (BKRIssueViewController*)createIssueViewControllerWithIssue:(BKRIssue*)issue { + BKRIssueViewController *controller = [[BKRIssueViewController alloc] initWithBakerIssue:issue]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleReadIssue:) name:@"read_issue_request" object:controller]; + return controller; +} + +- (BOOL)prefersStatusBarHidden { + return NO; +} + +#pragma mark - Shelf data source + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView*)collectionView { + return 1; +} + +- (NSInteger)collectionView:(UICollectionView*)collectionView numberOfItemsInSection:(NSInteger)section { + return [self.issueViewControllers count]; +} + +- (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView cellForItemAtIndexPath:(NSIndexPath*)indexPath { + CGSize cellSize = [BKRIssueViewController getIssueCellSizeForOrientation:self.interfaceOrientation]; + CGRect cellFrame = CGRectMake(0, 0, cellSize.width, cellSize.height); + + static NSString *cellIdentifier = @"cellIdentifier"; + UICollectionViewCell* cell = [self.gridView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath]; + if (cell == nil) { + UICollectionViewCell* cell = [[UICollectionViewCell alloc] initWithFrame:cellFrame]; + cell.contentView.backgroundColor = [UIColor clearColor]; + cell.backgroundColor = [UIColor clearColor]; + } + + BKRIssueViewController *controller = (self.issueViewControllers)[indexPath.row]; + UIView *removableIssueView = [cell.contentView viewWithTag:42]; + if (removableIssueView) { + [removableIssueView removeFromSuperview]; + } + [cell.contentView addSubview:controller.view]; + + return cell; +} + + +- (CGSize)collectionView:(UICollectionView*)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath*)indexPath { + return [BKRIssueViewController getIssueCellSizeForOrientation:realInterfaceOrientation]; +} + +- (UICollectionReusableView*)collectionView:(UICollectionView*)collectionView viewForSupplementaryElementOfKind:(NSString*)kind atIndexPath:(NSIndexPath*)indexPath { + if (!self.headerView) { + self.headerView = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"headerIdentifier" forIndexPath:indexPath]; + } + return self.headerView; +} + +- (CGSize)collectionView:(UICollectionView*)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { + return [self getBannerSize]; +} + +- (void)handleRefresh:(NSNotification*)notification { + [self setrefreshButtonEnabled:NO]; + + MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; + hud.labelText = NSLocalizedString(@"Loading", @""); + + [issuesManager refresh:^(BOOL status) { + if(status) { + + // Set dropdown categories + self.categoryItem.categories = issuesManager.categories; + + // Show / Hide category button + if(issuesManager.categories.count == 0) { + self.navigationItem.rightBarButtonItems = [NSArray arrayWithObjects:self.infoItem, nil]; + }else{ + self.navigationItem.rightBarButtonItems = [NSArray arrayWithObjects:self.infoItem, self.categoryItem, nil]; + } + + // Set issues + self.issues = issuesManager.issues; + + // Refresh issue list + [self refreshIssueList]; + + } else { + [BKRUtils showAlertWithTitle:NSLocalizedString(@"INTERNET_CONNECTION_UNAVAILABLE_TITLE", nil) + message:NSLocalizedString(@"INTERNET_CONNECTION_UNAVAILABLE_MESSAGE", nil) + buttonTitle:NSLocalizedString(@"INTERNET_CONNECTION_UNAVAILABLE_CLOSE", nil)]; + + [self setrefreshButtonEnabled:YES]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [MBProgressHUD hideAllHUDsForView:self.view animated:YES]; + }); + + } + }]; +} + +- (BKRIssueViewController*)issueViewControllerWithID:(NSString*)ID { + __block BKRIssueViewController* foundController = nil; + [self.issueViewControllers enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + BKRIssueViewController *ivc = (BKRIssueViewController*)obj; + if ([ivc.issue.ID isEqualToString:ID]) { + foundController = ivc; + *stop = YES; + } + }]; + return foundController; +} + +- (BKRIssue*)bakerIssueWithID:(NSString*)ID { + __block BKRIssue *foundIssue = nil; + [self.issues enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + BKRIssue *issue = (BKRIssue*)obj; + if ([issue.ID isEqualToString:ID]) { + foundIssue = issue; + *stop = YES; + } + }]; + return foundIssue; +} + +- (void)refreshIssueList { + + // Filter issues + __block NSMutableArray *filteredIssues = [NSMutableArray array]; + [issuesManager.issues enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + BKRIssue *issue = (BKRIssue *)obj; + + // Test if category exists + if([self.categoryItem.title isEqualToString:NSLocalizedString(@"ALL_CATEGORIES_TITLE", nil)] || [issue.categories containsObject:self.categoryItem.title]) { + [filteredIssues addObject:issue]; + } + }]; + + // Assign filtered issues + self.issues = [filteredIssues copy]; + + [self.shelfStatus load]; + for (BKRIssue *issue in self.issues) { + issue.price = [self.shelfStatus priceFor:issue.productID]; + } + + void (^updateIssues)() = ^{ + // Step 1: remove controllers for issues that no longer exist + __weak NSMutableArray *discardedControllers = [NSMutableArray array]; + [self.issueViewControllers enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + BKRIssueViewController *ivc = (BKRIssueViewController*)obj; + + if (![self bakerIssueWithID:ivc.issue.ID]) { + [discardedControllers addObject:ivc]; + [self.gridView deleteItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:idx inSection:0]]]; + } + }]; + [self.issueViewControllers removeObjectsInArray:discardedControllers]; + + // Step 2: add controllers for issues that did not yet exist (and refresh the ones that do exist) + [self.issues enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + // NOTE: this block changes the issueViewController array while looping + BKRIssue *issue = (BKRIssue*)obj; + + BKRIssueViewController *existingIvc = [self issueViewControllerWithID:issue.ID]; + + if (existingIvc) { + existingIvc.issue = issue; + } else { + BKRIssueViewController *newIvc = [self createIssueViewControllerWithIssue:issue]; + [self.issueViewControllers insertObject:newIvc atIndex:idx]; + [self.gridView insertItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:idx inSection:0]] ]; + } + }]; + + [self.gridView reloadData]; + }; + + // When first launched, the grid is not initialised, so we can't + // call in the "batch update" method of the grid view + if (self.gridView) { + [self.gridView performBatchUpdates:updateIssues completion:nil]; + } + else { + updateIssues(); + } + + [purchasesManager retrievePurchasesFor:[issuesManager productIDs] withCallback:^(NSDictionary *purchases) { + // List of purchases has been returned, so we can refresh all issues + [self.issueViewControllers enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + [(BKRIssueViewController*)obj refreshWithCache:NO]; + }]; + [self setrefreshButtonEnabled:YES]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [MBProgressHUD hideAllHUDsForView:self.view animated:YES]; + }); + }]; + + [purchasesManager retrievePricesFor:issuesManager.productIDs andEnableFailureNotifications:NO]; +} + +#pragma mark - Store Kit +- (void)handleSubscribeButtonPressed:(NSNotification*)notification { + if (self.subscriptionsActionSheet.visible) { + [self.subscriptionsActionSheet dismissWithClickedButtonIndex:(self.subscriptionsActionSheet.numberOfButtons - 1) animated:YES]; + } else { + self.subscriptionsActionSheet = [self buildSubscriptionsActionSheet]; + [self.subscriptionsActionSheet showFromBarButtonItem:self.subscribeButton animated:YES]; + } +} + +- (UIActionSheet*)buildSubscriptionsActionSheet { + NSString *title; + if ([api canGetPurchasesJSON]) { + if (purchasesManager.subscribed) { + title = NSLocalizedString(@"SUBSCRIPTIONS_SHEET_SUBSCRIBED", nil); + } else { + title = NSLocalizedString(@"SUBSCRIPTIONS_SHEET_NOT_SUBSCRIBED", nil); + } + } else { + title = NSLocalizedString(@"SUBSCRIPTIONS_SHEET_GENERIC", nil); + } + + UIActionSheet *sheet = [[UIActionSheet alloc]initWithTitle:title + delegate:self + cancelButtonTitle:nil + destructiveButtonTitle:nil + otherButtonTitles: nil]; + NSMutableArray *actions = [NSMutableArray array]; + + if (!purchasesManager.subscribed) { + if ([[BKRSettings sharedSettings].freeSubscriptionProductId length] > 0 && ![purchasesManager isPurchased:[BKRSettings sharedSettings].freeSubscriptionProductId]) { + [sheet addButtonWithTitle:NSLocalizedString(@"SUBSCRIPTIONS_SHEET_FREE", nil)]; + [actions addObject:[BKRSettings sharedSettings].freeSubscriptionProductId]; + } + + for (NSString *productId in [BKRSettings sharedSettings].autoRenewableSubscriptionProductIds) { + NSString *title = [purchasesManager displayTitleFor:productId]; + NSString *price = [purchasesManager priceFor:productId]; + if (price) { + [sheet addButtonWithTitle:[NSString stringWithFormat:@"%@ %@", title, price]]; + [actions addObject:productId]; + } + } + } + + if ([issuesManager hasProductIDs]) { + [sheet addButtonWithTitle:NSLocalizedString(@"SUBSCRIPTIONS_SHEET_RESTORE", nil)]; + [actions addObject:@"restore"]; + } + + [sheet addButtonWithTitle:NSLocalizedString(@"SUBSCRIPTIONS_SHEET_CLOSE", nil)]; + [actions addObject:@"cancel"]; + + self.subscriptionsActionSheetActions = actions; + + sheet.cancelButtonIndex = sheet.numberOfButtons - 1; + return sheet; +} + +- (void) actionSheet:(UIActionSheet*)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex { + if (actionSheet == self.subscriptionsActionSheet) { + NSString *action = (self.subscriptionsActionSheetActions)[buttonIndex]; + if ([action isEqualToString:@"cancel"]) { + NSLog(@"Action sheet: cancel"); + [self setSubscribeButtonEnabled:YES]; + } else if ([action isEqualToString:@"restore"]) { + [self.blockingProgressView show]; + [purchasesManager restore]; + NSLog(@"Action sheet: restore"); + } else { + NSLog(@"Action sheet: %@", action); + [[NSNotificationCenter defaultCenter] postNotificationName:@"BakerSubscriptionPurchase" object:self]; // -> Baker Analytics Event + [self setSubscribeButtonEnabled:NO]; + if (![purchasesManager purchase:action]){ + [BKRUtils showAlertWithTitle:NSLocalizedString(@"SUBSCRIPTION_FAILED_TITLE", nil) + message:nil + buttonTitle:NSLocalizedString(@"SUBSCRIPTION_FAILED_CLOSE", nil)]; + [self setSubscribeButtonEnabled:YES]; + } + } + } +} + +- (void)handleRestoreFailed:(NSNotification*)notification { + NSError *error = (notification.userInfo)[@"error"]; + [BKRUtils showAlertWithTitle:NSLocalizedString(@"RESTORE_FAILED_TITLE", nil) + message:[error localizedDescription] + buttonTitle:NSLocalizedString(@"RESTORE_FAILED_CLOSE", nil)]; + + [self.blockingProgressView dismissWithClickedButtonIndex:0 animated:YES]; + +} + +- (void)handleMultipleRestores:(NSNotification*)notification { + if ([BKRSettings sharedSettings].isNewsstand) { + + if ([notRecognisedTransactions count] > 0) { + NSSet *productIDs = [NSSet setWithArray:[[notRecognisedTransactions valueForKey:@"payment"] valueForKey:@"productIdentifier"]]; + NSString *productsList = [[productIDs allObjects] componentsJoinedByString:@", "]; + + [BKRUtils showAlertWithTitle:NSLocalizedString(@"RESTORED_ISSUE_NOT_RECOGNISED_TITLE", nil) + message:[NSString stringWithFormat:NSLocalizedString(@"RESTORED_ISSUE_NOT_RECOGNISED_MESSAGE", nil), productsList] + buttonTitle:NSLocalizedString(@"RESTORED_ISSUE_NOT_RECOGNISED_CLOSE", nil)]; + + for (SKPaymentTransaction *transaction in notRecognisedTransactions) { + [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; + } + [notRecognisedTransactions removeAllObjects]; + } + } + + [self handleRefresh:nil]; + [self.blockingProgressView dismissWithClickedButtonIndex:0 animated:YES]; +} + +- (void)handleRestoredIssueNotRecognised:(NSNotification*)notification { + SKPaymentTransaction *transaction = (notification.userInfo)[@"transaction"]; + [notRecognisedTransactions addObject:transaction]; +} + +// TODO: this can probably be removed +- (void)handleSubscription:(NSNotification*)notification { + [self setSubscribeButtonEnabled:NO]; + [purchasesManager purchase:[BKRSettings sharedSettings].freeSubscriptionProductId]; +} + +- (void)handleSubscriptionPurchased:(NSNotification*)notification { + SKPaymentTransaction *transaction = (notification.userInfo)[@"transaction"]; + + [purchasesManager markAsPurchased:transaction.payment.productIdentifier]; + [self setSubscribeButtonEnabled:YES]; + + if ([purchasesManager finishTransaction:transaction]) { + if (!purchasesManager.subscribed) { + [BKRUtils showAlertWithTitle:NSLocalizedString(@"SUBSCRIPTION_SUCCESSFUL_TITLE", nil) + message:NSLocalizedString(@"SUBSCRIPTION_SUCCESSFUL_MESSAGE", nil) + buttonTitle:NSLocalizedString(@"SUBSCRIPTION_SUCCESSFUL_CLOSE", nil)]; + + [self handleRefresh:nil]; + } + } else { + [BKRUtils showAlertWithTitle:NSLocalizedString(@"TRANSACTION_RECORDING_FAILED_TITLE", nil) + message:NSLocalizedString(@"TRANSACTION_RECORDING_FAILED_MESSAGE", nil) + buttonTitle:NSLocalizedString(@"TRANSACTION_RECORDING_FAILED_CLOSE", nil)]; + } +} + +- (void)handleSubscriptionFailed:(NSNotification*)notification { + SKPaymentTransaction *transaction = (notification.userInfo)[@"transaction"]; + + // Show an error, unless it was the user who cancelled the transaction + if (transaction.error.code != SKErrorPaymentCancelled) { + [BKRUtils showAlertWithTitle:NSLocalizedString(@"SUBSCRIPTION_FAILED_TITLE", nil) + message:[transaction.error localizedDescription] + buttonTitle:NSLocalizedString(@"SUBSCRIPTION_FAILED_CLOSE", nil)]; + } + + [self setSubscribeButtonEnabled:YES]; +} + +- (void)handleSubscriptionRestored:(NSNotification*)notification { + SKPaymentTransaction *transaction = (notification.userInfo)[@"transaction"]; + + [purchasesManager markAsPurchased:transaction.payment.productIdentifier]; + + if (![purchasesManager finishTransaction:transaction]) { + NSLog(@"Could not confirm purchase restore with remote server for %@", transaction.payment.productIdentifier); + } +} + +- (void)handleProductsRetrieved:(NSNotification*)notification { + NSSet *ids = (notification.userInfo)[@"ids"]; + BOOL issuesRetrieved = NO; + + for (NSString *productId in ids) { + if ([productId isEqualToString:[BKRSettings sharedSettings].freeSubscriptionProductId]) { + // ID is for a free subscription + [self setSubscribeButtonEnabled:YES]; + } else if ([[BKRSettings sharedSettings].autoRenewableSubscriptionProductIds containsObject:productId]) { + // ID is for an auto-renewable subscription + [self setSubscribeButtonEnabled:YES]; + } else { + // ID is for an issue + issuesRetrieved = YES; + } + } + + if (issuesRetrieved) { + NSString *price; + for (BKRIssueViewController *controller in self.issueViewControllers) { + price = [purchasesManager priceFor:controller.issue.productID]; + if (price) { + [controller setPrice:price]; + [self.shelfStatus setPrice:price for:controller.issue.productID]; + } + } + [self.shelfStatus save]; + } +} + +- (void)handleProductsRequestFailed:(NSNotification*)notification { + NSError *error = (notification.userInfo)[@"error"]; + + [BKRUtils showAlertWithTitle:NSLocalizedString(@"PRODUCTS_REQUEST_FAILED_TITLE", nil) + message:[error localizedDescription] + buttonTitle:NSLocalizedString(@"PRODUCTS_REQUEST_FAILED_CLOSE", nil)]; +} + +#pragma mark - Navigation management + +- (void)collectionView:(UICollectionView*)collectionView didSelectItemAtIndexPath:(NSIndexPath*)indexPath { + [collectionView deselectItemAtIndexPath:indexPath animated:YES]; +} + +- (void)readIssue:(BKRIssue*)issue +{ + BKRBook *book = nil; + NSString *status = [issue getStatus]; + + if ([BKRSettings sharedSettings].isNewsstand) { + if ([status isEqual:@"opening"]) { + book = [[BKRBook alloc] initWithBookPath:issue.path bundled:NO]; + if (book) { + [self pushViewControllerWithBook:book]; + } else { + NSLog(@"[ERROR] Book %@ could not be initialized", issue.ID); + issue.transientStatus = BakerIssueTransientStatusNone; + // Let's refresh everything as it's easier. This is an edge case anyway ;) + for (BKRIssueViewController *controller in self.issueViewControllers) { + [controller refresh]; + } + [BKRUtils showAlertWithTitle:NSLocalizedString(@"ISSUE_OPENING_FAILED_TITLE", nil) + message:NSLocalizedString(@"ISSUE_OPENING_FAILED_MESSAGE", nil) + buttonTitle:NSLocalizedString(@"ISSUE_OPENING_FAILED_CLOSE", nil)]; + } + } + } else { + if ([status isEqual:@"bundled"]) { + book = [issue bakerBook]; + [self pushViewControllerWithBook:book]; + } + } +} +- (void)handleReadIssue:(NSNotification*)notification +{ + BKRIssueViewController *controller = notification.object; + [self readIssue:controller.issue]; +} +- (void)receiveBookProtocolNotification:(NSNotification*)notification +{ + self.bookToBeProcessed = (notification.userInfo)[@"ID"]; + [self.navigationController popToRootViewControllerAnimated:YES]; +} +- (void)handleBookToBeProcessed +{ + for (BKRIssueViewController *issueViewController in self.issueViewControllers) { + if ([issueViewController.issue.ID isEqualToString:self.bookToBeProcessed]) { + [issueViewController actionButtonPressed:nil]; + break; + } + } + + self.bookToBeProcessed = nil; +} +- (void)pushViewControllerWithBook:(BKRBook*)book +{ + BKRBookViewController *bakerViewController = [[BKRBookViewController alloc] initWithBook:book]; + [self.navigationController pushViewController:bakerViewController animated:YES]; +} + +#pragma mark - Buttons management + +- (void)setrefreshButtonEnabled:(BOOL)enabled { + self.refreshButton.enabled = enabled; +} + +- (void)setSubscribeButtonEnabled:(BOOL)enabled { + self.subscribeButton.enabled = enabled; + if (enabled) { + self.subscribeButton.title = NSLocalizedString(@"SUBSCRIBE_BUTTON_TEXT", nil); + } else { + self.subscribeButton.title = NSLocalizedString(@"SUBSCRIBE_BUTTON_DISABLED_TEXT", nil); + } +} + +- (void)webViewDidFinishLoad:(UIWebView*)webView { + // Inject user_id + [BKRUtils webView:webView dispatchHTMLEvent:@"init" withParams:@{@"user_id": [BKRBakerAPI UUID], + @"app_id": [BKRUtils appID]}]; +} + +- (void)handleInfoButtonPressed:(id)sender { + + // If the button is pressed when the info box is open, close it + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + if (infoPopover.isPopoverVisible) { + [infoPopover dismissPopoverAnimated:YES]; + return; + } + } + + // Prepare new view + UIViewController *popoverContent = [[UIViewController alloc] init]; + UIWebView *popoverView = [[UIWebView alloc] init]; + + popoverView.backgroundColor = [UIColor whiteColor]; + popoverView.delegate = self; + popoverContent.view = popoverView; + + // Load HTML file + NSString *path = [[NSBundle mainBundle] pathForResource:@"info" ofType:@"html" inDirectory:@"info"]; + [popoverView loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:path]]]; + + // Open view + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + // On iPad use the UIPopoverController + infoPopover = [[UIPopoverController alloc] initWithContentViewController:popoverContent]; + [infoPopover presentPopoverFromBarButtonItem:self.infoItem + permittedArrowDirections:UIPopoverArrowDirectionUp + animated:YES]; + } else { + // On iPhone push the view controller to the navigation + [self.navigationController pushViewController:popoverContent animated:YES]; + } + +} + +#pragma mark - Helper methods + +- (void)addPurchaseObserver:(SEL)notificationSelector name:(NSString*)notificationName { + if ([BKRSettings sharedSettings].isNewsstand) { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:notificationSelector + name:notificationName + object:purchasesManager]; + } +} + +- (int)getBannerHeight { + return [[BKRSettings sharedSettings].issuesShelfOptions[[NSString stringWithFormat:@"headerHeight%@%@", [self getDeviceString], [self getOrientationString]]] intValue]; +} + +- (CGSize)getBannerSize { + return CGSizeMake(self.view.frame.size.width, [self getBannerHeight]); +} + +- (NSString *)getDeviceString { + return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) ? @"Pad" : @"Phone"; +} + +- (NSString *)getOrientationString { + return UIInterfaceOrientationIsLandscape(self.interfaceOrientation) ? @"Landscape" : @"Portrait"; +} + +#pragma mark - BKRCategoryFilterItemDelegate + +- (void)categoryFilterItem:(BKRCategoryFilterItem *)categoryFilterItem clickedAction:(NSString *)action { + [self refreshIssueList]; +} + +@end diff --git a/BakerShelf/Constants.h b/BakerShelf/Constants.h deleted file mode 100644 index 80a67309..00000000 --- a/BakerShelf/Constants.h +++ /dev/null @@ -1,92 +0,0 @@ -// -// Constants.h -// Baker -// -// ========================================================================================== -// -// Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi -// 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 Baker Framework nor the names of its contributors may be used to -// endorse or promote products derived from this software without specific prior written -// permission. -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT -// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// - - -#ifndef Baker_Constants_h -#define Baker_Constants_h - - // ---------------------------------------------------------------------------------------------------- - // NEWSSTAND SUPPORT - // The following line, together with other settings, enables Newsstand mode. - // Remove this, remove the NewsstandKit.framework and the Newsstand entries in Baker-Info.plist to disable it. - // See: https://github.com/Simbul/baker/wiki/Newsstand-vs-Bundled-publications-support-in-Baker-4.0 - #define BAKER_NEWSSTAND - - #ifdef BAKER_NEWSSTAND - - // ---------------------------------------------------------------------------------------------------- - // Mandatory - This constant defines where the JSON file containing all the publications is located. - // For more information on this file, see: https://github.com/Simbul/baker/wiki/Newsstand-shelf-JSON - // E.g. @"http://example.com/shelf.json" - #define NEWSSTAND_MANIFEST_URL @"http://bakerframework.com/demo/shelf.json" - - // ---------------------------------------------------------------------------------------------------- - // Optional - This constant specifies the URL to ping back when a user purchases an issue or a subscription. - // For more information, see: https://github.com/Simbul/baker/wiki/Baker-Server-API - // E.g. @"http://example.com/purchased" - #define PURCHASE_CONFIRMATION_URL @"" - - // ---------------------------------------------------------------------------------------------------- - // Optional - This constant specifies a URL that will be used to retrieve the list of purchased issues. - // For more information, see: https://github.com/Simbul/baker/wiki/Baker-Server-API - // E.g. @"http://example.com/purchases" - #define PURCHASES_URL @"" - - // ---------------------------------------------------------------------------------------------------- - // Optional - This constant specifies the URL to ping back when a user enables push notifications. - // For more information, see: https://github.com/Simbul/baker/wiki/Baker-Server-API - // E.g. @"http://example.com/post_apns_token" - #define POST_APNS_TOKEN_URL @"" - - // ---------------------------------------------------------------------------------------------------- - // Mandatory - The following two constants identify the subscriptions you set up in iTunesConnect. - // See: iTunes Connect -> Manage Your Application -> (Your application) -> Manage In App Purchases - // You *have* to set at least one among FREE_SUBSCRIPTION_PRODUCT_ID and AUTO_RENEWABLE_SUBSCRIPTION_PRODUCT_IDS. - - // This constant identifies a free subscription. - // E.g. @"com.example.MyBook.subscription.free" - #define FREE_SUBSCRIPTION_PRODUCT_ID @"" - - // This constant identifies one or more auto-renewable subscriptions. - // E.g.: - // #define AUTO_RENEWABLE_SUBSCRIPTION_PRODUCT_IDS [NSArray arrayWithObjects: \ - // @"com.example.MyBook.subscription.3months", \ - // @"com.example.MyBook.subscription.6months", \ - // nil] - #define AUTO_RENEWABLE_SUBSCRIPTION_PRODUCT_IDS [NSArray arrayWithObjects: \ - nil] - - #endif - - // Timeout for most network requests (in seconds) - #define REQUEST_TIMEOUT 15 - -#endif diff --git a/BakerShelf/IssueViewController.m b/BakerShelf/IssueViewController.m deleted file mode 100644 index 171fd520..00000000 --- a/BakerShelf/IssueViewController.m +++ /dev/null @@ -1,739 +0,0 @@ -// -// IssueViewController.m -// Baker -// -// ========================================================================================== -// -// Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi -// 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 Baker Framework nor the names of its contributors may be used to -// endorse or promote products derived from this software without specific prior written -// permission. -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT -// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// - -#import - -#import "IssueViewController.h" -#import "SSZipArchive.h" -#import "UIConstants.h" -#ifdef BAKER_NEWSSTAND -#import "PurchasesManager.h" -#endif - -#import "UIColor+Extensions.h" -#import "Utils.h" - -@implementation IssueViewController - -#pragma mark - Synthesis - -@synthesize issue; -@synthesize actionButton; -@synthesize archiveButton; -@synthesize progressBar; -@synthesize spinner; -@synthesize loadingLabel; -@synthesize priceLabel; - -@synthesize issueCover; -@synthesize titleLabel; -@synthesize infoLabel; - -@synthesize currentStatus; - -#pragma mark - Init - -- (id)initWithBakerIssue:(BakerIssue *)bakerIssue -{ - self = [super init]; - if (self) { - self.issue = bakerIssue; - self.currentStatus = nil; - - purchaseDelayed = NO; - - #ifdef BAKER_NEWSSTAND - purchasesManager = [PurchasesManager sharedInstance]; - [self addPurchaseObserver:@selector(handleIssueRestored:) name:@"notification_issue_restored"]; - - [self addIssueObserver:@selector(handleDownloadStarted:) name:self.issue.notificationDownloadStartedName]; - [self addIssueObserver:@selector(handleDownloadProgressing:) name:self.issue.notificationDownloadProgressingName]; - [self addIssueObserver:@selector(handleDownloadFinished:) name:self.issue.notificationDownloadFinishedName]; - [self addIssueObserver:@selector(handleDownloadError:) name:self.issue.notificationDownloadErrorName]; - [self addIssueObserver:@selector(handleUnzipError:) name:self.issue.notificationUnzipErrorName]; - #endif - } - return self; -} - -#pragma mark - View Lifecycle - -- (void)viewDidLoad -{ - [super viewDidLoad]; - - CGSize cellSize = [IssueViewController getIssueCellSize]; - - self.view.frame = CGRectMake(0, 0, cellSize.width, cellSize.height); - self.view.backgroundColor = [UIColor clearColor]; - self.view.tag = 42; - - if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")) { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(preferredContentSizeChanged:) name:UIContentSizeCategoryDidChangeNotification object:nil]; - } - - UI ui = [IssueViewController getIssueContentMeasures]; - - self.issueCover = [UIButton buttonWithType:UIButtonTypeCustom]; - issueCover.frame = CGRectMake(ui.cellPadding, ui.cellPadding, ui.thumbWidth, ui.thumbHeight); - - issueCover.backgroundColor = [UIColor colorWithHexString:ISSUES_COVER_BACKGROUND_COLOR]; - issueCover.adjustsImageWhenHighlighted = NO; - issueCover.adjustsImageWhenDisabled = NO; - - issueCover.layer.shadowOpacity = 0.5; - issueCover.layer.shadowOffset = CGSizeMake(0, 2); - issueCover.layer.shouldRasterize = YES; - issueCover.layer.rasterizationScale = [UIScreen mainScreen].scale; - - [issueCover addTarget:self action:@selector(actionButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; - [self.view addSubview:issueCover]; - - // SETUP TITLE LABEL - self.titleLabel = [[[UILabel alloc] init] autorelease]; - titleLabel.textColor = [UIColor colorWithHexString:ISSUES_TITLE_COLOR]; - titleLabel.backgroundColor = [UIColor clearColor]; - titleLabel.lineBreakMode = NSLineBreakByTruncatingTail; - titleLabel.textAlignment = NSTextAlignmentLeft; - - [self.view addSubview:titleLabel]; - - // SETUP INFO LABEL - self.infoLabel = [[[UILabel alloc] init] autorelease]; - infoLabel.textColor = [UIColor colorWithHexString:ISSUES_INFO_COLOR]; - infoLabel.backgroundColor = [UIColor clearColor]; - infoLabel.lineBreakMode = NSLineBreakByTruncatingTail; - infoLabel.textAlignment = NSTextAlignmentLeft; - - [self.view addSubview:infoLabel]; - - // SETUP PRICE LABEL - self.priceLabel = [[[UILabel alloc] init] autorelease]; - priceLabel.textColor = [UIColor colorWithHexString:ISSUES_PRICE_COLOR]; - priceLabel.backgroundColor = [UIColor clearColor]; - priceLabel.lineBreakMode = NSLineBreakByTruncatingTail; - priceLabel.textAlignment = NSTextAlignmentLeft; - - [self.view addSubview:priceLabel]; - - // SETUP ACTION BUTTON - self.actionButton = [UIButton buttonWithType:UIButtonTypeCustom]; - actionButton.backgroundColor = [UIColor colorWithHexString:ISSUES_ACTION_BUTTON_BACKGROUND_COLOR]; - - [actionButton setTitle:NSLocalizedString(@"ACTION_DOWNLOADED_TEXT", nil) forState:UIControlStateNormal]; - [actionButton setTitleColor:[UIColor colorWithHexString:ISSUES_ACTION_BUTTON_COLOR] forState:UIControlStateNormal]; - [actionButton addTarget:self action:@selector(actionButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; - - [self.view addSubview:actionButton]; - - // SETUP ARCHIVE BUTTON - self.archiveButton = [UIButton buttonWithType:UIButtonTypeCustom]; - archiveButton.backgroundColor = [UIColor colorWithHexString:ISSUES_ARCHIVE_BUTTON_BACKGROUND_COLOR]; - - [archiveButton setTitle:NSLocalizedString(@"ARCHIVE_TEXT", nil) forState:UIControlStateNormal]; - [archiveButton setTitleColor:[UIColor colorWithHexString:ISSUES_ARCHIVE_BUTTON_COLOR] forState:UIControlStateNormal]; - - #ifdef BAKER_NEWSSTAND - [archiveButton addTarget:self action:@selector(archiveButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; - [self.view addSubview:archiveButton]; - #endif - - // SETUP DOWN/LOADING SPINNER AND LABEL - self.spinner = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray] autorelease]; - spinner.color = [UIColor colorWithHexString:ISSUES_LOADING_SPINNER_COLOR]; - spinner.backgroundColor = [UIColor clearColor]; - spinner.hidesWhenStopped = YES; - - self.loadingLabel = [[[UILabel alloc] init] autorelease]; - loadingLabel.textColor = [UIColor colorWithHexString:ISSUES_LOADING_LABEL_COLOR]; - loadingLabel.backgroundColor = [UIColor clearColor]; - loadingLabel.textAlignment = NSTextAlignmentLeft; - loadingLabel.text = NSLocalizedString(@"DOWNLOADING_TEXT", nil); - - [self.view addSubview:spinner]; - [self.view addSubview:loadingLabel]; - - // SETUP PROGRESS BAR - self.progressBar = [[[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault] autorelease]; - self.progressBar.progressTintColor = [UIColor colorWithHexString:ISSUES_PROGRESSBAR_TINT_COLOR]; - - [self.view addSubview:progressBar]; - - #ifdef BAKER_NEWSSTAND - // RESUME PENDING NEWSSTAND DOWNLOAD - NKLibrary *nkLib = [NKLibrary sharedLibrary]; - for (NKAssetDownload *asset in [nkLib downloadingAssets]) { - if ([asset.issue.name isEqualToString:self.issue.ID]) { - NSLog(@"[BakerShelf] Resuming abandoned Newsstand download: %@", asset.issue.name); - [self.issue downloadWithAsset:asset]; - } - } - #endif - - [self refreshContentWithCache:NO]; -} -- (void)refreshContentWithCache:(bool)cache { - UIFont *titleFont; - UIFont *infoFont; - UIFont *actionFont; - UIFont *archiveFont; - - #if defined(ISSUES_TITLE_FONT) && defined(ISSUES_TITLE_FONT_SIZE) - titleFont = [UIFont fontWithName:ISSUES_TITLE_FONT size:ISSUES_TITLE_FONT_SIZE]; - #else - if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")) { - titleFont = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]; - } else { - titleFont = [UIFont fontWithName:@"Helvetica" size:15]; - } - #endif - - #if defined(ISSUES_INFO_FONT) && defined(ISSUES_INFO_FONT_SIZE) - infoFont = [UIFont fontWithName:ISSUES_INFO_FONT size:ISSUES_INFO_FONT_SIZE]; - #else - if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")) { - infoFont = [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote]; - } else { - infoFont = [UIFont fontWithName:@"Helvetica" size:15]; - } - #endif - - #if defined(ISSUES_ACTION_BUTTON_FONT) && defined(ISSUES_ACTION_BUTTON_FONT_SIZE) - actionFont = [UIFont fontWithName:ISSUES_ACTION_BUTTON_FONT size:ISSUES_ACTION_BUTTON_FONT_SIZE]; - #else - if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")) { - actionFont = [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote]; - } else { - actionFont = [UIFont fontWithName:@"Helvetica-Bold" size:11]; - } - #endif - - #if defined(ISSUES_ARCHIVE_BUTTON_FONT) && defined(ISSUES_ARCHIVE_BUTTON_FONT_SIZE) - archiveFont = [UIFont fontWithName:ISSUES_ARCHIVE_BUTTON_FONT size:ISSUES_ARCHIVE_BUTTON_FONT_SIZE]; - #else - if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")) { - archiveFont = [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote]; - } else { - archiveFont = [UIFont fontWithName:@"Helvetica-Bold" size:11]; - } - #endif - - UI ui = [IssueViewController getIssueContentMeasures]; - int heightOffset = ui.cellPadding; - uint textLineheight = [@"The brown fox jumps over the lazy dog" sizeWithFont:infoFont constrainedToSize:CGSizeMake(MAXFLOAT, MAXFLOAT)].height; - - // SETUP COVER IMAGE - [self.issue getCoverWithCache:cache andBlock:^(UIImage *image) { - [issueCover setBackgroundImage:image forState:UIControlStateNormal]; - }]; - - // SETUP TITLE LABEL - titleLabel.font = titleFont; - titleLabel.frame = CGRectMake(ui.contentOffset, heightOffset, 170, 60); - titleLabel.numberOfLines = 3; - titleLabel.text = self.issue.title; - [titleLabel sizeToFit]; - - heightOffset = heightOffset + titleLabel.frame.size.height + 5; - - // SETUP INFO LABEL - infoLabel.font = infoFont; - infoLabel.frame = CGRectMake(ui.contentOffset, heightOffset, 170, 60); - infoLabel.numberOfLines = 3; - infoLabel.text = self.issue.info; - [infoLabel sizeToFit]; - - heightOffset = heightOffset + infoLabel.frame.size.height + 5; - - // SETUP PRICE LABEL - self.priceLabel.frame = CGRectMake(ui.contentOffset, heightOffset, 170, textLineheight); - priceLabel.font = infoFont; - - heightOffset = heightOffset + priceLabel.frame.size.height + 10; - - // SETUP ACTION BUTTON - NSString *status = [self.issue getStatus]; - if ([status isEqualToString:@"remote"] || [status isEqualToString:@"purchasable"] || [status isEqualToString:@"purchased"]) { - actionButton.frame = CGRectMake(ui.contentOffset, heightOffset, 110, 30); - } else if ([status isEqualToString:@"downloaded"] || [status isEqualToString:@"bundled"]) { - actionButton.frame = CGRectMake(ui.contentOffset, heightOffset, 80, 30); - } - actionButton.titleLabel.font = actionFont; - - // SETUP ARCHIVE BUTTON - archiveButton.frame = CGRectMake(ui.contentOffset + 80 + 10, heightOffset, 80, 30); - archiveButton.titleLabel.font = archiveFont; - - // SETUP DOWN/LOADING SPINNER AND LABEL - spinner.frame = CGRectMake(ui.contentOffset, heightOffset, 30, 30); - self.loadingLabel.frame = CGRectMake(ui.contentOffset + self.spinner.frame.size.width + 10, heightOffset, 135, 30); - loadingLabel.font = actionFont; - - heightOffset = heightOffset + self.loadingLabel.frame.size.height + 5; - - // SETUP PROGRESS BAR - self.progressBar.frame = CGRectMake(ui.contentOffset, heightOffset, 170, 30); -} - -- (void)preferredContentSizeChanged:(NSNotification *)notification { - [self refreshContentWithCache:YES]; -} - -- (void)viewWillAppear:(BOOL)animated -{ - [super viewWillAppear:animated]; - [self refresh]; -} -- (void)refresh -{ - [self refresh:[self.issue getStatus]]; -} -- (void)refresh:(NSString *)status -{ - //NSLog(@"[BakerShelf] Shelf UI - Refreshing %@ item with status from <%@> to <%@>", self.issue.ID, self.currentStatus, status); - if ([status isEqualToString:@"remote"]) - { - [self.priceLabel setText:NSLocalizedString(@"FREE_TEXT", nil)]; - - [self.actionButton setTitle:NSLocalizedString(@"ACTION_REMOTE_TEXT", nil) forState:UIControlStateNormal]; - [self.spinner stopAnimating]; - - self.actionButton.hidden = NO; - self.archiveButton.hidden = YES; - self.progressBar.hidden = YES; - self.loadingLabel.hidden = YES; - self.priceLabel.hidden = NO; - } - else if ([status isEqualToString:@"connecting"]) - { - NSLog(@"[BakerShelf] '%@' is Connecting...", self.issue.ID); - [self.spinner startAnimating]; - - self.actionButton.hidden = YES; - self.archiveButton.hidden = YES; - self.progressBar.progress = 0; - self.loadingLabel.text = NSLocalizedString(@"CONNECTING_TEXT", nil); - self.loadingLabel.hidden = NO; - self.progressBar.hidden = YES; - self.priceLabel.hidden = YES; - } - else if ([status isEqualToString:@"downloading"]) - { - NSLog(@"[BakerShelf] '%@' is Downloading...", self.issue.ID); - [self.spinner startAnimating]; - - self.actionButton.hidden = YES; - self.archiveButton.hidden = YES; - self.progressBar.progress = 0; - self.loadingLabel.text = NSLocalizedString(@"DOWNLOADING_TEXT", nil); - self.loadingLabel.hidden = NO; - self.progressBar.hidden = NO; - self.priceLabel.hidden = YES; - } - else if ([status isEqualToString:@"downloaded"]) - { - NSLog(@"[BakerShelf] '%@' is Ready to be Read.", self.issue.ID); - [self.actionButton setTitle:NSLocalizedString(@"ACTION_DOWNLOADED_TEXT", nil) forState:UIControlStateNormal]; - [self.spinner stopAnimating]; - - self.actionButton.hidden = NO; - self.archiveButton.hidden = NO; - self.loadingLabel.hidden = YES; - self.progressBar.hidden = YES; - self.priceLabel.hidden = YES; - } - else if ([status isEqualToString:@"bundled"]) - { - [self.actionButton setTitle:NSLocalizedString(@"ACTION_DOWNLOADED_TEXT", nil) forState:UIControlStateNormal]; - [self.spinner stopAnimating]; - - self.actionButton.hidden = NO; - self.archiveButton.hidden = YES; - self.loadingLabel.hidden = YES; - self.progressBar.hidden = YES; - self.priceLabel.hidden = YES; - } - else if ([status isEqualToString:@"opening"]) - { - [self.spinner startAnimating]; - - self.actionButton.hidden = YES; - self.archiveButton.hidden = YES; - self.loadingLabel.text = NSLocalizedString(@"OPENING_TEXT", nil); - self.loadingLabel.hidden = NO; - self.progressBar.hidden = YES; - self.priceLabel.hidden = YES; - } - else if ([status isEqualToString:@"purchasable"]) - { - [self.actionButton setTitle:NSLocalizedString(@"ACTION_BUY_TEXT", nil) forState:UIControlStateNormal]; - [self.spinner stopAnimating]; - - if (self.issue.price) { - [self.priceLabel setText:self.issue.price]; - } - - self.actionButton.hidden = NO; - self.archiveButton.hidden = YES; - self.progressBar.hidden = YES; - self.loadingLabel.hidden = YES; - self.priceLabel.hidden = NO; - } - else if ([status isEqualToString:@"purchasing"]) - { - NSLog(@"[BakerShelf] '%@' is being Purchased...", self.issue.ID); - [self.spinner startAnimating]; - - self.loadingLabel.text = NSLocalizedString(@"BUYING_TEXT", nil); - - self.actionButton.hidden = YES; - self.archiveButton.hidden = YES; - self.progressBar.hidden = YES; - self.loadingLabel.hidden = NO; - self.priceLabel.hidden = NO; - } - else if ([status isEqualToString:@"purchased"]) - { - NSLog(@"[BakerShelf] '%@' is Purchased.", self.issue.ID); - [self.priceLabel setText:NSLocalizedString(@"PURCHASED_TEXT", nil)]; - - [self.actionButton setTitle:NSLocalizedString(@"ACTION_REMOTE_TEXT", nil) forState:UIControlStateNormal]; - [self.spinner stopAnimating]; - - self.actionButton.hidden = NO; - self.archiveButton.hidden = YES; - self.progressBar.hidden = YES; - self.loadingLabel.hidden = YES; - self.priceLabel.hidden = NO; - } - else if ([status isEqualToString:@"unpriced"]) - { - [self.spinner startAnimating]; - - self.loadingLabel.text = NSLocalizedString(@"RETRIEVING_TEXT", nil); - - self.actionButton.hidden = YES; - self.archiveButton.hidden = YES; - self.progressBar.hidden = YES; - self.loadingLabel.hidden = NO; - self.priceLabel.hidden = YES; - } - - [self refreshContentWithCache:YES]; - - self.currentStatus = status; -} - -#pragma mark - Memory management - -- (void)dealloc -{ - [issue release]; - [actionButton release]; - [archiveButton release]; - [progressBar release]; - [spinner release]; - [loadingLabel release]; - [priceLabel release]; - [issueCover release]; - [titleLabel release]; - [infoLabel release]; - [currentStatus release]; - - [super dealloc]; -} - -#pragma mark - Issue management - -- (void)actionButtonPressed:(UIButton *)sender -{ - NSString *status = [self.issue getStatus]; - if ([status isEqualToString:@"remote"] || [status isEqualToString:@"purchased"]) { - #ifdef BAKER_NEWSSTAND - [[NSNotificationCenter defaultCenter] postNotificationName:@"BakerIssueDownload" object:self]; // -> Baker Analytics Event - [self download]; - #endif - } else if ([status isEqualToString:@"downloaded"] || [status isEqualToString:@"bundled"]) { - [[NSNotificationCenter defaultCenter] postNotificationName:@"BakerIssueOpen" object:self]; // -> Baker Analytics Event - [self read]; - } else if ([status isEqualToString:@"downloading"]) { - // TODO: assuming it is supported by NewsstandKit, implement a "Cancel" operation - } else if ([status isEqualToString:@"purchasable"]) { - #ifdef BAKER_NEWSSTAND - [[NSNotificationCenter defaultCenter] postNotificationName:@"BakerIssuePurchase" object:self]; // -> Baker Analytics Event - [self buy]; - #endif - } -} -#ifdef BAKER_NEWSSTAND -- (void)download { - [self.issue download]; -} -- (void)buy { - [self addPurchaseObserver:@selector(handleIssuePurchased:) name:@"notification_issue_purchased"]; - [self addPurchaseObserver:@selector(handleIssuePurchaseFailed:) name:@"notification_issue_purchase_failed"]; - - if (![purchasesManager purchase:self.issue.productID]) { - // Still retrieving SKProduct: delay purchase - purchaseDelayed = YES; - - [self removePurchaseObserver:@"notification_issue_purchased"]; - [self removePurchaseObserver:@"notification_issue_purchase_failed"]; - - [purchasesManager retrievePriceFor:self.issue.productID]; - - self.issue.transientStatus = BakerIssueTransientStatusUnpriced; - [self refresh]; - } else { - self.issue.transientStatus = BakerIssueTransientStatusPurchasing; - [self refresh]; - } -} -- (void)handleIssuePurchased:(NSNotification *)notification { - SKPaymentTransaction *transaction = [notification.userInfo objectForKey:@"transaction"]; - - if ([transaction.payment.productIdentifier isEqualToString:issue.productID]) { - - [self removePurchaseObserver:@"notification_issue_purchased"]; - [self removePurchaseObserver:@"notification_issue_purchase_failed"]; - - [purchasesManager markAsPurchased:transaction.payment.productIdentifier]; - - if ([purchasesManager finishTransaction:transaction]) { - if (!transaction.originalTransaction) { - // Do not show alert on restoring a transaction - [Utils showAlertWithTitle:NSLocalizedString(@"ISSUE_PURCHASE_SUCCESSFUL_TITLE", nil) - message:[NSString stringWithFormat:NSLocalizedString(@"ISSUE_PURCHASE_SUCCESSFUL_MESSAGE", nil), self.issue.title] - buttonTitle:NSLocalizedString(@"ISSUE_PURCHASE_SUCCESSFUL_CLOSE", nil)]; - } - } else { - [Utils showAlertWithTitle:NSLocalizedString(@"TRANSACTION_RECORDING_FAILED_TITLE", nil) - message:NSLocalizedString(@"TRANSACTION_RECORDING_FAILED_MESSAGE", nil) - buttonTitle:NSLocalizedString(@"TRANSACTION_RECORDING_FAILED_CLOSE", nil)]; - } - - self.issue.transientStatus = BakerIssueTransientStatusNone; - - [purchasesManager retrievePurchasesFor:[NSSet setWithObject:self.issue.productID] withCallback:^(NSDictionary *purchases) { - [self refresh]; - }]; - } -} -- (void)handleIssuePurchaseFailed:(NSNotification *)notification { - SKPaymentTransaction *transaction = [notification.userInfo objectForKey:@"transaction"]; - - if ([transaction.payment.productIdentifier isEqualToString:issue.productID]) { - // Show an error, unless it was the user who cancelled the transaction - if (transaction.error.code != SKErrorPaymentCancelled) { - [Utils showAlertWithTitle:NSLocalizedString(@"ISSUE_PURCHASE_FAILED_TITLE", nil) - message:[transaction.error localizedDescription] - buttonTitle:NSLocalizedString(@"ISSUE_PURCHASE_FAILED_CLOSE", nil)]; - } - - [self removePurchaseObserver:@"notification_issue_purchased"]; - [self removePurchaseObserver:@"notification_issue_purchase_failed"]; - - self.issue.transientStatus = BakerIssueTransientStatusNone; - [self refresh]; - } -} - -- (void)handleIssueRestored:(NSNotification *)notification { - SKPaymentTransaction *transaction = [notification.userInfo objectForKey:@"transaction"]; - - if ([transaction.payment.productIdentifier isEqualToString:issue.productID]) { - [purchasesManager markAsPurchased:transaction.payment.productIdentifier]; - - if (![purchasesManager finishTransaction:transaction]) { - NSLog(@"[BakerShelf] Could not confirm purchase restore with remote server for %@", transaction.payment.productIdentifier); - } - - self.issue.transientStatus = BakerIssueTransientStatusNone; - [self refresh]; - } -} - -- (void)setPrice:(NSString *)price { - self.issue.price = price; - if (purchaseDelayed) { - purchaseDelayed = NO; - [self buy]; - } else { - [self refresh]; - } -} -#endif -- (void)read -{ - self.issue.transientStatus = BakerIssueTransientStatusOpening; - [self refresh]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"read_issue_request" object:self]; -} - -#pragma mark - Newsstand download management - -- (void)handleDownloadStarted:(NSNotification *)notification { - [self refresh]; -} -- (void)handleDownloadProgressing:(NSNotification *)notification { - float bytesWritten = [[notification.userInfo objectForKey:@"totalBytesWritten"] floatValue]; - float bytesExpected = [[notification.userInfo objectForKey:@"expectedTotalBytes"] floatValue]; - - if ([self.currentStatus isEqualToString:@"connecting"]) { - self.issue.transientStatus = BakerIssueTransientStatusDownloading; - [self refresh]; - } - [self.progressBar setProgress:(bytesWritten / bytesExpected) animated:YES]; -} -- (void)handleDownloadFinished:(NSNotification *)notification { - self.issue.transientStatus = BakerIssueTransientStatusNone; - [self refresh]; -} -- (void)handleDownloadError:(NSNotification *)notification { - [Utils showAlertWithTitle:NSLocalizedString(@"DOWNLOAD_FAILED_TITLE", nil) - message:NSLocalizedString(@"DOWNLOAD_FAILED_MESSAGE", nil) - buttonTitle:NSLocalizedString(@"DOWNLOAD_FAILED_CLOSE", nil)]; - - self.issue.transientStatus = BakerIssueTransientStatusNone; - [self refresh]; -} -- (void)handleUnzipError:(NSNotification *)notification { - [Utils showAlertWithTitle:NSLocalizedString(@"UNZIP_FAILED_TITLE", nil) - message:NSLocalizedString(@"UNZIP_FAILED_MESSAGE", nil) - buttonTitle:NSLocalizedString(@"UNZIP_FAILED_CLOSE", nil)]; - - self.issue.transientStatus = BakerIssueTransientStatusNone; - [self refresh]; -} - -#pragma mark - Newsstand archive management - -#ifdef BAKER_NEWSSTAND -- (void)archiveButtonPressed:(UIButton *)sender -{ - UIAlertView *updateAlert = [[UIAlertView alloc] - initWithTitle: NSLocalizedString(@"ARCHIVE_ALERT_TITLE", nil) - message: NSLocalizedString(@"ARCHIVE_ALERT_MESSAGE", nil) - delegate: self - cancelButtonTitle: NSLocalizedString(@"ARCHIVE_ALERT_BUTTON_CANCEL", nil) - otherButtonTitles: NSLocalizedString(@"ARCHIVE_ALERT_BUTTON_OK", nil), nil]; - [updateAlert show]; - [updateAlert release]; -} - -- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex -{ - if(buttonIndex == 1){ - [[NSNotificationCenter defaultCenter] postNotificationName:@"BakerIssueArchive" object:self]; // -> Baker Analytics Event - - NKLibrary *nkLib = [NKLibrary sharedLibrary]; - NKIssue *nkIssue = [nkLib issueWithName:self.issue.ID]; - NSString *name = nkIssue.name; - NSDate *date = nkIssue.date; - - [nkLib removeIssue:nkIssue]; - - nkIssue = [nkLib addIssueWithName:name date:date]; - self.issue.path = [[nkIssue contentURL] path]; - - [self refresh]; - } -} -#endif - -#pragma mark - Helper methods - -- (void)addPurchaseObserver:(SEL)notificationSelector name:(NSString *)notificationName { - #ifdef BAKER_NEWSSTAND - [[NSNotificationCenter defaultCenter] addObserver:self - selector:notificationSelector - name:notificationName - object:purchasesManager]; - #endif -} - -- (void)removePurchaseObserver:(NSString *)notificationName { - #ifdef BAKER_NEWSSTAND - [[NSNotificationCenter defaultCenter] removeObserver:self - name:notificationName - object:purchasesManager]; - #endif -} - -- (void)addIssueObserver:(SEL)notificationSelector name:(NSString *)notificationName { - #ifdef BAKER_NEWSSTAND - [[NSNotificationCenter defaultCenter] addObserver:self - selector:notificationSelector - name:notificationName - object:nil]; - #endif -} - -+ (UI)getIssueContentMeasures -{ - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { - UI iPad = { - .cellPadding = 30, - .thumbWidth = 135, - .thumbHeight = 180, - .contentOffset = 184 - }; - return iPad; - } else { - UI iPhone = { - .cellPadding = 22, - .thumbWidth = 87, - .thumbHeight = 116, - .contentOffset = 128 - }; - return iPhone; - } -} - -+ (int)getIssueCellHeight -{ - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { - return 240; - } else { - return 190; - } -} -+ (CGSize)getIssueCellSize -{ - CGRect screenRect = [[UIScreen mainScreen] bounds]; - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { - return CGSizeMake((screenRect.size.width - 10) / 2, [IssueViewController getIssueCellHeight]); - } else { - return CGSizeMake(screenRect.size.width - 10, [IssueViewController getIssueCellHeight]); - } -} - -@end diff --git a/BakerShelf/ShelfViewController.m b/BakerShelf/ShelfViewController.m deleted file mode 100644 index 095a4ed4..00000000 --- a/BakerShelf/ShelfViewController.m +++ /dev/null @@ -1,783 +0,0 @@ -// -// ShelfViewController.m -// Baker -// -// ========================================================================================== -// -// Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi -// 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 Baker Framework nor the names of its contributors may be used to -// endorse or promote products derived from this software without specific prior written -// permission. -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT -// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// - -#import "ShelfViewController.h" -#import "UICustomNavigationBar.h" -#import "Constants.h" - -#import "BakerViewController.h" -#import "IssueViewController.h" - -#import "NSData+Base64.h" -#import "NSString+Extensions.h" -#import "Utils.h" - -@implementation ShelfViewController - -@synthesize issues; -@synthesize issueViewControllers; -@synthesize gridView; -@synthesize subscribeButton; -@synthesize refreshButton; -@synthesize shelfStatus; -@synthesize subscriptionsActionSheet; -@synthesize supportedOrientation; -@synthesize blockingProgressView; -@synthesize bookToBeProcessed; - -#pragma mark - Init - -- (id)init { - self = [super init]; - if (self) { - #ifdef BAKER_NEWSSTAND - purchasesManager = [PurchasesManager sharedInstance]; - [self addPurchaseObserver:@selector(handleProductsRetrieved:) - name:@"notification_products_retrieved"]; - [self addPurchaseObserver:@selector(handleProductsRequestFailed:) - name:@"notification_products_request_failed"]; - [self addPurchaseObserver:@selector(handleSubscriptionPurchased:) - name:@"notification_subscription_purchased"]; - [self addPurchaseObserver:@selector(handleSubscriptionFailed:) - name:@"notification_subscription_failed"]; - [self addPurchaseObserver:@selector(handleSubscriptionRestored:) - name:@"notification_subscription_restored"]; - [self addPurchaseObserver:@selector(handleRestoreFailed:) - name:@"notification_restore_failed"]; - [self addPurchaseObserver:@selector(handleMultipleRestores:) - name:@"notification_multiple_restores"]; - [self addPurchaseObserver:@selector(handleRestoredIssueNotRecognised:) - name:@"notification_restored_issue_not_recognised"]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(receiveBookProtocolNotification:) - name:@"notification_book_protocol" - object:nil]; - - [[SKPaymentQueue defaultQueue] addTransactionObserver:purchasesManager]; - #endif - - api = [BakerAPI sharedInstance]; - issuesManager = [[IssuesManager sharedInstance] retain]; - notRecognisedTransactions = [[NSMutableArray alloc] init]; - - self.shelfStatus = [[[ShelfStatus alloc] init] autorelease]; - self.issueViewControllers = [[[NSMutableArray alloc] init] autorelease]; - self.supportedOrientation = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UISupportedInterfaceOrientations"]; - self.bookToBeProcessed = nil; - - #ifdef BAKER_NEWSSTAND - [self handleRefresh:nil]; - #endif - } - return self; -} - -- (id)initWithBooks:(NSArray *)currentBooks -{ - self = [self init]; - if (self) { - self.issues = currentBooks; - - NSMutableArray *controllers = [NSMutableArray array]; - for (BakerIssue *issue in self.issues) { - IssueViewController *controller = [self createIssueViewControllerWithIssue:issue]; - [controllers addObject:controller]; - } - self.issueViewControllers = [NSMutableArray arrayWithArray:controllers]; - } - return self; -} - -#pragma mark - Memory management - -- (void)dealloc -{ - [gridView release]; - [issueViewControllers release]; - [issues release]; - [subscribeButton release]; - [refreshButton release]; - [shelfStatus release]; - [subscriptionsActionSheet release]; - [supportedOrientation release]; - [blockingProgressView release]; - [issuesManager release]; - [notRecognisedTransactions release]; - [bookToBeProcessed release]; - - #ifdef BAKER_NEWSSTAND - [purchasesManager release]; - #endif - - [super dealloc]; -} - -#pragma mark - View lifecycle - -- (void)viewDidLoad -{ - [super viewDidLoad]; - - self.navigationItem.title = NSLocalizedString(@"SHELF_NAVIGATION_TITLE", nil); - - self.background = [[[UIImageView alloc] init] autorelease]; - - self.gridView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:[[[UICollectionViewFlowLayout alloc] init] autorelease]]; - self.gridView.dataSource = self; - self.gridView.delegate = self; - self.gridView.backgroundColor = [UIColor clearColor]; - [self.gridView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cellIdentifier"]; - - [self.view addSubview:self.background]; - [self.view addSubview:self.gridView]; - - [self willRotateToInterfaceOrientation:self.interfaceOrientation duration:0]; - [self.gridView reloadData]; - - #ifdef BAKER_NEWSSTAND - self.refreshButton = [[[UIBarButtonItem alloc] - initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh - target:self - action:@selector(handleRefresh:)] - autorelease]; - - self.subscribeButton = [[[UIBarButtonItem alloc] - initWithTitle: NSLocalizedString(@"SUBSCRIBE_BUTTON_TEXT", nil) - style:UIBarButtonItemStylePlain - target:self - action:@selector(handleSubscribeButtonPressed:)] - autorelease]; - - self.blockingProgressView = [[UIAlertView alloc] - initWithTitle:@"Processing..." - message:@"\n" - delegate:nil - cancelButtonTitle:nil - otherButtonTitles:nil]; - UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; - spinner.center = CGPointMake(139.5, 75.5); // .5 so it doesn't blur - [self.blockingProgressView addSubview:spinner]; - [spinner startAnimating]; - [spinner release]; - - NSMutableSet *subscriptions = [NSMutableSet setWithArray:AUTO_RENEWABLE_SUBSCRIPTION_PRODUCT_IDS]; - if ([FREE_SUBSCRIPTION_PRODUCT_ID length] > 0 && ![purchasesManager isPurchased:FREE_SUBSCRIPTION_PRODUCT_ID]) { - [subscriptions addObject:FREE_SUBSCRIPTION_PRODUCT_ID]; - } - [purchasesManager retrievePricesFor:subscriptions andEnableFailureNotifications:NO]; - #endif -} -- (void)viewWillAppear:(BOOL)animated -{ - [super viewWillAppear:animated]; - [self.navigationController.navigationBar setTranslucent:NO]; - [self willRotateToInterfaceOrientation:self.interfaceOrientation duration:0]; - - for (IssueViewController *controller in self.issueViewControllers) { - controller.issue.transientStatus = BakerIssueTransientStatusNone; - [controller refresh]; - } - - #ifdef BAKER_NEWSSTAND - NSMutableArray *buttonItems = [NSMutableArray arrayWithObject:self.refreshButton]; - if ([purchasesManager hasSubscriptions] || [issuesManager hasProductIDs]) { - [buttonItems addObject:self.subscribeButton]; - } - self.navigationItem.leftBarButtonItems = buttonItems; - #endif - - UIBarButtonItem *infoButton = [[[UIBarButtonItem alloc] - initWithTitle: NSLocalizedString(@"INFO_BUTTON_TEXT", nil) - style:UIBarButtonItemStylePlain - target:self - action:@selector(handleInfoButtonPressed:)] - autorelease]; - - // Remove file info.html if you don't want the info button to be added to the shelf navigation bar - NSString *infoPath = [[NSBundle mainBundle] pathForResource:@"info" ofType:@"html" inDirectory:@"info"]; - if ([[NSFileManager defaultManager] fileExistsAtPath:infoPath]) { - self.navigationItem.rightBarButtonItem = infoButton; - } -} -- (void)viewDidAppear:(BOOL)animated -{ - if (self.bookToBeProcessed) { - [self handleBookToBeProcessed]; - } -} -- (NSUInteger)supportedInterfaceOrientations -{ - return UIInterfaceOrientationMaskAll; -} -- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation -{ - return [supportedOrientation indexOfObject:[NSString stringFromInterfaceOrientation:interfaceOrientation]] != NSNotFound; -} -- (BOOL)shouldAutorotate -{ - return YES; -} -- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration -{ - int width = 0; - int height = 0; - - NSString *image = @""; - CGSize size = [UIScreen mainScreen].bounds.size; - int landscapePadding = 0; - - if (UIInterfaceOrientationIsPortrait(toInterfaceOrientation)) { - width = size.width; - height = size.height - 64; - image = @"shelf-bg-portrait"; - } else if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) { - width = size.height; - height = size.width - 64; - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { - height = height + 12; - } - image = @"shelf-bg-landscape"; - CGFloat cellWidth = [IssueViewController getIssueCellSize].width; - landscapePadding = width / 4 - cellWidth / 2; - } - - if (size.height == 568) { - image = [NSString stringWithFormat:@"%@-568h", image]; - } else { - image = [NSString stringWithFormat:@"%@", image]; - } - - int bannerHeight = [ShelfViewController getBannerHeight]; - - self.background.frame = CGRectMake(0, 0, width, height); - self.background.image = [UIImage imageNamed:image]; - - self.gridView.frame = CGRectMake(landscapePadding, bannerHeight, width - 2 * landscapePadding, height - bannerHeight); -} -- (IssueViewController *)createIssueViewControllerWithIssue:(BakerIssue *)issue -{ - IssueViewController *controller = [[[IssueViewController alloc] initWithBakerIssue:issue] autorelease]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleReadIssue:) name:@"read_issue_request" object:controller]; - return controller; -} - -#pragma mark - Shelf data source - -- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { - return 1; -} - -- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { - return [issueViewControllers count]; -} - -- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { - CGSize cellSize = [IssueViewController getIssueCellSize]; - CGRect cellFrame = CGRectMake(0, 0, cellSize.width, cellSize.height); - - static NSString *cellIdentifier = @"cellIdentifier"; - UICollectionViewCell* cell = [self.gridView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath]; - if (cell == nil) - { - UICollectionViewCell* cell = [[[UICollectionViewCell alloc] initWithFrame:cellFrame] autorelease]; - - cell.contentView.backgroundColor = [UIColor clearColor]; - cell.backgroundColor = [UIColor clearColor]; - } - - IssueViewController *controller = [self.issueViewControllers objectAtIndex:indexPath.row]; - UIView *removableIssueView = [cell.contentView viewWithTag:42]; - if (removableIssueView) { - [removableIssueView removeFromSuperview]; - } - [cell.contentView addSubview:controller.view]; - - return cell; -} - -- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { - return [IssueViewController getIssueCellSize]; -} - -#ifdef BAKER_NEWSSTAND -- (void)handleRefresh:(NSNotification *)notification { - [self setrefreshButtonEnabled:NO]; - - [issuesManager refresh:^(BOOL status) { - if(status) { - self.issues = issuesManager.issues; - - [shelfStatus load]; - for (BakerIssue *issue in self.issues) { - issue.price = [shelfStatus priceFor:issue.productID]; - } - - void (^updateIssues)() = ^{ - // Step 1: remove controllers for issues that no longer exist - __block NSMutableArray *discardedControllers = [NSMutableArray array]; - [self.issueViewControllers enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - IssueViewController *ivc = (IssueViewController *)obj; - - if (![self bakerIssueWithID:ivc.issue.ID]) { - [discardedControllers addObject:ivc]; - [self.gridView deleteItemsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForItem:idx inSection:0]]]; - } - }]; - [self.issueViewControllers removeObjectsInArray:discardedControllers]; - - // Step 2: add controllers for issues that did not yet exist (and refresh the ones that do exist) - [self.issues enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - // NOTE: this block changes the issueViewController array while looping - BakerIssue *issue = (BakerIssue *)obj; - - IssueViewController *existingIvc = [self issueViewControllerWithID:issue.ID]; - - if (existingIvc) { - existingIvc.issue = issue; - } else { - IssueViewController *newIvc = [self createIssueViewControllerWithIssue:issue]; - [self.issueViewControllers insertObject:newIvc atIndex:idx]; - [self.gridView insertItemsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForItem:idx inSection:0] ] ]; - } - }]; - - [self.gridView reloadData]; - }; - - // When first launched, the grid is not initialised, so we can't - // call in the "batch update" method of the grid view - if (self.gridView) { - [self.gridView performBatchUpdates:updateIssues completion:nil]; - } - else { - updateIssues(); - } - - [purchasesManager retrievePurchasesFor:[issuesManager productIDs] withCallback:^(NSDictionary *purchases) { - // List of purchases has been returned, so we can refresh all issues - [self.issueViewControllers enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - [(IssueViewController *)obj refreshContentWithCache:NO]; - }]; - [self setrefreshButtonEnabled:YES]; - }]; - - [purchasesManager retrievePricesFor:issuesManager.productIDs andEnableFailureNotifications:NO]; - } else { - [Utils showAlertWithTitle:NSLocalizedString(@"INTERNET_CONNECTION_UNAVAILABLE_TITLE", nil) - message:NSLocalizedString(@"INTERNET_CONNECTION_UNAVAILABLE_MESSAGE", nil) - buttonTitle:NSLocalizedString(@"INTERNET_CONNECTION_UNAVAILABLE_CLOSE", nil)]; - [self setrefreshButtonEnabled:YES]; - } - }]; -} - -- (IssueViewController *)issueViewControllerWithID:(NSString *)ID { - __block IssueViewController* foundController = nil; - [self.issueViewControllers enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - IssueViewController *ivc = (IssueViewController *)obj; - if ([ivc.issue.ID isEqualToString:ID]) { - foundController = ivc; - *stop = YES; - } - }]; - return foundController; -} - -- (BakerIssue *)bakerIssueWithID:(NSString *)ID { - __block BakerIssue *foundIssue = nil; - [self.issues enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - BakerIssue *issue = (BakerIssue *)obj; - if ([issue.ID isEqualToString:ID]) { - foundIssue = issue; - *stop = YES; - } - }]; - return foundIssue; -} - -#pragma mark - Store Kit -- (void)handleSubscribeButtonPressed:(NSNotification *)notification { - if (subscriptionsActionSheet.visible) { - [subscriptionsActionSheet dismissWithClickedButtonIndex:(subscriptionsActionSheet.numberOfButtons - 1) animated:YES]; - } else { - self.subscriptionsActionSheet = [self buildSubscriptionsActionSheet]; - [subscriptionsActionSheet showFromBarButtonItem:self.subscribeButton animated:YES]; - } -} - -- (UIActionSheet *)buildSubscriptionsActionSheet { - NSString *title; - if ([api canGetPurchasesJSON]) { - if (purchasesManager.subscribed) { - title = NSLocalizedString(@"SUBSCRIPTIONS_SHEET_SUBSCRIBED", nil); - } else { - title = NSLocalizedString(@"SUBSCRIPTIONS_SHEET_NOT_SUBSCRIBED", nil); - } - } else { - title = NSLocalizedString(@"SUBSCRIPTIONS_SHEET_GENERIC", nil); - } - - UIActionSheet *sheet = [[UIActionSheet alloc]initWithTitle:title - delegate:self - cancelButtonTitle:nil - destructiveButtonTitle:nil - otherButtonTitles: nil]; - NSMutableArray *actions = [NSMutableArray array]; - - if (!purchasesManager.subscribed) { - if ([FREE_SUBSCRIPTION_PRODUCT_ID length] > 0 && ![purchasesManager isPurchased:FREE_SUBSCRIPTION_PRODUCT_ID]) { - [sheet addButtonWithTitle:NSLocalizedString(@"SUBSCRIPTIONS_SHEET_FREE", nil)]; - [actions addObject:FREE_SUBSCRIPTION_PRODUCT_ID]; - } - - for (NSString *productId in AUTO_RENEWABLE_SUBSCRIPTION_PRODUCT_IDS) { - NSString *title = NSLocalizedString(productId, nil); - NSString *price = [purchasesManager priceFor:productId]; - if (price) { - [sheet addButtonWithTitle:[NSString stringWithFormat:@"%@ %@", title, price]]; - [actions addObject:productId]; - } - } - } - - if ([issuesManager hasProductIDs]) { - [sheet addButtonWithTitle:NSLocalizedString(@"SUBSCRIPTIONS_SHEET_RESTORE", nil)]; - [actions addObject:@"restore"]; - } - - [sheet addButtonWithTitle:NSLocalizedString(@"SUBSCRIPTIONS_SHEET_CLOSE", nil)]; - [actions addObject:@"cancel"]; - - self.subscriptionsActionSheetActions = actions; - - sheet.cancelButtonIndex = sheet.numberOfButtons - 1; - return sheet; -} - -- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex { - if (actionSheet == subscriptionsActionSheet) { - NSString *action = [self.subscriptionsActionSheetActions objectAtIndex:buttonIndex]; - if ([action isEqualToString:@"cancel"]) { - NSLog(@"Action sheet: cancel"); - [self setSubscribeButtonEnabled:YES]; - } else if ([action isEqualToString:@"restore"]) { - [self.blockingProgressView show]; - [purchasesManager restore]; - NSLog(@"Action sheet: restore"); - } else { - NSLog(@"Action sheet: %@", action); - [[NSNotificationCenter defaultCenter] postNotificationName:@"BakerSubscriptionPurchase" object:self]; // -> Baker Analytics Event - [self setSubscribeButtonEnabled:NO]; - if (![purchasesManager purchase:action]){ - [Utils showAlertWithTitle:NSLocalizedString(@"SUBSCRIPTION_FAILED_TITLE", nil) - message:nil - buttonTitle:NSLocalizedString(@"SUBSCRIPTION_FAILED_CLOSE", nil)]; - [self setSubscribeButtonEnabled:YES]; - } - } - } -} - -- (void)handleRestoreFailed:(NSNotification *)notification { - NSError *error = [notification.userInfo objectForKey:@"error"]; - [Utils showAlertWithTitle:NSLocalizedString(@"RESTORE_FAILED_TITLE", nil) - message:[error localizedDescription] - buttonTitle:NSLocalizedString(@"RESTORE_FAILED_CLOSE", nil)]; - - [self.blockingProgressView dismissWithClickedButtonIndex:0 animated:YES]; - -} - -- (void)handleMultipleRestores:(NSNotification *)notification { - #ifdef BAKER_NEWSSTAND - if ([notRecognisedTransactions count] > 0) { - NSSet *productIDs = [NSSet setWithArray:[[notRecognisedTransactions valueForKey:@"payment"] valueForKey:@"productIdentifier"]]; - NSString *productsList = [[productIDs allObjects] componentsJoinedByString:@", "]; - - [Utils showAlertWithTitle:NSLocalizedString(@"RESTORED_ISSUE_NOT_RECOGNISED_TITLE", nil) - message:[NSString stringWithFormat:NSLocalizedString(@"RESTORED_ISSUE_NOT_RECOGNISED_MESSAGE", nil), productsList] - buttonTitle:NSLocalizedString(@"RESTORED_ISSUE_NOT_RECOGNISED_CLOSE", nil)]; - - for (SKPaymentTransaction *transaction in notRecognisedTransactions) { - [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; - } - [notRecognisedTransactions removeAllObjects]; - } - #endif - - [self handleRefresh:nil]; - [self.blockingProgressView dismissWithClickedButtonIndex:0 animated:YES]; -} - -- (void)handleRestoredIssueNotRecognised:(NSNotification *)notification { - SKPaymentTransaction *transaction = [notification.userInfo objectForKey:@"transaction"]; - [notRecognisedTransactions addObject:transaction]; -} - -// TODO: this can probably be removed -- (void)handleSubscription:(NSNotification *)notification { - [self setSubscribeButtonEnabled:NO]; - [purchasesManager purchase:FREE_SUBSCRIPTION_PRODUCT_ID]; -} - -- (void)handleSubscriptionPurchased:(NSNotification *)notification { - SKPaymentTransaction *transaction = [notification.userInfo objectForKey:@"transaction"]; - - [purchasesManager markAsPurchased:transaction.payment.productIdentifier]; - [self setSubscribeButtonEnabled:YES]; - - if ([purchasesManager finishTransaction:transaction]) { - if (!purchasesManager.subscribed) { - [Utils showAlertWithTitle:NSLocalizedString(@"SUBSCRIPTION_SUCCESSFUL_TITLE", nil) - message:NSLocalizedString(@"SUBSCRIPTION_SUCCESSFUL_MESSAGE", nil) - buttonTitle:NSLocalizedString(@"SUBSCRIPTION_SUCCESSFUL_CLOSE", nil)]; - - [self handleRefresh:nil]; - } - } else { - [Utils showAlertWithTitle:NSLocalizedString(@"TRANSACTION_RECORDING_FAILED_TITLE", nil) - message:NSLocalizedString(@"TRANSACTION_RECORDING_FAILED_MESSAGE", nil) - buttonTitle:NSLocalizedString(@"TRANSACTION_RECORDING_FAILED_CLOSE", nil)]; - } -} - -- (void)handleSubscriptionFailed:(NSNotification *)notification { - SKPaymentTransaction *transaction = [notification.userInfo objectForKey:@"transaction"]; - - // Show an error, unless it was the user who cancelled the transaction - if (transaction.error.code != SKErrorPaymentCancelled) { - [Utils showAlertWithTitle:NSLocalizedString(@"SUBSCRIPTION_FAILED_TITLE", nil) - message:[transaction.error localizedDescription] - buttonTitle:NSLocalizedString(@"SUBSCRIPTION_FAILED_CLOSE", nil)]; - } - - [self setSubscribeButtonEnabled:YES]; -} - -- (void)handleSubscriptionRestored:(NSNotification *)notification { - SKPaymentTransaction *transaction = [notification.userInfo objectForKey:@"transaction"]; - - [purchasesManager markAsPurchased:transaction.payment.productIdentifier]; - - if (![purchasesManager finishTransaction:transaction]) { - NSLog(@"Could not confirm purchase restore with remote server for %@", transaction.payment.productIdentifier); - } -} - -- (void)handleProductsRetrieved:(NSNotification *)notification { - NSSet *ids = [notification.userInfo objectForKey:@"ids"]; - BOOL issuesRetrieved = NO; - - for (NSString *productId in ids) { - if ([productId isEqualToString:FREE_SUBSCRIPTION_PRODUCT_ID]) { - // ID is for a free subscription - [self setSubscribeButtonEnabled:YES]; - } else if ([AUTO_RENEWABLE_SUBSCRIPTION_PRODUCT_IDS containsObject:productId]) { - // ID is for an auto-renewable subscription - [self setSubscribeButtonEnabled:YES]; - } else { - // ID is for an issue - issuesRetrieved = YES; - } - } - - if (issuesRetrieved) { - NSString *price; - for (IssueViewController *controller in self.issueViewControllers) { - price = [purchasesManager priceFor:controller.issue.productID]; - if (price) { - [controller setPrice:price]; - [shelfStatus setPrice:price for:controller.issue.productID]; - } - } - [shelfStatus save]; - } -} - -- (void)handleProductsRequestFailed:(NSNotification *)notification { - NSError *error = [notification.userInfo objectForKey:@"error"]; - - [Utils showAlertWithTitle:NSLocalizedString(@"PRODUCTS_REQUEST_FAILED_TITLE", nil) - message:[error localizedDescription] - buttonTitle:NSLocalizedString(@"PRODUCTS_REQUEST_FAILED_CLOSE", nil)]; -} - -#endif - -#pragma mark - Navigation management - -- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { - [collectionView deselectItemAtIndexPath:indexPath animated:YES]; -} - -- (void)readIssue:(BakerIssue *)issue -{ - BakerBook *book = nil; - NSString *status = [issue getStatus]; - - #ifdef BAKER_NEWSSTAND - if ([status isEqual:@"opening"]) { - book = [[[BakerBook alloc] initWithBookPath:issue.path bundled:NO] autorelease]; - if (book) { - [self pushViewControllerWithBook:book]; - } else { - NSLog(@"[ERROR] Book %@ could not be initialized", issue.ID); - issue.transientStatus = BakerIssueTransientStatusNone; - // Let's refresh everything as it's easier. This is an edge case anyway ;) - for (IssueViewController *controller in issueViewControllers) { - [controller refresh]; - } - [Utils showAlertWithTitle:NSLocalizedString(@"ISSUE_OPENING_FAILED_TITLE", nil) - message:NSLocalizedString(@"ISSUE_OPENING_FAILED_MESSAGE", nil) - buttonTitle:NSLocalizedString(@"ISSUE_OPENING_FAILED_CLOSE", nil)]; - } - } - #else - if ([status isEqual:@"bundled"]) { - book = [issue bakerBook]; - [self pushViewControllerWithBook:book]; - } - #endif -} -- (void)handleReadIssue:(NSNotification *)notification -{ - IssueViewController *controller = notification.object; - [self readIssue:controller.issue]; -} -- (void)receiveBookProtocolNotification:(NSNotification *)notification -{ - self.bookToBeProcessed = [notification.userInfo objectForKey:@"ID"]; - [self.navigationController popToRootViewControllerAnimated:YES]; -} -- (void)handleBookToBeProcessed -{ - for (IssueViewController *issueViewController in self.issueViewControllers) { - if ([issueViewController.issue.ID isEqualToString:self.bookToBeProcessed]) { - [issueViewController actionButtonPressed:nil]; - break; - } - } - - self.bookToBeProcessed = nil; -} -- (void)pushViewControllerWithBook:(BakerBook *)book -{ - BakerViewController *bakerViewController = [[BakerViewController alloc] initWithBook:book]; - [self.navigationController pushViewController:bakerViewController animated:YES]; - [bakerViewController release]; -} - -#pragma mark - Buttons management - --(void)setrefreshButtonEnabled:(BOOL)enabled { - self.refreshButton.enabled = enabled; -} - --(void)setSubscribeButtonEnabled:(BOOL)enabled { - self.subscribeButton.enabled = enabled; - if (enabled) { - self.subscribeButton.title = NSLocalizedString(@"SUBSCRIBE_BUTTON_TEXT", nil); - } else { - self.subscribeButton.title = NSLocalizedString(@"SUBSCRIBE_BUTTON_DISABLED_TEXT", nil); - } -} - -- (void)webViewDidFinishLoad:(UIWebView *)webView { - // Inject user_id - [Utils webView:webView dispatchHTMLEvent:@"init" withParams:[NSDictionary dictionaryWithObjectsAndKeys: - [BakerAPI UUID], @"user_id", - [Utils appID], @"app_id", - nil]]; -} - -- (void)handleInfoButtonPressed:(id)sender { - - // If the button is pressed when the info box is open, close it - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) - { - if ([infoPopover isPopoverVisible]) - { - [infoPopover dismissPopoverAnimated:YES]; - return; - } - } - - // Prepare new view - UIViewController *popoverContent = [[UIViewController alloc] init]; - UIWebView *popoverView = [[UIWebView alloc] init]; - popoverView.backgroundColor = [UIColor blackColor]; - popoverView.delegate = self; - popoverContent.view = popoverView; - - // Load HTML file - NSString *path = [[NSBundle mainBundle] pathForResource:@"info" ofType:@"html" inDirectory:@"info"]; - [popoverView loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:path]]]; - - // Open view - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) - { - // On iPad use the UIPopoverController - infoPopover = [[UIPopoverController alloc] initWithContentViewController:popoverContent]; - [infoPopover presentPopoverFromBarButtonItem:sender - permittedArrowDirections:UIPopoverArrowDirectionUp - animated:YES]; - } - else { - // On iPhone push the view controller to the navigation - [self.navigationController pushViewController:popoverContent animated:YES]; - } - - [popoverView release]; - [popoverContent release]; -} - -#pragma mark - Helper methods - -- (void)addPurchaseObserver:(SEL)notificationSelector name:(NSString *)notificationName { - #ifdef BAKER_NEWSSTAND - [[NSNotificationCenter defaultCenter] addObserver:self - selector:notificationSelector - name:notificationName - object:purchasesManager]; - #endif -} - -+ (int)getBannerHeight -{ - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { - return 240; - } else { - return 104; - } -} - -@end diff --git a/BakerShelf/UIConstants.h b/BakerShelf/UIConstants.h deleted file mode 100644 index 059c651d..00000000 --- a/BakerShelf/UIConstants.h +++ /dev/null @@ -1,70 +0,0 @@ -// -// UIConstants.h -// Baker -// -// ========================================================================================== -// -// Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi -// 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 Baker Framework nor the names of its contributors may be used to -// endorse or promote products derived from this software without specific prior written -// permission. -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT -// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// - - -#ifndef Baker_UIConstants_h -#define Baker_UIConstants_h - - // Background color for issues cover (before downloading the actual cover) - #define ISSUES_COVER_BACKGROUND_COLOR @"#ffffff" - - // Title for issues in the shelf - // #define ISSUES_TITLE_FONT @"Helvetica" - // #define ISSUES_TITLE_FONT_SIZE 15 - #define ISSUES_TITLE_COLOR @"#000000" - - // Info text for issues in the shelf - // #define ISSUES_INFO_FONT @"Helvetica" - // #define ISSUES_INFO_FONT_SIZE 15 - #define ISSUES_INFO_COLOR @"#929292" - - #define ISSUES_PRICE_COLOR @"#b72529" - - // Download/read button for issues in the shelf - // #define ISSUES_ACTION_BUTTON_FONT @"Helvetica-Bold" - // #define ISSUES_ACTION_BUTTON_FONT_SIZE 11 - #define ISSUES_ACTION_BUTTON_BACKGROUND_COLOR @"#b72529" - #define ISSUES_ACTION_BUTTON_COLOR @"#ffffff" - - // Archive button for issues in the shelf - // #define ISSUES_ARCHIVE_BUTTON_FONT @"Helvetica-Bold" - // #define ISSUES_ARCHIVE_BUTTON_FONT_SIZE 11 - #define ISSUES_ARCHIVE_BUTTON_COLOR @"#b72529" - #define ISSUES_ARCHIVE_BUTTON_BACKGROUND_COLOR @"#ffffff" - - // Text and spinner for issues that are being loaded in the shelf - #define ISSUES_LOADING_LABEL_COLOR @"#b72529" - #define ISSUES_LOADING_SPINNER_COLOR @"#929292" - - // Progress bar for issues that are being downloaded in the shelf - #define ISSUES_PROGRESSBAR_TINT_COLOR @"#b72529" - -#endif diff --git a/BakerShelf/UICustomNavigationController.m b/BakerShelf/UICustomNavigationController.m deleted file mode 100644 index eed807f7..00000000 --- a/BakerShelf/UICustomNavigationController.m +++ /dev/null @@ -1,70 +0,0 @@ -// -// UICustomNavigationController.m -// Baker -// -// ========================================================================================== -// -// Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi -// 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 Baker Framework nor the names of its contributors may be used to -// endorse or promote products derived from this software without specific prior written -// permission. -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT -// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// - -#import "UICustomNavigationController.h" -#import "UICustomNavigationBar.h" - -@implementation UICustomNavigationController - -- (id)init -{ - self = [super init]; - return [[self updateNavigationBar] retain]; -} -- (id)updateNavigationBar -{ - [self navigationBar]; - - NSMutableData *data = [NSMutableData data]; - - NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; - [archiver encodeObject:self forKey:@"self"]; - [archiver finishEncoding]; - [archiver release]; - - NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; - [unarchiver setClass:[UICustomNavigationBar class] forClassName:@"UINavigationBar"]; - self = [unarchiver decodeObjectForKey:@"self"]; - [unarchiver finishDecoding]; - [unarchiver release]; - - return self; -} -- (NSUInteger)supportedInterfaceOrientations -{ - return [self.topViewController supportedInterfaceOrientations]; -} -- (BOOL)shouldAutorotate -{ - return [self.topViewController shouldAutorotate]; -} - -@end \ No newline at end of file diff --git a/BakerShelf/info/made-with-baker.png b/BakerShelf/info/made-with-baker.png index 4377dac6..003a769d 100644 Binary files a/BakerShelf/info/made-with-baker.png and b/BakerShelf/info/made-with-baker.png differ diff --git a/BakerShelf/info/style.css b/BakerShelf/info/style.css index b4082554..660ec4f0 100644 --- a/BakerShelf/info/style.css +++ b/BakerShelf/info/style.css @@ -43,7 +43,7 @@ strong { } h1 { - color: #b50303; + color: #bc242a; font-size: 25px; padding: 0; margin: 15px 0 15px 0; @@ -88,7 +88,7 @@ h2 { */ .button { display: inline-block; - background: #b50303; + background: #bc242a; color: #ffffff; padding: 8px 14px; text-decoration: none; diff --git a/BakerShelf/lib/Reachability.h b/BakerShelf/lib/BKRReachability.h similarity index 71% rename from BakerShelf/lib/Reachability.h rename to BakerShelf/lib/BKRReachability.h index abf37434..2b9dafb1 100644 --- a/BakerShelf/lib/Reachability.h +++ b/BakerShelf/lib/BKRReachability.h @@ -58,12 +58,12 @@ typedef enum ReachableViaWWAN = 1 } NetworkStatus; -@class Reachability; +@class BKRReachability; -typedef void (^NetworkReachable)(Reachability * reachability); -typedef void (^NetworkUnreachable)(Reachability * reachability); +typedef void (^NetworkReachable)(BKRReachability * reachability); +typedef void (^NetworkUnreachable)(BKRReachability * reachability); -@interface Reachability : NSObject +@interface BKRReachability : NSObject @property (nonatomic, copy) NetworkReachable reachableBlock; @property (nonatomic, copy) NetworkUnreachable unreachableBlock; @@ -71,32 +71,32 @@ typedef void (^NetworkUnreachable)(Reachability * reachability); @property (nonatomic, assign) BOOL reachableOnWWAN; -+(Reachability*)reachabilityWithHostname:(NSString*)hostname; -+(Reachability*)reachabilityForInternetConnection; -+(Reachability*)reachabilityWithAddress:(const struct sockaddr_in*)hostAddress; -+(Reachability*)reachabilityForLocalWiFi; ++ (BKRReachability*)reachabilityWithHostname:(NSString*)hostname; ++ (BKRReachability*)reachabilityForInternetConnection; ++ (BKRReachability*)reachabilityWithAddress:(const struct sockaddr_in*)hostAddress; ++ (BKRReachability*)reachabilityForLocalWiFi; --(Reachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref; +- (BKRReachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref; --(BOOL)startNotifier; --(void)stopNotifier; +- (BOOL)startNotifier; +- (void)stopNotifier; --(BOOL)isReachable; --(BOOL)isReachableViaWWAN; --(BOOL)isReachableViaWiFi; +- (BOOL)isReachable; +- (BOOL)isReachableViaWWAN; +- (BOOL)isReachableViaWiFi; // WWAN may be available, but not active until a connection has been established. // WiFi may require a connection for VPN on Demand. --(BOOL)isConnectionRequired; // Identical DDG variant. --(BOOL)connectionRequired; // Apple's routine. +- (BOOL)isConnectionRequired; // Identical DDG variant. +- (BOOL)connectionRequired; // Apple's routine. // Dynamic, on demand connection? --(BOOL)isConnectionOnDemand; +- (BOOL)isConnectionOnDemand; // Is user intervention required? --(BOOL)isInterventionRequired; +- (BOOL)isInterventionRequired; --(NetworkStatus)currentReachabilityStatus; --(SCNetworkReachabilityFlags)reachabilityFlags; --(NSString*)currentReachabilityString; --(NSString*)currentReachabilityFlags; +- (NetworkStatus)currentReachabilityStatus; +- (SCNetworkReachabilityFlags)reachabilityFlags; +- (NSString*)currentReachabilityString; +- (NSString*)currentReachabilityFlags; @end diff --git a/BakerShelf/lib/Reachability.m b/BakerShelf/lib/BKRReachability.m similarity index 91% rename from BakerShelf/lib/Reachability.m rename to BakerShelf/lib/BKRReachability.m index b178f841..f4d88499 100644 --- a/BakerShelf/lib/Reachability.m +++ b/BakerShelf/lib/BKRReachability.m @@ -25,12 +25,12 @@ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF POSSIBILITY OF SUCH DAMAGE. */ -#import "Reachability.h" +#import "BKRReachability.h" NSString *const kReachabilityChangedNotification = @"kReachabilityChangedNotification"; -@interface Reachability () +@interface BKRReachability () @property (nonatomic, assign) SCNetworkReachabilityRef reachabilityRef; @@ -44,8 +44,8 @@ @interface Reachability () @property (nonatomic, strong) id reachabilityObject; --(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags; --(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags; +- (void)reachabilityChanged:(SCNetworkReachabilityFlags)flags; +- (BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags; @end @@ -72,9 +72,9 @@ static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkRea { #pragma unused (target) #if __has_feature(objc_arc) - Reachability *reachability = ((__bridge Reachability*)info); + BKRReachability *reachability = ((__bridge BKRReachability*)info); #else - Reachability *reachability = ((Reachability*)info); + BKRReachability *reachability = ((BKRReachability*)info); #endif // we probably dont need an autoreleasepool here as GCD docs state each queue has its own autorelease pool @@ -86,7 +86,7 @@ static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkRea } -@implementation Reachability +@implementation BKRReachability @synthesize reachabilityRef; @synthesize reachabilitySerialQueue; @@ -99,7 +99,7 @@ @implementation Reachability @synthesize reachabilityObject; #pragma mark - class constructor methods -+(Reachability*)reachabilityWithHostname:(NSString*)hostname ++ (BKRReachability*)reachabilityWithHostname:(NSString*)hostname { SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostname UTF8String]); if (ref) @@ -117,7 +117,7 @@ +(Reachability*)reachabilityWithHostname:(NSString*)hostname return nil; } -+(Reachability *)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress ++ (BKRReachability *)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress { SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress); if (ref) @@ -134,7 +134,7 @@ +(Reachability *)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress return nil; } -+(Reachability *)reachabilityForInternetConnection ++ (BKRReachability *)reachabilityForInternetConnection { struct sockaddr_in zeroAddress; bzero(&zeroAddress, sizeof(zeroAddress)); @@ -144,7 +144,7 @@ +(Reachability *)reachabilityForInternetConnection return [self reachabilityWithAddress:&zeroAddress]; } -+(Reachability*)reachabilityForLocalWiFi ++ (BKRReachability*)reachabilityForLocalWiFi { struct sockaddr_in localWifiAddress; bzero(&localWifiAddress, sizeof(localWifiAddress)); @@ -159,7 +159,7 @@ +(Reachability*)reachabilityForLocalWiFi // initialization methods --(Reachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref +- (BKRReachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref { self = [super init]; if (self != nil) @@ -171,7 +171,7 @@ -(Reachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref return self; } --(void)dealloc +- (void)dealloc { [self stopNotifier]; @@ -181,8 +181,6 @@ -(void)dealloc self.reachabilityRef = nil; } - self.reachableBlock = nil; - self.unreachableBlock = nil; #if !(__has_feature(objc_arc)) [super dealloc]; @@ -198,7 +196,7 @@ -(void)dealloc // - In other words DO NOT DO ANY UI UPDATES IN THE BLOCKS. // INSTEAD USE dispatch_async(dispatch_get_main_queue(), ^{UISTUFF}) (or dispatch_sync if you want) --(BOOL)startNotifier +- (BOOL)startNotifier { SCNetworkReachabilityContext context = { 0, NULL, NULL, NULL, NULL }; @@ -271,7 +269,7 @@ -(BOOL)startNotifier return YES; } --(void)stopNotifier +- (void)stopNotifier { // first stop any callbacks! SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL); @@ -302,7 +300,7 @@ -(void)stopNotifier #define testcase (kSCNetworkReachabilityFlagsConnectionRequired | kSCNetworkReachabilityFlagsTransientConnection) --(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags +- (BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags { BOOL connectionUP = YES; @@ -327,7 +325,7 @@ -(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags return connectionUP; } --(BOOL)isReachable +- (BOOL)isReachable { SCNetworkReachabilityFlags flags; @@ -337,7 +335,7 @@ -(BOOL)isReachable return [self isReachableWithFlags:flags]; } --(BOOL)isReachableViaWWAN +- (BOOL)isReachableViaWWAN { #if TARGET_OS_IPHONE @@ -360,7 +358,7 @@ -(BOOL)isReachableViaWWAN return NO; } --(BOOL)isReachableViaWiFi +- (BOOL)isReachableViaWiFi { SCNetworkReachabilityFlags flags = 0; @@ -386,12 +384,12 @@ -(BOOL)isReachableViaWiFi // WWAN may be available, but not active until a connection has been established. // WiFi may require a connection for VPN on Demand. --(BOOL)isConnectionRequired +- (BOOL)isConnectionRequired { return [self connectionRequired]; } --(BOOL)connectionRequired +- (BOOL)connectionRequired { SCNetworkReachabilityFlags flags; @@ -404,7 +402,7 @@ -(BOOL)connectionRequired } // Dynamic, on demand connection? --(BOOL)isConnectionOnDemand +- (BOOL)isConnectionOnDemand { SCNetworkReachabilityFlags flags; @@ -418,7 +416,7 @@ -(BOOL)isConnectionOnDemand } // Is user intervention required? --(BOOL)isInterventionRequired +- (BOOL)isInterventionRequired { SCNetworkReachabilityFlags flags; @@ -434,7 +432,7 @@ -(BOOL)isInterventionRequired #pragma mark - reachability status stuff --(NetworkStatus)currentReachabilityStatus +- (NetworkStatus)currentReachabilityStatus { if([self isReachable]) { @@ -449,7 +447,7 @@ -(NetworkStatus)currentReachabilityStatus return NotReachable; } --(SCNetworkReachabilityFlags)reachabilityFlags +- (SCNetworkReachabilityFlags)reachabilityFlags { SCNetworkReachabilityFlags flags = 0; @@ -461,7 +459,7 @@ -(SCNetworkReachabilityFlags)reachabilityFlags return 0; } --(NSString*)currentReachabilityString +- (NSString*)currentReachabilityString { NetworkStatus temp = [self currentReachabilityStatus]; @@ -478,14 +476,14 @@ -(NSString*)currentReachabilityString return NSLocalizedString(@"No Connection", @""); } --(NSString*)currentReachabilityFlags +- (NSString*)currentReachabilityFlags { return reachabilityFlags([self reachabilityFlags]); } #pragma mark - callback function calls this method --(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags +- (void)reachabilityChanged:(SCNetworkReachabilityFlags)flags { if([self isReachableWithFlags:flags]) { diff --git a/BakerShelf/lib/BKRZipArchive.h b/BakerShelf/lib/BKRZipArchive.h new file mode 100755 index 00000000..1f5843aa --- /dev/null +++ b/BakerShelf/lib/BKRZipArchive.h @@ -0,0 +1,51 @@ +// +// SSZipArchive.h +// SSZipArchive +// +// Created by Sam Soffes on 7/21/10. +// Copyright (c) Sam Soffes 2010-2013. All rights reserved. +// + +#ifndef _BKRZIPARCHIVE_H +#define _BKRZIPARCHIVE_H + +#import +#include "minizip/unzip.h" + +@protocol BKRZipArchiveDelegate; + +@interface BKRZipArchive : NSObject + +// Unzip ++ (BOOL)unzipFileAtPath:(NSString*)path toDestination:(NSString*)destination; ++ (BOOL)unzipFileAtPath:(NSString*)path toDestination:(NSString*)destination overwrite:(BOOL)overwrite password:(NSString*)password error:(NSError **)error; + ++ (BOOL)unzipFileAtPath:(NSString*)path toDestination:(NSString*)destination delegate:(id)delegate; ++ (BOOL)unzipFileAtPath:(NSString*)path toDestination:(NSString*)destination overwrite:(BOOL)overwrite password:(NSString*)password error:(NSError **)error delegate:(id)delegate; + +// Zip ++ (BOOL)createZipFileAtPath:(NSString*)path withFilesAtPaths:(NSArray*)filenames; ++ (BOOL)createZipFileAtPath:(NSString*)path withContentsOfDirectory:(NSString*)directoryPath; + +- (id)initWithPath:(NSString*)path; +- (BOOL)open; +- (BOOL)writeFile:(NSString*)path; +- (BOOL)writeData:(NSData*)data filename:(NSString*)filename; +- (BOOL)close; + +@end + + +@protocol BKRZipArchiveDelegate + +@optional + +- (void)zipArchiveWillUnzipArchiveAtPath:(NSString*)path zipInfo:(unz_global_info)zipInfo; +- (void)zipArchiveDidUnzipArchiveAtPath:(NSString*)path zipInfo:(unz_global_info)zipInfo unzippedPath:(NSString*)unzippedPath; + +- (void)zipArchiveWillUnzipFileAtIndex:(NSInteger)fileIndex totalFiles:(NSInteger)totalFiles archivePath:(NSString*)archivePath fileInfo:(unz_file_info)fileInfo; +- (void)zipArchiveDidUnzipFileAtIndex:(NSInteger)fileIndex totalFiles:(NSInteger)totalFiles archivePath:(NSString*)archivePath fileInfo:(unz_file_info)fileInfo; + +@end + +#endif /* _BKRZIPARCHIVE_H */ diff --git a/BakerView/lib/SSZipArchive.m b/BakerShelf/lib/BKRZipArchive.m similarity index 91% rename from BakerView/lib/SSZipArchive.m rename to BakerShelf/lib/BKRZipArchive.m index b6e81ea4..37d0696d 100755 --- a/BakerView/lib/SSZipArchive.m +++ b/BakerShelf/lib/BKRZipArchive.m @@ -6,7 +6,7 @@ // Copyright (c) Sam Soffes 2010-2013. All rights reserved. // -#import "SSZipArchive.h" +#import "BKRZipArchive.h" #include "minizip/zip.h" #import "zlib.h" #import "zconf.h" @@ -15,12 +15,12 @@ #define CHUNK 16384 -@interface SSZipArchive () -+ (NSDate *)_dateWithMSDOSFormat:(UInt32)msdosDateTime; +@interface BKRZipArchive () ++ (NSDate*)_dateWithMSDOSFormat:(UInt32)msdosDateTime; @end -@implementation SSZipArchive { +@implementation BKRZipArchive { NSString *_path; NSString *_filename; zipFile _zip; @@ -29,22 +29,22 @@ @implementation SSZipArchive { #pragma mark - Unzipping -+ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination { ++ (BOOL)unzipFileAtPath:(NSString*)path toDestination:(NSString*)destination { return [self unzipFileAtPath:path toDestination:destination delegate:nil]; } -+ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination overwrite:(BOOL)overwrite password:(NSString *)password error:(NSError **)error { ++ (BOOL)unzipFileAtPath:(NSString*)path toDestination:(NSString*)destination overwrite:(BOOL)overwrite password:(NSString*)password error:(NSError **)error { return [self unzipFileAtPath:path toDestination:destination overwrite:overwrite password:password error:error delegate:nil]; } -+ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination delegate:(id)delegate { ++ (BOOL)unzipFileAtPath:(NSString*)path toDestination:(NSString*)destination delegate:(id)delegate { return [self unzipFileAtPath:path toDestination:destination overwrite:YES password:nil error:nil delegate:delegate]; } -+ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination overwrite:(BOOL)overwrite password:(NSString *)password error:(NSError **)error delegate:(id)delegate { ++ (BOOL)unzipFileAtPath:(NSString*)path toDestination:(NSString*)destination overwrite:(BOOL)overwrite password:(NSString*)password error:(NSError **)error delegate:(id)delegate { // Begin opening zipFile zip = unzOpen((const char*)[path UTF8String]); if (zip == NULL) { @@ -109,7 +109,7 @@ + (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination o archivePath:path fileInfo:fileInfo]; } - char *filename = (char *)malloc(fileInfo.size_filename + 1); + char *filename = (char*)malloc(fileInfo.size_filename + 1); unzGetCurrentFileInfo(zip, &fileInfo, filename, fileInfo.size_filename + 1, NULL, 0, NULL, 0); filename[fileInfo.size_filename] = '\0'; @@ -296,9 +296,9 @@ + (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination o #pragma mark - Zipping -+ (BOOL)createZipFileAtPath:(NSString *)path withFilesAtPaths:(NSArray *)paths { ++ (BOOL)createZipFileAtPath:(NSString*)path withFilesAtPaths:(NSArray*)paths { BOOL success = NO; - SSZipArchive *zipArchive = [[SSZipArchive alloc] initWithPath:path]; + BKRZipArchive *zipArchive = [[BKRZipArchive alloc] initWithPath:path]; if ([zipArchive open]) { for (NSString *path in paths) { [zipArchive writeFile:path]; @@ -314,11 +314,11 @@ + (BOOL)createZipFileAtPath:(NSString *)path withFilesAtPaths:(NSArray *)paths { } -+ (BOOL)createZipFileAtPath:(NSString *)path withContentsOfDirectory:(NSString *)directoryPath { ++ (BOOL)createZipFileAtPath:(NSString*)path withContentsOfDirectory:(NSString*)directoryPath { BOOL success = NO; NSFileManager *fileManager = nil; - SSZipArchive *zipArchive = [[SSZipArchive alloc] initWithPath:path]; + BKRZipArchive *zipArchive = [[BKRZipArchive alloc] initWithPath:path]; if ([zipArchive open]) { // use a local filemanager (queue/thread compatibility) @@ -346,7 +346,7 @@ + (BOOL)createZipFileAtPath:(NSString *)path withContentsOfDirectory:(NSString * } -- (id)initWithPath:(NSString *)path { +- (id)initWithPath:(NSString*)path { if ((self = [super init])) { _path = [path copy]; } @@ -382,7 +382,7 @@ - (void)zipInfo:(zip_fileinfo*)zipInfo setDate:(NSDate*)date { } -- (BOOL)writeFile:(NSString *)path +- (BOOL)writeFile:(NSString*)path { return [self writeFileAtPath:path withFileName:nil]; } @@ -390,7 +390,7 @@ - (BOOL)writeFile:(NSString *)path // supports writing files with logical folder/directory structure // *path* is the absolute path of the file that will be compressed // *fileName* is the relative name of the file how it is stored within the zip e.g. /folder/subfolder/text1.txt -- (BOOL)writeFileAtPath:(NSString *)path withFileName:(NSString *)fileName { +- (BOOL)writeFileAtPath:(NSString*)path withFileName:(NSString*)fileName { NSAssert((_zip != NULL), @"Attempting to write to an archive which was never opened"); FILE *input = fopen([path UTF8String], "r"); @@ -411,7 +411,7 @@ - (BOOL)writeFileAtPath:(NSString *)path withFileName:(NSString *)fileName { NSDictionary *attr = [[NSFileManager defaultManager] attributesOfItemAtPath:path error: nil]; if( attr ) { - NSDate *fileDate = (NSDate *)[attr objectForKey:NSFileModificationDate]; + NSDate *fileDate = (NSDate*)[attr objectForKey:NSFileModificationDate]; if( fileDate ) { [self zipInfo:&zipInfo setDate: fileDate ]; @@ -419,7 +419,7 @@ - (BOOL)writeFileAtPath:(NSString *)path withFileName:(NSString *)fileName { // Write permissions into the external attributes, for details on this see here: http://unix.stackexchange.com/a/14727 // Get the permissions value from the files attributes - NSNumber *permissionsValue = (NSNumber *)[attr objectForKey:NSFilePosixPermissions]; + NSNumber *permissionsValue = (NSNumber*)[attr objectForKey:NSFilePosixPermissions]; if (permissionsValue) { // Get the short value for the permissions short permissionsShort = permissionsValue.shortValue; @@ -452,7 +452,7 @@ - (BOOL)writeFileAtPath:(NSString *)path withFileName:(NSString *)fileName { } -- (BOOL)writeData:(NSData *)data filename:(NSString *)filename { +- (BOOL)writeData:(NSData*)data filename:(NSString*)filename { if (!_zip) { return NO; } @@ -487,7 +487,7 @@ - (BOOL)close { // // 3658 = 0011 0110 0101 1000 = 0011011 0010 11000 = 27 2 24 = 2007-02-24 // 7423 = 0111 0100 0010 0011 - 01110 100001 00011 = 14 33 2 = 14:33:06 -+ (NSDate *)_dateWithMSDOSFormat:(UInt32)msdosDateTime { ++ (NSDate*)_dateWithMSDOSFormat:(UInt32)msdosDateTime { static const UInt32 kYearMask = 0xFE000000; static const UInt32 kMonthMask = 0x1E00000; static const UInt32 kDayMask = 0x1F0000; diff --git a/BakerShelf/lib/MBProgressHUD.h b/BakerShelf/lib/MBProgressHUD.h new file mode 100644 index 00000000..1caa3ca4 --- /dev/null +++ b/BakerShelf/lib/MBProgressHUD.h @@ -0,0 +1,515 @@ +// +// MBProgressHUD.h +// Version 0.9 +// Created by Matej Bukovinski on 2.4.09. +// + +// This code is distributed under the terms and conditions of the MIT license. + +// Copyright (c) 2013 Matej Bukovinski +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import +#import + +@protocol MBProgressHUDDelegate; + + +typedef enum { + /** Progress is shown using an UIActivityIndicatorView. This is the default. */ + MBProgressHUDModeIndeterminate, + /** Progress is shown using a round, pie-chart like, progress view. */ + MBProgressHUDModeDeterminate, + /** Progress is shown using a horizontal progress bar */ + MBProgressHUDModeDeterminateHorizontalBar, + /** Progress is shown using a ring-shaped progress view. */ + MBProgressHUDModeAnnularDeterminate, + /** Shows a custom view */ + MBProgressHUDModeCustomView, + /** Shows only labels */ + MBProgressHUDModeText +} MBProgressHUDMode; + +typedef enum { + /** Opacity animation */ + MBProgressHUDAnimationFade, + /** Opacity + scale animation */ + MBProgressHUDAnimationZoom, + MBProgressHUDAnimationZoomOut = MBProgressHUDAnimationZoom, + MBProgressHUDAnimationZoomIn +} MBProgressHUDAnimation; + + +#ifndef MB_INSTANCETYPE +#if __has_feature(objc_instancetype) + #define MB_INSTANCETYPE instancetype +#else + #define MB_INSTANCETYPE id +#endif +#endif + +#ifndef MB_STRONG +#if __has_feature(objc_arc) + #define MB_STRONG strong +#else + #define MB_STRONG retain +#endif +#endif + +#ifndef MB_WEAK +#if __has_feature(objc_arc_weak) + #define MB_WEAK weak +#elif __has_feature(objc_arc) + #define MB_WEAK unsafe_unretained +#else + #define MB_WEAK assign +#endif +#endif + +#if NS_BLOCKS_AVAILABLE +typedef void (^MBProgressHUDCompletionBlock)(); +#endif + + +/** + * Displays a simple HUD window containing a progress indicator and two optional labels for short messages. + * + * This is a simple drop-in class for displaying a progress HUD view similar to Apple's private UIProgressHUD class. + * The MBProgressHUD window spans over the entire space given to it by the initWithFrame constructor and catches all + * user input on this region, thereby preventing the user operations on components below the view. The HUD itself is + * drawn centered as a rounded semi-transparent view which resizes depending on the user specified content. + * + * This view supports four modes of operation: + * - MBProgressHUDModeIndeterminate - shows a UIActivityIndicatorView + * - MBProgressHUDModeDeterminate - shows a custom round progress indicator + * - MBProgressHUDModeAnnularDeterminate - shows a custom annular progress indicator + * - MBProgressHUDModeCustomView - shows an arbitrary, user specified view (@see customView) + * + * All three modes can have optional labels assigned: + * - If the labelText property is set and non-empty then a label containing the provided content is placed below the + * indicator view. + * - If also the detailsLabelText property is set then another label is placed below the first label. + */ +@interface MBProgressHUD : UIView + +/** + * Creates a new HUD, adds it to provided view and shows it. The counterpart to this method is hideHUDForView:animated:. + * + * @param view The view that the HUD will be added to + * @param animated If set to YES the HUD will appear using the current animationType. If set to NO the HUD will not use + * animations while appearing. + * @return A reference to the created HUD. + * + * @see hideHUDForView:animated: + * @see animationType + */ ++ (MB_INSTANCETYPE)showHUDAddedTo:(UIView *)view animated:(BOOL)animated; + +/** + * Finds the top-most HUD subview and hides it. The counterpart to this method is showHUDAddedTo:animated:. + * + * @param view The view that is going to be searched for a HUD subview. + * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use + * animations while disappearing. + * @return YES if a HUD was found and removed, NO otherwise. + * + * @see showHUDAddedTo:animated: + * @see animationType + */ ++ (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated; + +/** + * Finds all the HUD subviews and hides them. + * + * @param view The view that is going to be searched for HUD subviews. + * @param animated If set to YES the HUDs will disappear using the current animationType. If set to NO the HUDs will not use + * animations while disappearing. + * @return the number of HUDs found and removed. + * + * @see hideHUDForView:animated: + * @see animationType + */ ++ (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated; + +/** + * Finds the top-most HUD subview and returns it. + * + * @param view The view that is going to be searched. + * @return A reference to the last HUD subview discovered. + */ ++ (MB_INSTANCETYPE)HUDForView:(UIView *)view; + +/** + * Finds all HUD subviews and returns them. + * + * @param view The view that is going to be searched. + * @return All found HUD views (array of MBProgressHUD objects). + */ ++ (NSArray *)allHUDsForView:(UIView *)view; + +/** + * A convenience constructor that initializes the HUD with the window's bounds. Calls the designated constructor with + * window.bounds as the parameter. + * + * @param window The window instance that will provide the bounds for the HUD. Should be the same instance as + * the HUD's superview (i.e., the window that the HUD will be added to). + */ +- (id)initWithWindow:(UIWindow *)window; + +/** + * A convenience constructor that initializes the HUD with the view's bounds. Calls the designated constructor with + * view.bounds as the parameter + * + * @param view The view instance that will provide the bounds for the HUD. Should be the same instance as + * the HUD's superview (i.e., the view that the HUD will be added to). + */ +- (id)initWithView:(UIView *)view; + +/** + * Display the HUD. You need to make sure that the main thread completes its run loop soon after this method call so + * the user interface can be updated. Call this method when your task is already set-up to be executed in a new thread + * (e.g., when using something like NSOperation or calling an asynchronous call like NSURLRequest). + * + * @param animated If set to YES the HUD will appear using the current animationType. If set to NO the HUD will not use + * animations while appearing. + * + * @see animationType + */ +- (void)show:(BOOL)animated; + +/** + * Hide the HUD. This still calls the hudWasHidden: delegate. This is the counterpart of the show: method. Use it to + * hide the HUD when your task completes. + * + * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use + * animations while disappearing. + * + * @see animationType + */ +- (void)hide:(BOOL)animated; + +/** + * Hide the HUD after a delay. This still calls the hudWasHidden: delegate. This is the counterpart of the show: method. Use it to + * hide the HUD when your task completes. + * + * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use + * animations while disappearing. + * @param delay Delay in seconds until the HUD is hidden. + * + * @see animationType + */ +- (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay; + +/** + * Shows the HUD while a background task is executing in a new thread, then hides the HUD. + * + * This method also takes care of autorelease pools so your method does not have to be concerned with setting up a + * pool. + * + * @param method The method to be executed while the HUD is shown. This method will be executed in a new thread. + * @param target The object that the target method belongs to. + * @param object An optional object to be passed to the method. + * @param animated If set to YES the HUD will (dis)appear using the current animationType. If set to NO the HUD will not use + * animations while (dis)appearing. + */ +- (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated; + +#if NS_BLOCKS_AVAILABLE + +/** + * Shows the HUD while a block is executing on a background queue, then hides the HUD. + * + * @see showAnimated:whileExecutingBlock:onQueue:completionBlock: + */ +- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block; + +/** + * Shows the HUD while a block is executing on a background queue, then hides the HUD. + * + * @see showAnimated:whileExecutingBlock:onQueue:completionBlock: + */ +- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(MBProgressHUDCompletionBlock)completion; + +/** + * Shows the HUD while a block is executing on the specified dispatch queue, then hides the HUD. + * + * @see showAnimated:whileExecutingBlock:onQueue:completionBlock: + */ +- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue; + +/** + * Shows the HUD while a block is executing on the specified dispatch queue, executes completion block on the main queue, and then hides the HUD. + * + * @param animated If set to YES the HUD will (dis)appear using the current animationType. If set to NO the HUD will + * not use animations while (dis)appearing. + * @param block The block to be executed while the HUD is shown. + * @param queue The dispatch queue on which the block should be executed. + * @param completion The block to be executed on completion. + * + * @see completionBlock + */ +- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue + completionBlock:(MBProgressHUDCompletionBlock)completion; + +/** + * A block that gets called after the HUD was completely hidden. + */ +@property (copy) MBProgressHUDCompletionBlock completionBlock; + +#endif + +/** + * MBProgressHUD operation mode. The default is MBProgressHUDModeIndeterminate. + * + * @see MBProgressHUDMode + */ +@property (assign) MBProgressHUDMode mode; + +/** + * The animation type that should be used when the HUD is shown and hidden. + * + * @see MBProgressHUDAnimation + */ +@property (assign) MBProgressHUDAnimation animationType; + +/** + * The UIView (e.g., a UIImageView) to be shown when the HUD is in MBProgressHUDModeCustomView. + * For best results use a 37 by 37 pixel view (so the bounds match the built in indicator bounds). + */ +@property (MB_STRONG) UIView *customView; + +/** + * The HUD delegate object. + * + * @see MBProgressHUDDelegate + */ +@property (MB_WEAK) id delegate; + +/** + * An optional short message to be displayed below the activity indicator. The HUD is automatically resized to fit + * the entire text. If the text is too long it will get clipped by displaying "..." at the end. If left unchanged or + * set to @"", then no message is displayed. + */ +@property (copy) NSString *labelText; + +/** + * An optional details message displayed below the labelText message. This message is displayed only if the labelText + * property is also set and is different from an empty string (@""). The details text can span multiple lines. + */ +@property (copy) NSString *detailsLabelText; + +/** + * The opacity of the HUD window. Defaults to 0.8 (80% opacity). + */ +@property (assign) float opacity; + +/** + * The color of the HUD window. Defaults to black. If this property is set, color is set using + * this UIColor and the opacity property is not used. using retain because performing copy on + * UIColor base colors (like [UIColor greenColor]) cause problems with the copyZone. + */ +@property (MB_STRONG) UIColor *color; + +/** + * The x-axis offset of the HUD relative to the centre of the superview. + */ +@property (assign) float xOffset; + +/** + * The y-axis offset of the HUD relative to the centre of the superview. + */ +@property (assign) float yOffset; + +/** + * The amount of space between the HUD edge and the HUD elements (labels, indicators or custom views). + * Defaults to 20.0 + */ +@property (assign) float margin; + +/** + * The corner radius for the HUD + * Defaults to 10.0 + */ +@property (assign) float cornerRadius; + +/** + * Cover the HUD background view with a radial gradient. + */ +@property (assign) BOOL dimBackground; + +/* + * Grace period is the time (in seconds) that the invoked method may be run without + * showing the HUD. If the task finishes before the grace time runs out, the HUD will + * not be shown at all. + * This may be used to prevent HUD display for very short tasks. + * Defaults to 0 (no grace time). + * Grace time functionality is only supported when the task status is known! + * @see taskInProgress + */ +@property (assign) float graceTime; + +/** + * The minimum time (in seconds) that the HUD is shown. + * This avoids the problem of the HUD being shown and than instantly hidden. + * Defaults to 0 (no minimum show time). + */ +@property (assign) float minShowTime; + +/** + * Indicates that the executed operation is in progress. Needed for correct graceTime operation. + * If you don't set a graceTime (different than 0.0) this does nothing. + * This property is automatically set when using showWhileExecuting:onTarget:withObject:animated:. + * When threading is done outside of the HUD (i.e., when the show: and hide: methods are used directly), + * you need to set this property when your task starts and completes in order to have normal graceTime + * functionality. + */ +@property (assign) BOOL taskInProgress; + +/** + * Removes the HUD from its parent view when hidden. + * Defaults to NO. + */ +@property (assign) BOOL removeFromSuperViewOnHide; + +/** + * Font to be used for the main label. Set this property if the default is not adequate. + */ +@property (MB_STRONG) UIFont* labelFont; + +/** + * Color to be used for the main label. Set this property if the default is not adequate. + */ +@property (MB_STRONG) UIColor* labelColor; + +/** + * Font to be used for the details label. Set this property if the default is not adequate. + */ +@property (MB_STRONG) UIFont* detailsLabelFont; + +/** + * Color to be used for the details label. Set this property if the default is not adequate. + */ +@property (MB_STRONG) UIColor* detailsLabelColor; + +/** + * The color of the activity indicator. Defaults to [UIColor whiteColor] + * Does nothing on pre iOS 5. + */ +@property (MB_STRONG) UIColor *activityIndicatorColor; + +/** + * The progress of the progress indicator, from 0.0 to 1.0. Defaults to 0.0. + */ +@property (assign) float progress; + +/** + * The minimum size of the HUD bezel. Defaults to CGSizeZero (no minimum size). + */ +@property (assign) CGSize minSize; + + +/** + * The actual size of the HUD bezel. + * You can use this to limit touch handling on the bezel aria only. + * @see https://github.com/jdg/MBProgressHUD/pull/200 + */ +@property (atomic, assign, readonly) CGSize size; + + +/** + * Force the HUD dimensions to be equal if possible. + */ +@property (assign, getter = isSquare) BOOL square; + +@end + + +@protocol MBProgressHUDDelegate + +@optional + +/** + * Called after the HUD was fully hidden from the screen. + */ +- (void)hudWasHidden:(MBProgressHUD *)hud; + +@end + + +/** + * A progress view for showing definite progress by filling up a circle (pie chart). + */ +@interface MBRoundProgressView : UIView + +/** + * Progress (0.0 to 1.0) + */ +@property (nonatomic, assign) float progress; + +/** + * Indicator progress color. + * Defaults to white [UIColor whiteColor] + */ +@property (nonatomic, MB_STRONG) UIColor *progressTintColor; + +/** + * Indicator background (non-progress) color. + * Defaults to translucent white (alpha 0.1) + */ +@property (nonatomic, MB_STRONG) UIColor *backgroundTintColor; + +/* + * Display mode - NO = round or YES = annular. Defaults to round. + */ +@property (nonatomic, assign, getter = isAnnular) BOOL annular; + +@end + + +/** + * A flat bar progress view. + */ +@interface MBBarProgressView : UIView + +/** + * Progress (0.0 to 1.0) + */ +@property (nonatomic, assign) float progress; + +/** + * Bar border line color. + * Defaults to white [UIColor whiteColor]. + */ +@property (nonatomic, MB_STRONG) UIColor *lineColor; + +/** + * Bar background color. + * Defaults to clear [UIColor clearColor]; + */ +@property (nonatomic, MB_STRONG) UIColor *progressRemainingColor; + +/** + * Bar progress color. + * Defaults to white [UIColor whiteColor]. + */ +@property (nonatomic, MB_STRONG) UIColor *progressColor; + +@end diff --git a/BakerShelf/lib/MBProgressHUD.m b/BakerShelf/lib/MBProgressHUD.m new file mode 100644 index 00000000..8e44ff8e --- /dev/null +++ b/BakerShelf/lib/MBProgressHUD.m @@ -0,0 +1,1024 @@ +// +// MBProgressHUD.m +// Version 0.9 +// Created by Matej Bukovinski on 2.4.09. +// + +#import "MBProgressHUD.h" +#import + + +#if __has_feature(objc_arc) + #define MB_AUTORELEASE(exp) exp + #define MB_RELEASE(exp) exp + #define MB_RETAIN(exp) exp +#else + #define MB_AUTORELEASE(exp) [exp autorelease] + #define MB_RELEASE(exp) [exp release] + #define MB_RETAIN(exp) [exp retain] +#endif + +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 + #define MBLabelAlignmentCenter NSTextAlignmentCenter +#else + #define MBLabelAlignmentCenter UITextAlignmentCenter +#endif + +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 + #define MB_TEXTSIZE(text, font) [text length] > 0 ? [text \ + sizeWithAttributes:@{NSFontAttributeName:font}] : CGSizeZero; +#else + #define MB_TEXTSIZE(text, font) [text length] > 0 ? [text sizeWithFont:font] : CGSizeZero; +#endif + +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 + #define MB_MULTILINE_TEXTSIZE(text, font, maxSize, mode) [text length] > 0 ? [text \ + boundingRectWithSize:maxSize options:(NSStringDrawingUsesLineFragmentOrigin) \ + attributes:@{NSFontAttributeName:font} context:nil].size : CGSizeZero; +#else + #define MB_MULTILINE_TEXTSIZE(text, font, maxSize, mode) [text length] > 0 ? [text \ + sizeWithFont:font constrainedToSize:maxSize lineBreakMode:mode] : CGSizeZero; +#endif + +#ifndef kCFCoreFoundationVersionNumber_iOS_7_0 + #define kCFCoreFoundationVersionNumber_iOS_7_0 847.20 +#endif + +#ifndef kCFCoreFoundationVersionNumber_iOS_8_0 + #define kCFCoreFoundationVersionNumber_iOS_8_0 1129.15 +#endif + + +static const CGFloat kPadding = 4.f; +static const CGFloat kLabelFontSize = 16.f; +static const CGFloat kDetailsLabelFontSize = 12.f; + + +@interface MBProgressHUD () { + BOOL useAnimation; + SEL methodForExecution; + id targetForExecution; + id objectForExecution; + UILabel *label; + UILabel *detailsLabel; + BOOL isFinished; + CGAffineTransform rotationTransform; +} + +@property (atomic, MB_STRONG) UIView *indicator; +@property (atomic, MB_STRONG) NSTimer *graceTimer; +@property (atomic, MB_STRONG) NSTimer *minShowTimer; +@property (atomic, MB_STRONG) NSDate *showStarted; + + +@end + + +@implementation MBProgressHUD + +#pragma mark - Properties + +@synthesize animationType; +@synthesize delegate; +@synthesize opacity; +@synthesize color; +@synthesize labelFont; +@synthesize labelColor; +@synthesize detailsLabelFont; +@synthesize detailsLabelColor; +@synthesize indicator; +@synthesize xOffset; +@synthesize yOffset; +@synthesize minSize; +@synthesize square; +@synthesize margin; +@synthesize dimBackground; +@synthesize graceTime; +@synthesize minShowTime; +@synthesize graceTimer; +@synthesize minShowTimer; +@synthesize taskInProgress; +@synthesize removeFromSuperViewOnHide; +@synthesize customView; +@synthesize showStarted; +@synthesize mode; +@synthesize labelText; +@synthesize detailsLabelText; +@synthesize progress; +@synthesize size; +@synthesize activityIndicatorColor; +#if NS_BLOCKS_AVAILABLE +@synthesize completionBlock; +#endif + +#pragma mark - Class methods + ++ (MB_INSTANCETYPE)showHUDAddedTo:(UIView *)view animated:(BOOL)animated { + MBProgressHUD *hud = [[self alloc] initWithView:view]; + hud.removeFromSuperViewOnHide = YES; + [view addSubview:hud]; + [hud show:animated]; + return MB_AUTORELEASE(hud); +} + ++ (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated { + MBProgressHUD *hud = [self HUDForView:view]; + if (hud != nil) { + hud.removeFromSuperViewOnHide = YES; + [hud hide:animated]; + return YES; + } + return NO; +} + ++ (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated { + NSArray *huds = [MBProgressHUD allHUDsForView:view]; + for (MBProgressHUD *hud in huds) { + hud.removeFromSuperViewOnHide = YES; + [hud hide:animated]; + } + return [huds count]; +} + ++ (MB_INSTANCETYPE)HUDForView:(UIView *)view { + NSEnumerator *subviewsEnum = [view.subviews reverseObjectEnumerator]; + for (UIView *subview in subviewsEnum) { + if ([subview isKindOfClass:self]) { + return (MBProgressHUD *)subview; + } + } + return nil; +} + ++ (NSArray *)allHUDsForView:(UIView *)view { + NSMutableArray *huds = [NSMutableArray array]; + NSArray *subviews = view.subviews; + for (UIView *aView in subviews) { + if ([aView isKindOfClass:self]) { + [huds addObject:aView]; + } + } + return [NSArray arrayWithArray:huds]; +} + +#pragma mark - Lifecycle + +- (id)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + // Set default values for properties + self.animationType = MBProgressHUDAnimationFade; + self.mode = MBProgressHUDModeIndeterminate; + self.labelText = nil; + self.detailsLabelText = nil; + self.opacity = 0.8f; + self.color = nil; + self.labelFont = [UIFont boldSystemFontOfSize:kLabelFontSize]; + self.labelColor = [UIColor whiteColor]; + self.detailsLabelFont = [UIFont boldSystemFontOfSize:kDetailsLabelFontSize]; + self.detailsLabelColor = [UIColor whiteColor]; + self.activityIndicatorColor = [UIColor whiteColor]; + self.xOffset = 0.0f; + self.yOffset = 0.0f; + self.dimBackground = NO; + self.margin = 20.0f; + self.cornerRadius = 10.0f; + self.graceTime = 0.0f; + self.minShowTime = 0.0f; + self.removeFromSuperViewOnHide = NO; + self.minSize = CGSizeZero; + self.square = NO; + self.contentMode = UIViewContentModeCenter; + self.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin + | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; + + // Transparent background + self.opaque = NO; + self.backgroundColor = [UIColor clearColor]; + // Make it invisible for now + self.alpha = 0.0f; + + taskInProgress = NO; + rotationTransform = CGAffineTransformIdentity; + + [self setupLabels]; + [self updateIndicators]; + [self registerForKVO]; + [self registerForNotifications]; + } + return self; +} + +- (id)initWithView:(UIView *)view { + NSAssert(view, @"View must not be nil."); + return [self initWithFrame:view.bounds]; +} + +- (id)initWithWindow:(UIWindow *)window { + return [self initWithView:window]; +} + +- (void)dealloc { + [self unregisterFromNotifications]; + [self unregisterFromKVO]; +#if !__has_feature(objc_arc) + [color release]; + [indicator release]; + [label release]; + [detailsLabel release]; + [labelText release]; + [detailsLabelText release]; + [graceTimer release]; + [minShowTimer release]; + [showStarted release]; + [customView release]; + [labelFont release]; + [labelColor release]; + [detailsLabelFont release]; + [detailsLabelColor release]; +#if NS_BLOCKS_AVAILABLE + [completionBlock release]; +#endif + [super dealloc]; +#endif +} + +#pragma mark - Show & hide + +- (void)show:(BOOL)animated { + useAnimation = animated; + // If the grace time is set postpone the HUD display + if (self.graceTime > 0.0) { + self.graceTimer = [NSTimer scheduledTimerWithTimeInterval:self.graceTime target:self + selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO]; + } + // ... otherwise show the HUD imediately + else { + [self setNeedsDisplay]; + [self showUsingAnimation:useAnimation]; + } +} + +- (void)hide:(BOOL)animated { + useAnimation = animated; + // If the minShow time is set, calculate how long the hud was shown, + // and pospone the hiding operation if necessary + if (self.minShowTime > 0.0 && showStarted) { + NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:showStarted]; + if (interv < self.minShowTime) { + self.minShowTimer = [NSTimer scheduledTimerWithTimeInterval:(self.minShowTime - interv) target:self + selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO]; + return; + } + } + // ... otherwise hide the HUD immediately + [self hideUsingAnimation:useAnimation]; +} + +- (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay { + [self performSelector:@selector(hideDelayed:) withObject:[NSNumber numberWithBool:animated] afterDelay:delay]; +} + +- (void)hideDelayed:(NSNumber *)animated { + [self hide:[animated boolValue]]; +} + +#pragma mark - Timer callbacks + +- (void)handleGraceTimer:(NSTimer *)theTimer { + // Show the HUD only if the task is still running + if (taskInProgress) { + [self setNeedsDisplay]; + [self showUsingAnimation:useAnimation]; + } +} + +- (void)handleMinShowTimer:(NSTimer *)theTimer { + [self hideUsingAnimation:useAnimation]; +} + +#pragma mark - View Hierrarchy + +- (BOOL)shouldPerformOrientationTransform { + BOOL isPreiOS8 = NSFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_8_0; + // prior to iOS8 code needs to take care of rotation if it is being added to the window + return isPreiOS8 && [self.superview isKindOfClass:[UIWindow class]]; +} + +- (void)didMoveToSuperview { + if ([self shouldPerformOrientationTransform]) { + [self setTransformForCurrentOrientation:NO]; + } +} + +#pragma mark - Internal show & hide operations + +- (void)showUsingAnimation:(BOOL)animated { + if (animated && animationType == MBProgressHUDAnimationZoomIn) { + self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(0.5f, 0.5f)); + } else if (animated && animationType == MBProgressHUDAnimationZoomOut) { + self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(1.5f, 1.5f)); + } + self.showStarted = [NSDate date]; + // Fade in + if (animated) { + [UIView beginAnimations:nil context:NULL]; + [UIView setAnimationDuration:0.30]; + self.alpha = 1.0f; + if (animationType == MBProgressHUDAnimationZoomIn || animationType == MBProgressHUDAnimationZoomOut) { + self.transform = rotationTransform; + } + [UIView commitAnimations]; + } + else { + self.alpha = 1.0f; + } +} + +- (void)hideUsingAnimation:(BOOL)animated { + // Fade out + if (animated && showStarted) { + [UIView beginAnimations:nil context:NULL]; + [UIView setAnimationDuration:0.30]; + [UIView setAnimationDelegate:self]; + [UIView setAnimationDidStopSelector:@selector(animationFinished:finished:context:)]; + // 0.02 prevents the hud from passing through touches during the animation the hud will get completely hidden + // in the done method + if (animationType == MBProgressHUDAnimationZoomIn) { + self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(1.5f, 1.5f)); + } else if (animationType == MBProgressHUDAnimationZoomOut) { + self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(0.5f, 0.5f)); + } + + self.alpha = 0.02f; + [UIView commitAnimations]; + } + else { + self.alpha = 0.0f; + [self done]; + } + self.showStarted = nil; +} + +- (void)animationFinished:(NSString *)animationID finished:(BOOL)finished context:(void*)context { + [self done]; +} + +- (void)done { + [NSObject cancelPreviousPerformRequestsWithTarget:self]; + isFinished = YES; + self.alpha = 0.0f; + if (removeFromSuperViewOnHide) { + [self removeFromSuperview]; + } +#if NS_BLOCKS_AVAILABLE + if (self.completionBlock) { + self.completionBlock(); + self.completionBlock = NULL; + } +#endif + if ([delegate respondsToSelector:@selector(hudWasHidden:)]) { + [delegate performSelector:@selector(hudWasHidden:) withObject:self]; + } +} + +#pragma mark - Threading + +- (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated { + methodForExecution = method; + targetForExecution = MB_RETAIN(target); + objectForExecution = MB_RETAIN(object); + // Launch execution in new thread + self.taskInProgress = YES; + [NSThread detachNewThreadSelector:@selector(launchExecution) toTarget:self withObject:nil]; + // Show HUD view + [self show:animated]; +} + +#if NS_BLOCKS_AVAILABLE + +- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block { + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL]; +} + +- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(void (^)())completion { + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:completion]; +} + +- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue { + [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL]; +} + +- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue + completionBlock:(MBProgressHUDCompletionBlock)completion { + self.taskInProgress = YES; + self.completionBlock = completion; + dispatch_async(queue, ^(void) { + block(); + dispatch_async(dispatch_get_main_queue(), ^(void) { + [self cleanUp]; + }); + }); + [self show:animated]; +} + +#endif + +- (void)launchExecution { + @autoreleasepool { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + // Start executing the requested task + [targetForExecution performSelector:methodForExecution withObject:objectForExecution]; +#pragma clang diagnostic pop + // Task completed, update view in main thread (note: view operations should + // be done only in the main thread) + [self performSelectorOnMainThread:@selector(cleanUp) withObject:nil waitUntilDone:NO]; + } +} + +- (void)cleanUp { + taskInProgress = NO; +#if !__has_feature(objc_arc) + [targetForExecution release]; + [objectForExecution release]; +#else + targetForExecution = nil; + objectForExecution = nil; +#endif + [self hide:useAnimation]; +} + +#pragma mark - UI + +- (void)setupLabels { + label = [[UILabel alloc] initWithFrame:self.bounds]; + label.adjustsFontSizeToFitWidth = NO; + label.textAlignment = MBLabelAlignmentCenter; + label.opaque = NO; + label.backgroundColor = [UIColor clearColor]; + label.textColor = self.labelColor; + label.font = self.labelFont; + label.text = self.labelText; + [self addSubview:label]; + + detailsLabel = [[UILabel alloc] initWithFrame:self.bounds]; + detailsLabel.font = self.detailsLabelFont; + detailsLabel.adjustsFontSizeToFitWidth = NO; + detailsLabel.textAlignment = MBLabelAlignmentCenter; + detailsLabel.opaque = NO; + detailsLabel.backgroundColor = [UIColor clearColor]; + detailsLabel.textColor = self.detailsLabelColor; + detailsLabel.numberOfLines = 0; + detailsLabel.font = self.detailsLabelFont; + detailsLabel.text = self.detailsLabelText; + [self addSubview:detailsLabel]; +} + +- (void)updateIndicators { + + BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]]; + BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]]; + + if (mode == MBProgressHUDModeIndeterminate) { + if (!isActivityIndicator) { + // Update to indeterminate indicator + [indicator removeFromSuperview]; + self.indicator = MB_AUTORELEASE([[UIActivityIndicatorView alloc] + initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]); + [(UIActivityIndicatorView *)indicator startAnimating]; + [self addSubview:indicator]; + } +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 50000 + [(UIActivityIndicatorView *)indicator setColor:self.activityIndicatorColor]; +#endif + } + else if (mode == MBProgressHUDModeDeterminateHorizontalBar) { + // Update to bar determinate indicator + [indicator removeFromSuperview]; + self.indicator = MB_AUTORELEASE([[MBBarProgressView alloc] init]); + [self addSubview:indicator]; + } + else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) { + if (!isRoundIndicator) { + // Update to determinante indicator + [indicator removeFromSuperview]; + self.indicator = MB_AUTORELEASE([[MBRoundProgressView alloc] init]); + [self addSubview:indicator]; + } + if (mode == MBProgressHUDModeAnnularDeterminate) { + [(MBRoundProgressView *)indicator setAnnular:YES]; + } + } + else if (mode == MBProgressHUDModeCustomView && customView != indicator) { + // Update custom view indicator + [indicator removeFromSuperview]; + self.indicator = customView; + [self addSubview:indicator]; + } else if (mode == MBProgressHUDModeText) { + [indicator removeFromSuperview]; + self.indicator = nil; + } +} + +#pragma mark - Layout + +- (void)layoutSubviews { + [super layoutSubviews]; + + // Entirely cover the parent view + UIView *parent = self.superview; + if (parent) { + self.frame = parent.bounds; + } + CGRect bounds = self.bounds; + + // Determine the total widt and height needed + CGFloat maxWidth = bounds.size.width - 4 * margin; + CGSize totalSize = CGSizeZero; + + CGRect indicatorF = indicator.bounds; + indicatorF.size.width = MIN(indicatorF.size.width, maxWidth); + totalSize.width = MAX(totalSize.width, indicatorF.size.width); + totalSize.height += indicatorF.size.height; + + CGSize labelSize = MB_TEXTSIZE(label.text, label.font); + labelSize.width = MIN(labelSize.width, maxWidth); + totalSize.width = MAX(totalSize.width, labelSize.width); + totalSize.height += labelSize.height; + if (labelSize.height > 0.f && indicatorF.size.height > 0.f) { + totalSize.height += kPadding; + } + + CGFloat remainingHeight = bounds.size.height - totalSize.height - kPadding - 4 * margin; + CGSize maxSize = CGSizeMake(maxWidth, remainingHeight); + CGSize detailsLabelSize = MB_MULTILINE_TEXTSIZE(detailsLabel.text, detailsLabel.font, maxSize, detailsLabel.lineBreakMode); + totalSize.width = MAX(totalSize.width, detailsLabelSize.width); + totalSize.height += detailsLabelSize.height; + if (detailsLabelSize.height > 0.f && (indicatorF.size.height > 0.f || labelSize.height > 0.f)) { + totalSize.height += kPadding; + } + + totalSize.width += 2 * margin; + totalSize.height += 2 * margin; + + // Position elements + CGFloat yPos = round(((bounds.size.height - totalSize.height) / 2)) + margin + yOffset; + CGFloat xPos = xOffset; + indicatorF.origin.y = yPos; + indicatorF.origin.x = round((bounds.size.width - indicatorF.size.width) / 2) + xPos; + indicator.frame = indicatorF; + yPos += indicatorF.size.height; + + if (labelSize.height > 0.f && indicatorF.size.height > 0.f) { + yPos += kPadding; + } + CGRect labelF; + labelF.origin.y = yPos; + labelF.origin.x = round((bounds.size.width - labelSize.width) / 2) + xPos; + labelF.size = labelSize; + label.frame = labelF; + yPos += labelF.size.height; + + if (detailsLabelSize.height > 0.f && (indicatorF.size.height > 0.f || labelSize.height > 0.f)) { + yPos += kPadding; + } + CGRect detailsLabelF; + detailsLabelF.origin.y = yPos; + detailsLabelF.origin.x = round((bounds.size.width - detailsLabelSize.width) / 2) + xPos; + detailsLabelF.size = detailsLabelSize; + detailsLabel.frame = detailsLabelF; + + // Enforce minsize and quare rules + if (square) { + CGFloat max = MAX(totalSize.width, totalSize.height); + if (max <= bounds.size.width - 2 * margin) { + totalSize.width = max; + } + if (max <= bounds.size.height - 2 * margin) { + totalSize.height = max; + } + } + if (totalSize.width < minSize.width) { + totalSize.width = minSize.width; + } + if (totalSize.height < minSize.height) { + totalSize.height = minSize.height; + } + + size = totalSize; +} + +#pragma mark BG Drawing + +- (void)drawRect:(CGRect)rect { + + CGContextRef context = UIGraphicsGetCurrentContext(); + UIGraphicsPushContext(context); + + if (self.dimBackground) { + //Gradient colours + size_t gradLocationsNum = 2; + CGFloat gradLocations[2] = {0.0f, 1.0f}; + CGFloat gradColors[8] = {0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.75f}; + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, gradColors, gradLocations, gradLocationsNum); + CGColorSpaceRelease(colorSpace); + //Gradient center + CGPoint gradCenter= CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); + //Gradient radius + float gradRadius = MIN(self.bounds.size.width , self.bounds.size.height) ; + //Gradient draw + CGContextDrawRadialGradient (context, gradient, gradCenter, + 0, gradCenter, gradRadius, + kCGGradientDrawsAfterEndLocation); + CGGradientRelease(gradient); + } + + // Set background rect color + if (self.color) { + CGContextSetFillColorWithColor(context, self.color.CGColor); + } else { + CGContextSetGrayFillColor(context, 0.0f, self.opacity); + } + + + // Center HUD + CGRect allRect = self.bounds; + // Draw rounded HUD backgroud rect + CGRect boxRect = CGRectMake(round((allRect.size.width - size.width) / 2) + self.xOffset, + round((allRect.size.height - size.height) / 2) + self.yOffset, size.width, size.height); + float radius = self.cornerRadius; + CGContextBeginPath(context); + CGContextMoveToPoint(context, CGRectGetMinX(boxRect) + radius, CGRectGetMinY(boxRect)); + CGContextAddArc(context, CGRectGetMaxX(boxRect) - radius, CGRectGetMinY(boxRect) + radius, radius, 3 * (float)M_PI / 2, 0, 0); + CGContextAddArc(context, CGRectGetMaxX(boxRect) - radius, CGRectGetMaxY(boxRect) - radius, radius, 0, (float)M_PI / 2, 0); + CGContextAddArc(context, CGRectGetMinX(boxRect) + radius, CGRectGetMaxY(boxRect) - radius, radius, (float)M_PI / 2, (float)M_PI, 0); + CGContextAddArc(context, CGRectGetMinX(boxRect) + radius, CGRectGetMinY(boxRect) + radius, radius, (float)M_PI, 3 * (float)M_PI / 2, 0); + CGContextClosePath(context); + CGContextFillPath(context); + + UIGraphicsPopContext(); +} + +#pragma mark - KVO + +- (void)registerForKVO { + for (NSString *keyPath in [self observableKeypaths]) { + [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL]; + } +} + +- (void)unregisterFromKVO { + for (NSString *keyPath in [self observableKeypaths]) { + [self removeObserver:self forKeyPath:keyPath]; + } +} + +- (NSArray *)observableKeypaths { + return [NSArray arrayWithObjects:@"mode", @"customView", @"labelText", @"labelFont", @"labelColor", + @"detailsLabelText", @"detailsLabelFont", @"detailsLabelColor", @"progress", @"activityIndicatorColor", nil]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if (![NSThread isMainThread]) { + [self performSelectorOnMainThread:@selector(updateUIForKeypath:) withObject:keyPath waitUntilDone:NO]; + } else { + [self updateUIForKeypath:keyPath]; + } +} + +- (void)updateUIForKeypath:(NSString *)keyPath { + if ([keyPath isEqualToString:@"mode"] || [keyPath isEqualToString:@"customView"] || + [keyPath isEqualToString:@"activityIndicatorColor"]) { + [self updateIndicators]; + } else if ([keyPath isEqualToString:@"labelText"]) { + label.text = self.labelText; + } else if ([keyPath isEqualToString:@"labelFont"]) { + label.font = self.labelFont; + } else if ([keyPath isEqualToString:@"labelColor"]) { + label.textColor = self.labelColor; + } else if ([keyPath isEqualToString:@"detailsLabelText"]) { + detailsLabel.text = self.detailsLabelText; + } else if ([keyPath isEqualToString:@"detailsLabelFont"]) { + detailsLabel.font = self.detailsLabelFont; + } else if ([keyPath isEqualToString:@"detailsLabelColor"]) { + detailsLabel.textColor = self.detailsLabelColor; + } else if ([keyPath isEqualToString:@"progress"]) { + if ([indicator respondsToSelector:@selector(setProgress:)]) { + [(id)indicator setValue:@(progress) forKey:@"progress"]; + } + return; + } + [self setNeedsLayout]; + [self setNeedsDisplay]; +} + +#pragma mark - Notifications + +- (void)registerForNotifications { + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + + [nc addObserver:self selector:@selector(statusBarOrientationDidChange:) + name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; +} + +- (void)unregisterFromNotifications { + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + [nc removeObserver:self name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; +} + +- (void)statusBarOrientationDidChange:(NSNotification *)notification { + UIView *superview = self.superview; + if (!superview) { + return; + } else if ([self shouldPerformOrientationTransform]) { + [self setTransformForCurrentOrientation:YES]; + } else { + self.frame = self.superview.bounds; + [self setNeedsDisplay]; + } +} + +- (void)setTransformForCurrentOrientation:(BOOL)animated { + // Stay in sync with the superview + if (self.superview) { + self.bounds = self.superview.bounds; + [self setNeedsDisplay]; + } + + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + CGFloat radians = 0; + if (UIInterfaceOrientationIsLandscape(orientation)) { + if (orientation == UIInterfaceOrientationLandscapeLeft) { radians = -(CGFloat)M_PI_2; } + else { radians = (CGFloat)M_PI_2; } + // Window coordinates differ! + self.bounds = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.width); + } else { + if (orientation == UIInterfaceOrientationPortraitUpsideDown) { radians = (CGFloat)M_PI; } + else { radians = 0; } + } + rotationTransform = CGAffineTransformMakeRotation(radians); + + if (animated) { + [UIView beginAnimations:nil context:nil]; + [UIView setAnimationDuration:0.3]; + } + [self setTransform:rotationTransform]; + if (animated) { + [UIView commitAnimations]; + } +} + +@end + + +@implementation MBRoundProgressView + +#pragma mark - Lifecycle + +- (id)init { + return [self initWithFrame:CGRectMake(0.f, 0.f, 37.f, 37.f)]; +} + +- (id)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor clearColor]; + self.opaque = NO; + _progress = 0.f; + _annular = NO; + _progressTintColor = [[UIColor alloc] initWithWhite:1.f alpha:1.f]; + _backgroundTintColor = [[UIColor alloc] initWithWhite:1.f alpha:.1f]; + [self registerForKVO]; + } + return self; +} + +- (void)dealloc { + [self unregisterFromKVO]; +#if !__has_feature(objc_arc) + [_progressTintColor release]; + [_backgroundTintColor release]; + [super dealloc]; +#endif +} + +#pragma mark - Drawing + +- (void)drawRect:(CGRect)rect { + + CGRect allRect = self.bounds; + CGRect circleRect = CGRectInset(allRect, 2.0f, 2.0f); + CGContextRef context = UIGraphicsGetCurrentContext(); + + if (_annular) { + // Draw background + BOOL isPreiOS7 = NSFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0; + CGFloat lineWidth = isPreiOS7 ? 5.f : 2.f; + UIBezierPath *processBackgroundPath = [UIBezierPath bezierPath]; + processBackgroundPath.lineWidth = lineWidth; + processBackgroundPath.lineCapStyle = kCGLineCapButt; + CGPoint center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); + CGFloat radius = (self.bounds.size.width - lineWidth)/2; + CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees + CGFloat endAngle = (2 * (float)M_PI) + startAngle; + [processBackgroundPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; + [_backgroundTintColor set]; + [processBackgroundPath stroke]; + // Draw progress + UIBezierPath *processPath = [UIBezierPath bezierPath]; + processPath.lineCapStyle = isPreiOS7 ? kCGLineCapRound : kCGLineCapSquare; + processPath.lineWidth = lineWidth; + endAngle = (self.progress * 2 * (float)M_PI) + startAngle; + [processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; + [_progressTintColor set]; + [processPath stroke]; + } else { + // Draw background + [_progressTintColor setStroke]; + [_backgroundTintColor setFill]; + CGContextSetLineWidth(context, 2.0f); + CGContextFillEllipseInRect(context, circleRect); + CGContextStrokeEllipseInRect(context, circleRect); + // Draw progress + CGPoint center = CGPointMake(allRect.size.width / 2, allRect.size.height / 2); + CGFloat radius = (allRect.size.width - 4) / 2; + CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees + CGFloat endAngle = (self.progress * 2 * (float)M_PI) + startAngle; + CGContextSetRGBFillColor(context, 1.0f, 1.0f, 1.0f, 1.0f); // white + CGContextMoveToPoint(context, center.x, center.y); + CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, 0); + CGContextClosePath(context); + CGContextFillPath(context); + } +} + +#pragma mark - KVO + +- (void)registerForKVO { + for (NSString *keyPath in [self observableKeypaths]) { + [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL]; + } +} + +- (void)unregisterFromKVO { + for (NSString *keyPath in [self observableKeypaths]) { + [self removeObserver:self forKeyPath:keyPath]; + } +} + +- (NSArray *)observableKeypaths { + return [NSArray arrayWithObjects:@"progressTintColor", @"backgroundTintColor", @"progress", @"annular", nil]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + [self setNeedsDisplay]; +} + +@end + + +@implementation MBBarProgressView + +#pragma mark - Lifecycle + +- (id)init { + return [self initWithFrame:CGRectMake(.0f, .0f, 120.0f, 20.0f)]; +} + +- (id)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + _progress = 0.f; + _lineColor = [UIColor whiteColor]; + _progressColor = [UIColor whiteColor]; + _progressRemainingColor = [UIColor clearColor]; + self.backgroundColor = [UIColor clearColor]; + self.opaque = NO; + [self registerForKVO]; + } + return self; +} + +- (void)dealloc { + [self unregisterFromKVO]; +#if !__has_feature(objc_arc) + [_lineColor release]; + [_progressColor release]; + [_progressRemainingColor release]; + [super dealloc]; +#endif +} + +#pragma mark - Drawing + +- (void)drawRect:(CGRect)rect { + CGContextRef context = UIGraphicsGetCurrentContext(); + + CGContextSetLineWidth(context, 2); + CGContextSetStrokeColorWithColor(context,[_lineColor CGColor]); + CGContextSetFillColorWithColor(context, [_progressRemainingColor CGColor]); + + // Draw background + float radius = (rect.size.height / 2) - 2; + CGContextMoveToPoint(context, 2, rect.size.height/2); + CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius); + CGContextAddLineToPoint(context, rect.size.width - radius - 2, 2); + CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius); + CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius); + CGContextAddLineToPoint(context, radius + 2, rect.size.height - 2); + CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius); + CGContextFillPath(context); + + // Draw border + CGContextMoveToPoint(context, 2, rect.size.height/2); + CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius); + CGContextAddLineToPoint(context, rect.size.width - radius - 2, 2); + CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius); + CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius); + CGContextAddLineToPoint(context, radius + 2, rect.size.height - 2); + CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius); + CGContextStrokePath(context); + + CGContextSetFillColorWithColor(context, [_progressColor CGColor]); + radius = radius - 2; + float amount = self.progress * rect.size.width; + + // Progress in the middle area + if (amount >= radius + 4 && amount <= (rect.size.width - radius - 4)) { + CGContextMoveToPoint(context, 4, rect.size.height/2); + CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); + CGContextAddLineToPoint(context, amount, 4); + CGContextAddLineToPoint(context, amount, radius + 4); + + CGContextMoveToPoint(context, 4, rect.size.height/2); + CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); + CGContextAddLineToPoint(context, amount, rect.size.height - 4); + CGContextAddLineToPoint(context, amount, radius + 4); + + CGContextFillPath(context); + } + + // Progress in the right arc + else if (amount > radius + 4) { + float x = amount - (rect.size.width - radius - 4); + + CGContextMoveToPoint(context, 4, rect.size.height/2); + CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); + CGContextAddLineToPoint(context, rect.size.width - radius - 4, 4); + float angle = -acos(x/radius); + if (isnan(angle)) angle = 0; + CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, M_PI, angle, 0); + CGContextAddLineToPoint(context, amount, rect.size.height/2); + + CGContextMoveToPoint(context, 4, rect.size.height/2); + CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); + CGContextAddLineToPoint(context, rect.size.width - radius - 4, rect.size.height - 4); + angle = acos(x/radius); + if (isnan(angle)) angle = 0; + CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, -M_PI, angle, 1); + CGContextAddLineToPoint(context, amount, rect.size.height/2); + + CGContextFillPath(context); + } + + // Progress is in the left arc + else if (amount < radius + 4 && amount > 0) { + CGContextMoveToPoint(context, 4, rect.size.height/2); + CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); + CGContextAddLineToPoint(context, radius + 4, rect.size.height/2); + + CGContextMoveToPoint(context, 4, rect.size.height/2); + CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); + CGContextAddLineToPoint(context, radius + 4, rect.size.height/2); + + CGContextFillPath(context); + } +} + +#pragma mark - KVO + +- (void)registerForKVO { + for (NSString *keyPath in [self observableKeypaths]) { + [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL]; + } +} + +- (void)unregisterFromKVO { + for (NSString *keyPath in [self observableKeypaths]) { + [self removeObserver:self forKeyPath:keyPath]; + } +} + +- (NSArray *)observableKeypaths { + return [NSArray arrayWithObjects:@"lineColor", @"progressRemainingColor", @"progressColor", @"progress", nil]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + [self setNeedsDisplay]; +} + +@end diff --git a/BakerShelf/lib/NSData+Base64.h b/BakerShelf/lib/NSData+BakerExtensions.h similarity index 87% rename from BakerShelf/lib/NSData+Base64.h rename to BakerShelf/lib/NSData+BakerExtensions.h index fe073ad6..1e57eb94 100644 --- a/BakerShelf/lib/NSData+Base64.h +++ b/BakerShelf/lib/NSData+BakerExtensions.h @@ -34,9 +34,9 @@ @interface NSData (Base64) -+ (NSData *)dataWithBase64EncodedString:(NSString *)string; -- (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth; -- (NSString *)base64EncodedString; ++ (NSData*)bkrDataWithBase64EncodedString:(NSString*)string; +- (NSString*)bkrBase64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth; +- (NSString*)bkrBase64EncodedString; @end diff --git a/BakerShelf/lib/NSData+Base64.m b/BakerShelf/lib/NSData+BakerExtensions.m similarity index 88% rename from BakerShelf/lib/NSData+Base64.m rename to BakerShelf/lib/NSData+BakerExtensions.m index 76c2e05b..a187a2d8 100644 --- a/BakerShelf/lib/NSData+Base64.m +++ b/BakerShelf/lib/NSData+BakerExtensions.m @@ -30,11 +30,11 @@ // 3. This notice may not be removed or altered from any source distribution. // -#import "NSData+Base64.h" +#import "NSData+BakerExtensions.h" @implementation NSData (Base64) -+ (NSData *)dataWithBase64EncodedString:(NSString *)string ++ (NSData *)bkrDataWithBase64EncodedString:(NSString *)string { const char lookup[] = { @@ -53,7 +53,7 @@ + (NSData *)dataWithBase64EncodedString:(NSString *)string const unsigned char *inputBytes = [inputData bytes]; long long maxOutputLength = (inputLength / 4 + 1) * 3; - NSMutableData *outputData = [NSMutableData dataWithLength:maxOutputLength]; + NSMutableData *outputData = [NSMutableData dataWithLength:(NSUInteger)maxOutputLength]; unsigned char *outputBytes = (unsigned char *)[outputData mutableBytes]; int accumulator = 0; @@ -81,11 +81,11 @@ + (NSData *)dataWithBase64EncodedString:(NSString *)string if (accumulator > 2) outputLength++; //truncate data to match actual output length - outputData.length = outputLength; + outputData.length = (NSUInteger)outputLength; return outputLength? outputData: nil; } -- (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth +- (NSString *)bkrBase64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth { //ensure wrapWidth is a multiple of 4 wrapWidth = (wrapWidth / 4) * 4; @@ -97,7 +97,7 @@ - (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth long long maxOutputLength = (inputLength / 3 + 1) * 4; maxOutputLength += wrapWidth? (maxOutputLength / wrapWidth) * 2: 0; - unsigned char *outputBytes = (unsigned char *)malloc(maxOutputLength); + unsigned char *outputBytes = (unsigned char *)malloc((unsigned long)maxOutputLength); long long i; long long outputLength = 0; @@ -135,8 +135,10 @@ - (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth } //truncate data to match actual output length - outputBytes = realloc(outputBytes, outputLength); - NSString *result = [[NSString alloc] initWithBytesNoCopy:outputBytes length:outputLength encoding:NSASCIIStringEncoding freeWhenDone:YES]; + if (outputLength > 0) { + outputBytes = realloc(outputBytes, (unsigned long)outputLength); + } + NSString *result = [[NSString alloc] initWithBytesNoCopy:outputBytes length:(NSUInteger)outputLength encoding:NSASCIIStringEncoding freeWhenDone:YES]; #if !__has_feature(objc_arc) [result autorelease]; @@ -145,9 +147,9 @@ - (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth return (outputLength >= 4)? result: nil; } -- (NSString *)base64EncodedString +- (NSString *)bkrBase64EncodedString { - return [self base64EncodedStringWithWrapWidth:0]; + return [self bkrBase64EncodedStringWithWrapWidth:0]; } @end diff --git a/BakerShelf/lib/NSMutableURLRequest+WebServiceClient.h b/BakerShelf/lib/NSMutableURLRequest+BakerExtensions.h similarity index 88% rename from BakerShelf/lib/NSMutableURLRequest+WebServiceClient.h rename to BakerShelf/lib/NSMutableURLRequest+BakerExtensions.h index d829da9b..07bcfea8 100644 --- a/BakerShelf/lib/NSMutableURLRequest+WebServiceClient.h +++ b/BakerShelf/lib/NSMutableURLRequest+BakerExtensions.h @@ -6,6 +6,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -32,9 +33,9 @@ #import -@interface NSMutableURLRequest (WebServiceClient) +@interface NSMutableURLRequest (BakerExtensions) -+ (NSString *) encodeFormPostParameters: (NSDictionary *) parameters; -- (void) setFormPostParameters: (NSDictionary *) parameters; ++ (NSString*)bkrEncodeFormPostParameters:(NSDictionary*)parameters; +- (void)bkrSetFormPostParameters:(NSDictionary*)parameters; @end diff --git a/BakerShelf/lib/NSMutableURLRequest+WebServiceClient.m b/BakerShelf/lib/NSMutableURLRequest+BakerExtensions.m similarity index 78% rename from BakerShelf/lib/NSMutableURLRequest+WebServiceClient.m rename to BakerShelf/lib/NSMutableURLRequest+BakerExtensions.m index 7b820af1..1057f802 100644 --- a/BakerShelf/lib/NSMutableURLRequest+WebServiceClient.m +++ b/BakerShelf/lib/NSMutableURLRequest+BakerExtensions.m @@ -6,6 +6,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -30,18 +31,18 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -#import "NSMutableURLRequest+WebServiceClient.h" +#import "NSMutableURLRequest+BakerExtensions.h" -@implementation NSMutableURLRequest (WebServiceClient) +@implementation NSMutableURLRequest (BakerExtensions) -+ (NSString *) encodeFormPostParameters: (NSDictionary *) parameters { - NSMutableString *formPostParams = [[[NSMutableString alloc] init] autorelease]; ++ (NSString*)bkrEncodeFormPostParameters:(NSDictionary*)parameters { + NSMutableString *formPostParams = [[NSMutableString alloc] init]; NSEnumerator *keys = [parameters keyEnumerator]; NSString *name = [keys nextObject]; while (nil != name) { - NSString *encodedValue = [((NSString *) CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef) [parameters objectForKey: name], NULL, CFSTR("=/:"), kCFStringEncodingUTF8)) autorelease]; + NSString *encodedValue = ((NSString *) CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef) [parameters objectForKey: name], NULL, CFSTR("=/:"), kCFStringEncodingUTF8))); [formPostParams appendString: name]; [formPostParams appendString: @"="]; @@ -57,8 +58,8 @@ + (NSString *) encodeFormPostParameters: (NSDictionary *) parameters { return formPostParams; } -- (void) setFormPostParameters: (NSDictionary *) parameters { - NSString *formPostParams = [NSMutableURLRequest encodeFormPostParameters: parameters]; +- (void)bkrSetFormPostParameters:(NSDictionary*)parameters { + NSString *formPostParams = [NSMutableURLRequest bkrEncodeFormPostParameters: parameters]; [self setHTTPBody: [formPostParams dataUsingEncoding: NSUTF8StringEncoding]]; [self setValue: @"application/x-www-form-urlencoded; charset=utf-8" forHTTPHeaderField: @"Content-Type"]; diff --git a/BakerShelf/lib/NSURL+Extensions.h b/BakerShelf/lib/NSURL+BakerExtensions.h similarity index 92% rename from BakerShelf/lib/NSURL+Extensions.h rename to BakerShelf/lib/NSURL+BakerExtensions.h index d030d6b8..ea8039b2 100644 --- a/BakerShelf/lib/NSURL+Extensions.h +++ b/BakerShelf/lib/NSURL+BakerExtensions.h @@ -6,6 +6,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -32,8 +33,8 @@ #import -@interface NSURL (Extensions) +@interface NSURL (BakerExtensions) -- (NSURL *)URLByAppendingQueryString:(NSString *)queryString; +- (NSURL*)bkrURLByAppendingQueryString:(NSString*)queryString; @end diff --git a/BakerShelf/lib/NSURL+Extensions.m b/BakerShelf/lib/NSURL+BakerExtensions.m similarity index 91% rename from BakerShelf/lib/NSURL+Extensions.m rename to BakerShelf/lib/NSURL+BakerExtensions.m index cd247dc2..950e5032 100644 --- a/BakerShelf/lib/NSURL+Extensions.m +++ b/BakerShelf/lib/NSURL+BakerExtensions.m @@ -6,6 +6,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -30,11 +31,11 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -#import "NSURL+Extensions.h" +#import "NSURL+BakerExtensions.h" -@implementation NSURL (Extensions) +@implementation NSURL (BakerExtensions) -- (NSURL *)URLByAppendingQueryString:(NSString *)queryString { +- (NSURL*)bkrURLByAppendingQueryString:(NSString*)queryString { if (![queryString length]) { return self; } @@ -42,7 +43,6 @@ - (NSURL *)URLByAppendingQueryString:(NSString *)queryString { NSString *URLString = [[NSString alloc] initWithFormat:@"%@%@%@", [self absoluteString], [self query] ? @"&" : @"?", queryString]; NSURL *theURL = [NSURL URLWithString:URLString]; - [URLString release]; return theURL; } diff --git a/BakerView/lib/minizip/crypt.h b/BakerShelf/lib/minizip/crypt.h similarity index 100% rename from BakerView/lib/minizip/crypt.h rename to BakerShelf/lib/minizip/crypt.h diff --git a/BakerView/lib/minizip/ioapi.c b/BakerShelf/lib/minizip/ioapi.c similarity index 99% rename from BakerView/lib/minizip/ioapi.c rename to BakerShelf/lib/minizip/ioapi.c index e0f73829..bd6dd95a 100755 --- a/BakerView/lib/minizip/ioapi.c +++ b/BakerShelf/lib/minizip/ioapi.c @@ -192,7 +192,7 @@ static long ZCALLBACK fseek64_file_func (voidpf opaque, voidpf stream, ZPOS64_T } ret = 0; - if(fseeko64((FILE *)stream, offset, fseek_origin) != 0) + if(fseeko64((FILE *)stream, (long)offset, fseek_origin) != 0) ret = -1; return ret; diff --git a/BakerView/lib/minizip/ioapi.h b/BakerShelf/lib/minizip/ioapi.h similarity index 100% rename from BakerView/lib/minizip/ioapi.h rename to BakerShelf/lib/minizip/ioapi.h diff --git a/BakerView/lib/minizip/mztools.c b/BakerShelf/lib/minizip/mztools.c similarity index 100% rename from BakerView/lib/minizip/mztools.c rename to BakerShelf/lib/minizip/mztools.c diff --git a/BakerView/lib/minizip/mztools.h b/BakerShelf/lib/minizip/mztools.h similarity index 100% rename from BakerView/lib/minizip/mztools.h rename to BakerShelf/lib/minizip/mztools.h diff --git a/BakerView/lib/minizip/unzip.c b/BakerShelf/lib/minizip/unzip.c similarity index 100% rename from BakerView/lib/minizip/unzip.c rename to BakerShelf/lib/minizip/unzip.c diff --git a/BakerView/lib/minizip/unzip.h b/BakerShelf/lib/minizip/unzip.h similarity index 100% rename from BakerView/lib/minizip/unzip.h rename to BakerShelf/lib/minizip/unzip.h diff --git a/BakerView/lib/minizip/zip.c b/BakerShelf/lib/minizip/zip.c similarity index 99% rename from BakerView/lib/minizip/zip.c rename to BakerShelf/lib/minizip/zip.c index db501c18..8e03a965 100755 --- a/BakerView/lib/minizip/zip.c +++ b/BakerShelf/lib/minizip/zip.c @@ -98,7 +98,7 @@ const char zip_copyright[] =" zip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll"; -#define SIZEDATA_INDATABLOCK (4096-(4*4)) +#define SIZEDATA_INDATABLOCK (4096- (4*4)) #define LOCALHEADERMAGIC (0x04034b50) #define CENTRALHEADERMAGIC (0x02014b50) diff --git a/BakerView/lib/minizip/zip.h b/BakerShelf/lib/minizip/zip.h similarity index 100% rename from BakerView/lib/minizip/zip.h rename to BakerShelf/lib/minizip/zip.h diff --git a/Baker/BakerAnalyticsEvents.h b/BakerView/BKRAnalyticsEvents.h similarity index 86% rename from Baker/BakerAnalyticsEvents.h rename to BakerView/BKRAnalyticsEvents.h index b2b40fe5..6e544d68 100644 --- a/Baker/BakerAnalyticsEvents.h +++ b/BakerView/BKRAnalyticsEvents.h @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -31,19 +32,14 @@ #import -#import "BakerViewController.h" -#import "ShelfViewController.h" -#import "IssueViewController.h" - -@interface BakerAnalyticsEvents : NSObject { - - id tracker; // Can be used to reference tracking libraries (i.e. Google Analytics, ...) - -} +@interface BKRAnalyticsEvents : NSObject +//{ +// id tracker; // Can be used to reference tracking libraries (i.e. Google Analytics, ...) +//} #pragma mark - Singleton -+ (BakerAnalyticsEvents *)sharedInstance; ++ (BKRAnalyticsEvents*)sharedInstance; - (id)init; @end diff --git a/Baker/BakerAnalyticsEvents.m b/BakerView/BKRAnalyticsEvents.m similarity index 64% rename from Baker/BakerAnalyticsEvents.m rename to BakerView/BKRAnalyticsEvents.m index 9b75ea35..e7a3674d 100644 --- a/Baker/BakerAnalyticsEvents.m +++ b/BakerView/BKRAnalyticsEvents.m @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -29,17 +30,15 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -#import "BakerAnalyticsEvents.h" - - -@implementation BakerAnalyticsEvents +#import "BKRAnalyticsEvents.h" +@implementation BKRAnalyticsEvents #pragma mark - Singleton -+ (BakerAnalyticsEvents *)sharedInstance { ++ (BKRAnalyticsEvents *)sharedInstance { static dispatch_once_t once; - static BakerAnalyticsEvents *sharedInstance; + static BKRAnalyticsEvents *sharedInstance; dispatch_once(&once, ^{ sharedInstance = [[self alloc] init]; }); @@ -47,32 +46,41 @@ + (BakerAnalyticsEvents *)sharedInstance { } - (id)init { - self = [super init]; + if (self) { - // ****** Add here your analytics code - // tracker = [[GAI sharedInstance] trackerWithTrackingId:@"ADD_HERE_YOUR_TRACKING_CODE"]; - - - // ****** Register to handle events - [self registerEvents]; - + // ****** Add here your analytics code + + //GAI Configuration + // Optional: automatically send uncaught exceptions to Google Analytics. + //[GAI sharedInstance].trackUncaughtExceptions = YES; + + // Optional: set Google Analytics dispatch interval to e.g. 20 seconds. + //[GAI sharedInstance].dispatchInterval = 20; + + // Optional: set Logger to VERBOSE for debug information. + //[[[GAI sharedInstance] logger] setLogLevel:kGAILogLevelVerbose]; + + // Initialize tracker. Replace with your tracking ID. + //[[GAI sharedInstance] trackerWithTrackingId:@"UA-XXXX-Y"]; + + // ****** Register to handle events + [self registerEvents]; + + } return self; } - (void) dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; - [super dealloc]; } - #pragma mark - Events - (void)registerEvents { + // Register the analytics event that are going to be tracked by Baker. - - NSArray *analyticEvents = [NSArray arrayWithObjects: - @"BakerApplicationStart", + NSArray *analyticEvents = @[@"BakerApplicationStart", @"BakerIssueDownload", @"BakerIssueOpen", @"BakerIssueClose", @@ -81,8 +89,7 @@ - (void)registerEvents { @"BakerSubscriptionPurchase", @"BakerViewPage", @"BakerViewIndexOpen", - @"BakerViewModalBrowser", - nil]; + @"BakerViewModalBrowser"]; for (NSString *eventName in analyticEvents) { [[NSNotificationCenter defaultCenter] addObserver:self @@ -91,39 +98,39 @@ - (void)registerEvents { object:nil]; } - } -- (void)receiveEvent:(NSNotification *)notification { - //NSLog(@"[BakerAnalyticsEvent] Received event %@", [notification name]); // Uncomment this to debug +- (void)receiveEvent:(NSNotification*)notification { + //NSLog(@"[BakerAnalyticsEvent] Received event %@", notification.name); // Uncomment this to debug + //GAI Activation + //id tracker = [[GAI sharedInstance] defaultTracker]; // If you want, you can handle differently the various events - if ([[notification name] isEqualToString:@"BakerApplicationStart"]) { + if ([notification.name isEqualToString:@"BakerApplicationStart"]) { // Track here when the Baker app opens - } else if ([[notification name] isEqualToString:@"BakerIssueDownload"]) { + } else if ([notification.name isEqualToString:@"BakerIssueDownload"]) { // Track here when a issue download is requested - } else if ([[notification name] isEqualToString:@"BakerIssueOpen"]) { + } else if ([notification.name isEqualToString:@"BakerIssueOpen"]) { // Track here when a issue is opened to be read - } else if ([[notification name] isEqualToString:@"BakerIssueClose"]) { + } else if ([notification.name isEqualToString:@"BakerIssueClose"]) { // Track here when a issue that was being read is closed - } else if ([[notification name] isEqualToString:@"BakerIssuePurchase"]) { + } else if ([notification.name isEqualToString:@"BakerIssuePurchase"]) { // Track here when a issue purchase is requested - } else if ([[notification name] isEqualToString:@"BakerIssueArchive"]) { + } else if ([notification.name isEqualToString:@"BakerIssueArchive"]) { // Track here when a issue archival is requested - } else if ([[notification name] isEqualToString:@"BakerSubscriptionPurchase"]) { + } else if ([notification.name isEqualToString:@"BakerSubscriptionPurchase"]) { // Track here when a subscription purchased is requested - } else if ([[notification name] isEqualToString:@"BakerViewPage"]) { + } else if ([notification.name isEqualToString:@"BakerViewPage"]) { // Track here when a specific page is opened // BakerViewController *bakerview = [notification object]; // Uncomment this to get the BakerViewController object and get its properties //NSLog(@" - Tracking page %d", bakerview.currentPageNumber); // This is useful to check if it works - } else if ([[notification name] isEqualToString:@"BakerViewIndexOpen"]) { + } else if ([notification.name isEqualToString:@"BakerViewIndexOpen"]) { // Track here the opening of the index and status bar - } else if ([[notification name] isEqualToString:@"BakerViewModalBrowser"]) { + } else if ([notification.name isEqualToString:@"BakerViewModalBrowser"]) { // Track here the opening of the modal view } else { - } -} +} @end diff --git a/BakerView/BakerBook.h b/BakerView/BKRBook.h similarity index 54% rename from BakerView/BakerBook.h rename to BakerView/BKRBook.h index 0404a827..1d983837 100644 --- a/BakerView/BakerBook.h +++ b/BakerView/BKRBook.h @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -31,57 +32,61 @@ #import -@interface BakerBook : NSObject +@interface BKRBook : NSObject #pragma mark - HPub Parameters Properties -@property (copy, nonatomic) NSNumber *hpub; -@property (copy, nonatomic) NSString *title; -@property (copy, nonatomic) NSString *date; +@property (nonatomic, strong) NSDictionary *bookData; +@property (copy, nonatomic) NSString *parseError; -@property (copy, nonatomic) NSArray *author; -@property (copy, nonatomic) NSArray *creator; -@property (copy, nonatomic) NSString *publisher; +@property (nonatomic, copy) NSNumber *hpub; +@property (nonatomic, copy) NSString *title; +@property (nonatomic, copy) NSString *date; -@property (copy, nonatomic) NSString *url; -@property (copy, nonatomic) NSString *cover; +@property (nonatomic, copy) NSArray *author; +@property (nonatomic, copy) NSArray *creator; +@property (nonatomic, copy) NSArray *categories; +@property (nonatomic, copy) NSString *publisher; -@property (copy, nonatomic) NSString *orientation; -@property (copy, nonatomic) NSNumber *zoomable; +@property (nonatomic, copy) NSString *url; +@property (nonatomic, copy) NSString *cover; -@property (strong, nonatomic) NSMutableArray *contents; +@property (nonatomic, copy) NSString *orientation; +@property (nonatomic, copy) NSNumber *zoomable; + +@property (nonatomic, strong) NSMutableArray *contents; #pragma mark - Baker HPub Extensions Properties -@property (copy, nonatomic) NSString *bakerBackground; -@property (copy, nonatomic) NSString *bakerBackgroundImagePortrait; -@property (copy, nonatomic) NSString *bakerBackgroundImageLandscape; -@property (copy, nonatomic) NSString *bakerPageNumbersColor; -@property (copy, nonatomic) NSNumber *bakerPageNumbersAlpha; -@property (copy, nonatomic) NSString *bakerPageScreenshots; - -@property (copy, nonatomic) NSString *bakerRendering; -@property (copy, nonatomic) NSNumber *bakerVerticalBounce; -@property (copy, nonatomic) NSNumber *bakerVerticalPagination; -@property (copy, nonatomic) NSNumber *bakerPageTurnTap; -@property (copy, nonatomic) NSNumber *bakerPageTurnSwipe; -@property (copy, nonatomic) NSNumber *bakerMediaAutoplay; - -@property (copy, nonatomic) NSNumber *bakerIndexWidth; -@property (copy, nonatomic) NSNumber *bakerIndexHeight; -@property (copy, nonatomic) NSNumber *bakerIndexBounce; -@property (copy, nonatomic) NSNumber *bakerStartAtPage; +@property (nonatomic, copy) NSString *bakerBackground; +@property (nonatomic, copy) NSString *bakerBackgroundImagePortrait; +@property (nonatomic, copy) NSString *bakerBackgroundImageLandscape; +@property (nonatomic, copy) NSString *bakerPageNumbersColor; +@property (nonatomic, copy) NSNumber *bakerPageNumbersAlpha; +@property (nonatomic, copy) NSString *bakerPageScreenshots; + +@property (nonatomic, copy) NSString *bakerRendering; +@property (nonatomic, copy) NSNumber *bakerVerticalBounce; +@property (nonatomic, copy) NSNumber *bakerVerticalPagination; +@property (nonatomic, copy) NSNumber *bakerPageTurnTap; +@property (nonatomic, copy) NSNumber *bakerPageTurnSwipe; +@property (nonatomic, copy) NSNumber *bakerMediaAutoplay; + +@property (nonatomic, copy) NSNumber *bakerIndexWidth; +@property (nonatomic, copy) NSNumber *bakerIndexHeight; +@property (nonatomic, copy) NSNumber *bakerIndexBounce; +@property (nonatomic, copy) NSNumber *bakerStartAtPage; #pragma mark - Book Status Properties -@property (copy, nonatomic) NSString *ID; -@property (copy, nonatomic) NSString *path; -@property (copy, nonatomic) NSNumber *isBundled; -@property (copy, nonatomic) NSString *screenshotsPath; -@property (copy, nonatomic) NSNumber *screenshotsWritable; -@property (copy, nonatomic) NSNumber *currentPage; -@property (copy, nonatomic) NSNumber *lastScrollIndex; -@property (copy, nonatomic) NSDate *lastOpenedDate; +@property (nonatomic, copy) NSString *ID; +@property (nonatomic, copy) NSString *path; +@property (nonatomic, copy) NSNumber *isBundled; +@property (nonatomic, copy) NSString *screenshotsPath; +@property (nonatomic, copy) NSNumber *screenshotsWritable; +@property (nonatomic, copy) NSNumber *currentPage; +@property (nonatomic, copy) NSNumber *lastScrollIndex; +@property (nonatomic, copy) NSDate *lastOpenedDate; #pragma mark - Init @@ -92,16 +97,15 @@ #pragma mark - HPub validation +- (BOOL)isValid; - (BOOL)validateBookJSON:(NSDictionary *)bookData withRequirements:(NSArray *)requirements; -- (BOOL)validateArray:(NSArray *)array forParam:(NSString *)param; -- (BOOL)validateString:(NSString *)string forParam:(NSString *)param; -- (BOOL)validateNumber:(NSNumber *)number forParam:(NSString *)param; +- (BOOL)validateArray:(NSArray *)array forParam:(NSString *)param withParamsArray:(NSArray*)paramsArray; +- (BOOL)validateString:(NSString *)string forParam:(NSString *)param withParamsArray:(NSArray*)paramsArray; +- (BOOL)validateNumber:(NSNumber *)number forParam:(NSString *)param withParamsArray:(NSArray*)paramsArray; - (BOOL)matchParam:(NSString *)param againstParamsArray:(NSArray *)paramsArray; #pragma mark - Book status management - (BOOL)updateBookPath:(NSString *)bookPath bundled:(BOOL)bundled; -- (void)openBook; -- (void)closeBook; @end diff --git a/BakerView/BKRBook.m b/BakerView/BKRBook.m new file mode 100644 index 00000000..c4c82cc7 --- /dev/null +++ b/BakerView/BKRBook.m @@ -0,0 +1,386 @@ +// +// BakerBook.m +// Baker +// +// ========================================================================================== +// +// Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout +// 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 Baker Framework nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written +// permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#import "BKRCore.h" +#import "BKRBook.h" +#import "NSString+BakerExtensions.h" + +@implementation BKRBook + +#pragma mark - Initialization + +- (id)initWithBookPath:(NSString*)bookPath bundled:(BOOL)bundled { + if (![[NSFileManager defaultManager] fileExistsAtPath:bookPath]) { + return nil; + } + + self = [self initWithBookJSONPath:[bookPath stringByAppendingPathComponent:@"book.json"]]; + if (self) { + [self updateBookPath:bookPath bundled:bundled]; + } + return self; + +} + +- (id)initWithBookJSONPath:(NSString*)bookJSONPath { + + if (![[NSFileManager defaultManager] fileExistsAtPath:bookJSONPath]) { + return nil; + } + + NSError* error = nil; + NSData* bookJSON = [NSData dataWithContentsOfFile:bookJSONPath options:0 error:&error]; + if (error) { + self.parseError = [NSString stringWithFormat:@"ERROR: reading 'book.json': %@", error.localizedDescription]; + NSLog(@"[BakerBook] ERROR reading 'book.json': %@", error.localizedDescription); + return nil; + } + + NSDictionary* bookData = [NSJSONSerialization JSONObjectWithData:bookJSON + options:0 + error:&error]; + if (error) { + self.parseError = [NSString stringWithFormat:@"ERROR: reading 'book.json': %@", error.localizedDescription]; + NSLog(@"[BakerBook] ERROR parsing 'book.json': %@", error.localizedDescription); + return nil; + } + + return [self initWithBookData:bookData]; + +} + +- (id)initWithBookData:(NSDictionary*)bookData { + self = [super init]; + if (self && [self loadBookData:bookData]) { + NSString *baseID = [self.title stringByAppendingFormat:@" %@", [self.url bkrStringSHAEncoded]]; + self.ID = [self sanitizeForPath:baseID]; + NSLog(@"[BakerBook] 'book.json' parsed successfully. Book '%@' created with id '%@'.", self.title, self.ID); + return self; + } + return nil; +} + +- (NSString*)sanitizeForPath:(NSString*)string { + + NSError *error = nil; + NSString *newString; + NSRegularExpression *regex; + + // Strip everything except numbers, ASCII letters and spaces + regex = [NSRegularExpression regularExpressionWithPattern:@"[^1-9a-z ]" options:NSRegularExpressionCaseInsensitive error:&error]; + newString = [regex stringByReplacingMatchesInString:string options:0 range:NSMakeRange(0, [string length]) withTemplate:@""]; + + // Replace spaces with dashes + regex = [NSRegularExpression regularExpressionWithPattern:@" +" options:NSRegularExpressionCaseInsensitive error:&error]; + newString = [regex stringByReplacingMatchesInString:newString options:0 range:NSMakeRange(0, [newString length]) withTemplate:@"-"]; + + return [newString lowercaseString]; + +} + +- (BOOL)loadBookData:(NSDictionary*)bookData { + if (![self validateBookJSON:bookData withRequirements:@[@"title", @"author", @"url", @"contents"]]) { + return NO; + } + + self.bookData = bookData; + + self.hpub = bookData[@"hpub"]; + self.title = bookData[@"title"]; + self.date = bookData[@"date"]; + self.categories = bookData[@"categories"]; + + if ([bookData[@"author"] isKindOfClass:[NSArray class]]) { + self.author = bookData[@"author"]; + } else { + self.author = @[bookData[@"author"]]; + } + + if ([bookData[@"creator"] isKindOfClass:[NSArray class]]) { + self.creator = bookData[@"creator"]; + } else if([bookData[@"creator"] isKindOfClass:[NSString class]]) { + self.creator = @[bookData[@"creator"]]; + } + + self.publisher = bookData[@"publisher"]; + + self.url = bookData[@"url"]; + self.cover = bookData[@"cover"]; + + self.orientation = bookData[@"orientation"]; + self.zoomable = bookData[@"zoomable"]; + + // TODO: create an array of n BakerPage objects + self.contents = bookData[@"contents"]; + + self.bakerBackground = bookData[@"-baker-background"]; + self.bakerBackgroundImagePortrait = bookData[@"-baker-background-image-portrait"]; + self.bakerBackgroundImageLandscape = bookData[@"-baker-background-image-landscape"]; + self.bakerPageNumbersColor = bookData[@"-baker-page-numbers-color"]; + self.bakerPageNumbersAlpha = bookData[@"-baker-page-numbers-alpha"]; + self.bakerPageScreenshots = bookData[@"-baker-page-screenshots"]; + + self.bakerRendering = bookData[@"-baker-rendering"]; + self.bakerVerticalBounce = bookData[@"-baker-vertical-bounce"]; + self.bakerVerticalPagination = bookData[@"-baker-vertical-pagination"]; + self.bakerPageTurnTap = bookData[@"-baker-page-turn-tap"]; + self.bakerPageTurnSwipe = bookData[@"-baker-page-turn-swipe"]; + self.bakerMediaAutoplay = bookData[@"-baker-media-autoplay"]; + + self.bakerIndexWidth = bookData[@"-baker-index-width"]; + self.bakerIndexHeight = bookData[@"-baker-index-height"]; + self.bakerIndexBounce = bookData[@"-baker-index-bounce"]; + self.bakerStartAtPage = bookData[@"-baker-start-at-page"]; + + [self loadBookJSONDefault]; + + return YES; +} + +- (void)loadBookJSONDefault { + + if (self.hpub == nil) { + self.hpub = @1; + } + + if (self.bakerBackground == nil) { + self.bakerBackground = @"#000000"; + } + if (self.bakerPageNumbersColor == nil) { + self.bakerPageNumbersColor = @"#ffffff"; + } + if (self.bakerPageNumbersAlpha == nil) { + self.bakerPageNumbersAlpha = @0.3f; + } + + if (self.bakerRendering == nil) { + self.bakerRendering = @"screenshots"; + } + if (self.bakerVerticalBounce == nil) { + self.bakerVerticalBounce = @YES; + } + if (self.bakerVerticalPagination == nil) { + self.bakerVerticalPagination = @NO; + } + + if (self.bakerPageTurnTap == nil) { + self.bakerPageTurnTap = @YES; + } + + if (self.bakerPageTurnSwipe == nil) { + self.bakerPageTurnSwipe = @YES; + } + if (self.bakerMediaAutoplay == nil) { + self.bakerMediaAutoplay = @NO; + } + + if (self.bakerIndexBounce == nil) { + self.bakerIndexBounce = @NO; + } + if (self.bakerStartAtPage == nil) { + self.bakerStartAtPage = @1; + } + +} + + +#pragma mark - HPub validation + +- (BOOL)isValid { + return BKR_IsEmpty(self.parseError); +} + +- (BOOL)validateBookJSON:(NSDictionary*)bookData withRequirements:(NSArray*)requirements { + for (NSString *param in requirements) { + if (bookData[param] == nil) { + NSLog(@"[BakerBook] ERROR: param '%@' is missing. Add it to 'book.json'.", param); + return NO; + } + } + + NSArray *shouldBeArray = @[@"author", + @"creator", + @"contents"]; + + NSArray *shouldBeString = @[@"title", + @"date", + @"author", + @"creator", + @"publisher", + @"url", + @"cover", + @"orientation", + @"-baker-background", + @"-baker-background-image-portrait", + @"-baker-background-image-landscape", + @"-baker-page-numbers-color", + @"-baker-page-screenshots", + @"-baker-rendering"]; + + NSArray *shouldBeNumber = @[@"hpub", + @"zoomable", + @"-baker-page-numbers-alpha", + @"-baker-vertical-bounce", + @"-baker-vertical-pagination", + @"-baker-page-turn-tap", + @"-baker-page-turn-swipe", + @"-baker-media-autoplay", + @"-baker-index-width", + @"-baker-index-height", + @"-baker-index-bounce", + @"-baker-start-at-page"]; + + NSArray *knownParams = [[shouldBeArray arrayByAddingObjectsFromArray:shouldBeString] arrayByAddingObjectsFromArray:shouldBeNumber]; + + for (NSString *param in bookData) { + + if (![self matchParam:param againstParamsArray:knownParams]) { + continue; + } + + id obj = bookData[param]; + if ([obj isKindOfClass:[NSArray class]] && ![self validateArray:(NSArray *)obj forParam:param withParamsArray:shouldBeArray]) { + return NO; + } else if ([obj isKindOfClass:[NSString class]] && ![self validateString:(NSString *)obj forParam:param withParamsArray:shouldBeString]) { + return NO; + } else if ([obj isKindOfClass:[NSNumber class]] && ![self validateNumber:(NSNumber *)obj forParam:param withParamsArray:shouldBeNumber]) { + return NO; + } + + } + + return YES; +} + +- (BOOL)validateArray:(NSArray*)array forParam:(NSString*)param withParamsArray:(NSArray*)paramsArray { + + if (![self matchParam:param againstParamsArray:paramsArray]) { + NSLog(@"[BakerBook] ERROR: param '%@' should not be an Array. Check it in 'book.json'.", param); + return NO; + } + + if (([param isEqualToString:@"author"] || [param isEqualToString:@"contents"]) && [array count] == 0) { + NSLog(@"[BakerBook] ERROR: param '%@' is empty. Fill it in 'book.json'.", param); + return NO; + } + + for (id obj in array) { + if ([param isEqualToString:@"author"] && (![obj isKindOfClass:[NSString class]] || [(NSString *)obj isEqualToString:@""])) { + NSLog(@"[BakerBook] ERROR: param 'author' is empty. Fill it in 'book.json'."); + return NO; + } else if ([param isEqualToString:@"contents"]) { + if ([obj isKindOfClass:[NSDictionary class]] && ![self validateBookJSON:(NSDictionary *)obj withRequirements:@[@"url"]]) { + NSLog(@"[BakerBook] ERROR: param 'contents' is not validating. Check it in 'book.json'."); + return NO; + } + } else if (![obj isKindOfClass:[NSString class]]) { + NSLog(@"[BakerBook] ERROR: param '%@' type is wrong. Check it in 'book.json'.", param); + return NO; + } + } + + return YES; + +} + +- (BOOL)validateString:(NSString*)string forParam:(NSString *)param withParamsArray:(NSArray*)paramsArray { + + if (![self matchParam:param againstParamsArray:paramsArray]) { + NSLog(@"[BakerBook] ERROR: param '%@' should not be a String. Check it in 'book.json'.", param); + return NO; + } + + if (([param isEqualToString:@"title"] || [param isEqualToString:@"author"] || [param isEqualToString:@"url"]) && [string isEqualToString:@""]) { + NSLog(@"[BakerBook] ERROR: param '%@' is empty. Fill it in 'book.json'.", param); + return NO; + } + + if ([param isEqualToString:@"-baker-rendering"] && (![string isEqualToString:@"screenshots"] && ![string isEqualToString:@"three-cards"])) { + NSLog(@"Error: param \"-baker-rendering\" should be equal to \"screenshots\" or \"three-cards\" but it's not"); + NSLog(@"[BakerBook] ERROR: param '-baker-rendering' must be equal to 'screenshots' or 'three-cards'. Check it in 'book.json'."); + return NO; + } + + return YES; + +} + +- (BOOL)validateNumber:(NSNumber*)number forParam:(NSString*)param withParamsArray:(NSArray*)paramsArray { + if (![self matchParam:param againstParamsArray:paramsArray]) { + NSLog(@"[BakerBook] ERROR: param '%@' should not be a Number. Check it in 'book.json'.", param); + return NO; + } + return YES; +} + +- (BOOL)matchParam:(NSString*)param againstParamsArray:(NSArray*)paramsArray { + for (NSString *match in paramsArray) { + if ([param isEqualToString:match]) { + return YES; + } + } + return NO; +} + +#pragma mark - Book status management + +- (BOOL)updateBookPath:(NSString*)bookPath bundled:(BOOL)bundled { + + NSFileManager *fileManager = [NSFileManager defaultManager]; + if (![fileManager fileExistsAtPath:bookPath]) { + return NO; + } + + self.path = bookPath; + self.isBundled = @(bundled); + + self.screenshotsPath = [bookPath stringByAppendingPathComponent:self.bakerPageScreenshots]; + self.screenshotsWritable = @YES; + + if (bundled) { + if (![fileManager fileExistsAtPath:self.screenshotsPath]) { + // TODO: generate writableBookPath in app private documents/books/self.ID; + NSString *writableBookPath = @"writableBookPath"; + self.screenshotsPath = [writableBookPath stringByAppendingPathComponent:self.bakerPageScreenshots]; + } else { + self.screenshotsWritable = @NO; + } + } + + if (![fileManager fileExistsAtPath:self.screenshotsPath]) { + return [fileManager createDirectoryAtPath:self.screenshotsPath withIntermediateDirectories:YES attributes:nil error:nil]; + } + + return YES; +} + +@end diff --git a/BakerView/BakerBookStatus.h b/BakerView/BKRBookStatus.h similarity index 86% rename from BakerView/BakerBookStatus.h rename to BakerView/BKRBookStatus.h index 370c13ed..31b973b3 100644 --- a/BakerView/BakerBookStatus.h +++ b/BakerView/BKRBookStatus.h @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -30,14 +31,14 @@ // #import -#import "JSONStatus.h" +#import "BKRJSONStatus.h" -@interface BakerBookStatus : JSONStatus +@interface BKRBookStatus : BKRJSONStatus -@property (copy, nonatomic) NSNumber *page; -@property (copy, nonatomic) NSString *scrollIndex; +@property (nonatomic, copy) NSNumber *page; +@property (nonatomic, copy) NSString *scrollIndex; -- (id)initWithBookId:(NSString *)bookId; +- (id)initWithBookId:(NSString*)bookId; - (void)save; @end diff --git a/BakerView/BakerBookStatus.m b/BakerView/BKRBookStatus.m similarity index 70% rename from BakerView/BakerBookStatus.m rename to BakerView/BKRBookStatus.m index 0876f5c5..f775edd8 100644 --- a/BakerView/BakerBookStatus.m +++ b/BakerView/BKRBookStatus.m @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -29,40 +30,26 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -#import "BakerBookStatus.h" +#import "BKRBookStatus.h" +#import "NSObject+BakerExtensions.h" -@implementation BakerBookStatus - -@synthesize page; -@synthesize scrollIndex; - -- (id)initWithBookId:(NSString *)bookId { - NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - NSString *statusPath = [[[cachePath stringByAppendingPathComponent:@"statuses"] stringByAppendingPathComponent:bookId] stringByAppendingPathExtension:@"json"]; +@implementation BKRBookStatus +- (id)initWithBookId:(NSString*)bookId { + NSString *statusPath = [[[self.bkrCachePath stringByAppendingPathComponent:@"statuses"] stringByAppendingPathComponent:bookId] stringByAppendingPathExtension:@"json"]; return [super initWithJSONPath:statusPath]; } -- (NSDictionary *)load { +- (NSDictionary*)load { NSDictionary *jsonDict = [super load]; - - self.page = [jsonDict objectForKey:@"page"]; - self.scrollIndex = [jsonDict objectForKey:@"scroll-index"]; - + self.page = jsonDict[@"page"]; + self.scrollIndex = jsonDict[@"scroll-index"]; return jsonDict; } - (void)save { - NSDictionary *jsonDict = [NSDictionary dictionaryWithObjectsAndKeys:page, @"page", scrollIndex, @"scroll-index", nil]; - + NSDictionary *jsonDict = @{@"page": self.page, @"scroll-index": self.scrollIndex}; [super save:jsonDict]; } -- (void)dealloc { - [page release]; - [scrollIndex release]; - - [super dealloc]; -} - @end diff --git a/BakerView/BakerViewController.h b/BakerView/BKRBookViewController.h similarity index 59% rename from BakerView/BakerViewController.h rename to BakerView/BKRBookViewController.h index eb155055..b4159166 100644 --- a/BakerView/BakerViewController.h +++ b/BakerView/BKRBookViewController.h @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -32,15 +33,14 @@ #import #import -#import "IndexViewController.h" -#import "ModalViewController.h" -#import "BakerBook.h" -#import "BakerBookStatus.h" - +#import "BKRIndexViewController.h" +#import "BKRModalWebViewController.h" +#import "BKRBook.h" +#import "BKRBookStatus.h" @class Downloader; -@interface BakerViewController : UIViewController { +@interface BKRBookViewController : UIViewController { CGRect screenBounds; @@ -79,9 +79,9 @@ BOOL adjustViewsOnAppDidBecomeActive; - UIScrollView *scrollView; + //UIScrollView *scrollView; UIWebView *prevPage; - UIWebView *currPage; + //UIWebView *currPage; UIWebView *nextPage; UIColor *webViewBackground; @@ -93,7 +93,7 @@ int totalPages; int lastPageNumber; - int currentPageNumber; + //int currentPageNumber; int pageWidth; int pageHeight; @@ -103,93 +103,106 @@ Downloader *downloader; UIAlertView *feedbackAlert; - IndexViewController *indexViewController; - ModalViewController *myModalViewController; + BKRIndexViewController *indexViewController; + BKRModalWebViewController *myModalViewController; - BakerBookStatus *bookStatus; + BKRBookStatus *bookStatus; } -#pragma mark - PROPERTIES -@property (strong, nonatomic) BakerBook *book; -@property (nonatomic, retain) UIScrollView *scrollView; -@property (nonatomic, retain) UIWebView *currPage; +#pragma mark - Properties + +@property (nonatomic, strong) BKRBook *book; +@property (nonatomic, strong) UIScrollView *scrollView; +@property (nonatomic, strong) UIWebView *currPage; +@property (nonatomic, strong) UIBarButtonItem *shareButton; -@property int currentPageNumber; -@property BOOL barsHidden; +@property (nonatomic, assign) int currentPageNumber; +@property (nonatomic, assign) BOOL barsHidden; -#pragma mark - INIT -- (id)initWithBook:(BakerBook *)bakerBook; -- (BOOL)loadBookWithBookPath:(NSString *)bookPath; +#pragma mark - Initialization + +- (id)initWithBook:(BKRBook*)bakerBook; +- (BOOL)loadBookWithBookPath:(NSString*)bookPath; - (void)cleanupBookEnvironment; - (void)resetPageSlots; - (void)resetPageDetails; - (void)buildPageArray; - (void)startReading; - (void)buildPageDetails; -- (void)setImageFor:(UIImageView *)view; +- (void)setImageFor:(UIImageView*)view; - (void)updateBookLayout; - (void)adjustScrollViewPosition; -- (void)setPageSize:(NSString *)orientation; -- (void)setTappableAreaSize; +- (void)setPageSize:(NSString*)orientation; +- (void)setTappableAreaSizeForOrientation:(NSString*)orientation; - (void)showPageDetails; -- (void)setFrame:(CGRect)frame forPage:(UIWebView *)page; -- (void)setupWebView:(UIWebView *)webView; -- (void)removeWebViewDoubleTapGestureRecognizer:(UIView *)view; +- (void)setFrame:(CGRect)frame forPage:(UIWebView*)page; +- (void)setupWebView:(UIWebView*)webView; +- (void)removeWebViewDoubleTapGestureRecognizer:(UIView*)view; + +#pragma mark - Loading -#pragma mark - LOADING - (BOOL)changePage:(int)page; - (void)gotoPageDelayer; - (void)gotoPage; -- (void)lockPage:(NSNumber *)lock; +- (void)lockPage:(NSNumber*)lock; - (void)addPageLoading:(int)slot; - (void)handlePageLoading; - (void)loadSlot:(int)slot withPage:(int)page; -- (BOOL)loadWebView:(UIWebView *)webview withPage:(int)page; +- (BOOL)loadWebView:(UIWebView*)webview withPage:(int)page; -#pragma mark - MODAL WEBVIEW -- (void)loadModalWebView:(NSURL *)url; +#pragma mark - Modal Webview + +- (void)loadModalWebView:(NSURL*)url; - (void)closeModalWebView; -#pragma mark - SCROLLVIEW +#pragma mark - Scroll View + - (CGRect)frameForPage:(int)page; -#pragma mark - WEBVIEW -- (void)webView:(UIWebView *)webView hidden:(BOOL)status animating:(BOOL)animating; -- (void)webViewDidAppear:(UIWebView *)webView animating:(BOOL)animating; -- (void)webView:(UIWebView *)webView setCorrectOrientation:(UIInterfaceOrientation)interfaceOrientation; +#pragma mark - UIWebViewDelegate + +- (void)webView:(UIWebView*)webView hidden:(BOOL)status animating:(BOOL)animating; +- (void)webViewDidAppear:(UIWebView*)webView animating:(BOOL)animating; +- (void)webView:(UIWebView*)webView setCorrectOrientation:(UIInterfaceOrientation)interfaceOrientation; + +#pragma mark - Screenshots -#pragma mark - SCREENSHOTS - (void)removeScreenshots; - (void)updateScreenshots; -- (BOOL)checkScreeshotForPage:(int)pageNumber andOrientation:(NSString *)interfaceOrientation; -- (void)takeScreenshotFromView:(UIWebView *)webView forPage:(int)pageNumber andOrientation:(NSString *)interfaceOrientation; -- (void)placeScreenshotForView:(UIWebView *)webView andPage:(int)pageNumber andOrientation:(NSString *)interfaceOrientation; +- (BOOL)checkScreeshotForPage:(int)pageNumber andOrientation:(NSString*)interfaceOrientation; +- (void)takeScreenshotFromView:(UIWebView*)webView forPage:(int)pageNumber andOrientation:(NSString*)interfaceOrientation; +- (void)placeScreenshotForView:(UIWebView*)webView andPage:(int)pageNumber andOrientation:(NSString*)interfaceOrientation; + +#pragma mark - UIGestureRecognizer + +- (void)handleInterceptedTouch:(NSNotification*)notification; +- (void)userDidTap:(UITouch*)touch; +- (void)userDidScroll:(UITouch*)touch; -#pragma mark - GESTURES -- (void)handleInterceptedTouch:(NSNotification *)notification; -- (void)userDidTap:(UITouch *)touch; -- (void)userDidScroll:(UITouch *)touch; +#pragma mark - Page Scrolling -#pragma mark - PAGE SCROLLING - (void)setCurrentPageHeight; - (int)getCurrentPageOffset; - (void)scrollUpCurrentPage:(int)offset animating:(BOOL)animating; - (void)scrollDownCurrentPage:(int)offset animating:(BOOL)animating; -- (void)scrollPage:(UIWebView *)webView to:(NSString *)offset animating:(BOOL)animating; +- (void)scrollPage:(UIWebView*)webView to:(NSString*)offset animating:(BOOL)animating; - (void)handleAnchor:(BOOL)animating; -#pragma mark - BARS VISIBILITY +#pragma mark - Navigation Bars + - (CGRect)getNewNavigationFrame:(BOOL)hidden; - (void)toggleBars; - (void)showBars; - (void)showNavigationBar; -- (void)hideBars:(NSNumber *)animated; -- (void)handleBookProtocol:(NSURL *)url; +- (void)hideBars:(NSNumber*)animated; +- (void)handleBookProtocol:(NSURL*)url; + +#pragma mark - Orientation + +- (NSString*)getCurrentInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation; -#pragma mark - ORIENTATION -- (NSString *)getCurrentInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation; +#pragma mark - Index View -#pragma mark - INDEX VIEW -- (BOOL)isIndexView:(UIWebView *)webView; +- (BOOL)isIndexView:(UIWebView*)webView; @end diff --git a/BakerView/BakerViewController.m b/BakerView/BKRBookViewController.m similarity index 73% rename from BakerView/BakerViewController.m rename to BakerView/BKRBookViewController.m index 1534def6..6fb85aca 100755 --- a/BakerView/BakerViewController.m +++ b/BakerView/BKRBookViewController.m @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -33,68 +34,56 @@ #import #import -#import "BakerViewController.h" -#import "SSZipArchive.h" -#import "PageTitleLabel.h" -#import "Utils.h" +#import "BKRBookViewController.h" +#import "BKRPageTitleLabel.h" +#import "BKRUtils.h" +#import "BKRSettings.h" +#import "NSObject+BakerExtensions.h" +#import "UIScreen+BakerExtensions.h" #define INDEX_FILE_NAME @"index.html" #define URL_OPEN_MODALLY @"referrer=Baker" #define URL_OPEN_EXTERNAL @"referrer=Safari" - -// SCREENSHOT +// Screenshots #define MAX_SCREENSHOT_AFTER_CP 10 #define MAX_SCREENSHOT_BEFORE_CP 10 -@implementation BakerViewController - -#pragma mark - SYNTHESIS -@synthesize book; -@synthesize scrollView; -@synthesize currPage; -@synthesize currentPageNumber; -@synthesize barsHidden; +@implementation BKRBookViewController #pragma mark - INIT -- (id)initWithBook:(BakerBook *)bakerBook { +- (id)initWithBook:(BKRBook *)bakerBook { self = [super init]; if (self) { NSLog(@"[BakerView] Init book view..."); - self.book = bakerBook; + + _book = bakerBook; if ([self respondsToSelector:@selector(automaticallyAdjustsScrollViewInsets)]) { // Only available in iOS 7 + self.automaticallyAdjustsScrollViewInsets = NO; } - // ****** DEVICE SCREEN BOUNDS screenBounds = [[UIScreen mainScreen] bounds]; NSLog(@"[BakerView] Device Screen (WxH): %fx%f.", screenBounds.size.width, screenBounds.size.height); - // ****** SUPPORTED ORIENTATION FROM PLIST - supportedOrientation = [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"UISupportedInterfaceOrientations"] retain]; + supportedOrientation = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UISupportedInterfaceOrientations"]; - - NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - if (![[NSFileManager defaultManager] fileExistsAtPath:cachePath]) { - [[NSFileManager defaultManager] createDirectoryAtPath:cachePath withIntermediateDirectories:YES attributes:nil error:nil]; + if (![[NSFileManager defaultManager] fileExistsAtPath:self.bkrCachePath]) { + [[NSFileManager defaultManager] createDirectoryAtPath:self.bkrCachePath withIntermediateDirectories:YES attributes:nil error:nil]; } - // ****** SCREENSHOTS DIRECTORY //TODO: set in load book only if is necessary - defaultScreeshotsPath = [[[cachePath stringByAppendingPathComponent:@"screenshots"] stringByAppendingPathComponent:book.ID] retain]; - + defaultScreeshotsPath = [[self.bkrCachePath stringByAppendingPathComponent:@"screenshots"] stringByAppendingPathComponent:bakerBook.ID]; // ****** STATUS FILE - bookStatus = [[BakerBookStatus alloc] initWithBookId:book.ID]; + bookStatus = [[BKRBookStatus alloc] initWithBookId:bakerBook.ID]; NSLog(@"[BakerView] Status: page %@ @ scrollIndex %@px.", bookStatus.page, bookStatus.scrollIndex); - // ****** Initialize audio session for html5 audio AVAudioSession *audioSession = [AVAudioSession sharedInstance]; BOOL ok; @@ -106,13 +95,13 @@ - (id)initWithBook:(BakerBook *)bakerBook { } // ****** BOOK ENVIRONMENT - pages = [[NSMutableArray array] retain]; - toLoad = [[NSMutableArray array] retain]; + pages = [NSMutableArray array]; + toLoad = [NSMutableArray array]; - pageDetails = [[NSMutableArray array] retain]; + pageDetails = [NSMutableArray array]; - attachedScreenshotPortrait = [[NSMutableDictionary dictionary] retain]; - attachedScreenshotLandscape = [[NSMutableDictionary dictionary] retain]; + attachedScreenshotPortrait = [NSMutableDictionary dictionary]; + attachedScreenshotLandscape = [NSMutableDictionary dictionary]; tapNumber = 0; stackedScrollingAnimations = 0; // TODO: CHECK IF STILL USED! @@ -128,7 +117,7 @@ - (id)initWithBook:(BakerBook *)bakerBook { shouldForceOrientationUpdate = YES; adjustViewsOnAppDidBecomeActive = NO; - barsHidden = NO; + _barsHidden = NO; webViewBackground = nil; @@ -136,51 +125,62 @@ - (id)initWithBook:(BakerBook *)bakerBook { anchorFromURL = nil; // TODO: LOAD BOOK METHOD IN VIEW DID LOAD - [self loadBookWithBookPath:book.path]; + [self loadBookWithBookPath:bakerBook.path]; } return self; } - (void)viewDidLoad { [super viewDidLoad]; - self.navigationItem.title = book.title; + self.navigationItem.title = self.book.title; // ****** SET THE INITIAL SIZE FOR EVERYTHING // Avoids strange animations when opening [self setPageSize:[self getCurrentInterfaceOrientation:self.interfaceOrientation]]; - + // SOCIAL MEDIA INTEGRATION - START + if ([BKRSettings sharedSettings].showSocialShareButton) { + self.shareButton = [[UIBarButtonItem alloc] + initWithBarButtonSystemItem:UIBarButtonSystemItemAction + target:self + action:@selector(shareBtnAction:)]; + self.navigationItem.rightBarButtonItem = self.shareButton; + } + // SOCIAL MEDIA INTEGRATION - END + // ****** SCROLLVIEW INIT - self.scrollView = [[[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, pageWidth, pageHeight)] autorelease]; - scrollView.showsHorizontalScrollIndicator = YES; - scrollView.showsVerticalScrollIndicator = NO; - scrollView.delaysContentTouches = NO; - scrollView.pagingEnabled = YES; - scrollView.delegate = self; + self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, pageWidth, pageHeight)]; + self.scrollView.showsHorizontalScrollIndicator = YES; + self.scrollView.showsVerticalScrollIndicator = NO; + self.scrollView.delaysContentTouches = NO; + self.scrollView.pagingEnabled = YES; + self.scrollView.delegate = self; - scrollView.scrollEnabled = [book.bakerPageTurnSwipe boolValue]; - scrollView.backgroundColor = [Utils colorWithHexString:book.bakerBackground]; + self.scrollView.scrollEnabled = [self.book.bakerPageTurnSwipe boolValue]; + self.scrollView.backgroundColor = [BKRUtils colorWithHexString:self.book.bakerBackground]; - [self.view addSubview:scrollView]; + [self.view addSubview:self.scrollView]; // ****** BAKER BACKGROUND backgroundImageLandscape = nil; backgroundImagePortrait = nil; - NSString *backgroundPathLandscape = book.bakerBackgroundImageLandscape; + NSString *backgroundPathLandscape = self.book.bakerBackgroundImageLandscape; if (backgroundPathLandscape != nil) { - backgroundPathLandscape = [book.path stringByAppendingPathComponent:backgroundPathLandscape]; - backgroundImageLandscape = [[UIImage imageWithContentsOfFile:backgroundPathLandscape] retain]; + backgroundPathLandscape = [self.book.path stringByAppendingPathComponent:backgroundPathLandscape]; + backgroundImageLandscape = [UIImage imageWithContentsOfFile:backgroundPathLandscape]; } - NSString *backgroundPathPortrait = book.bakerBackgroundImagePortrait; + NSString *backgroundPathPortrait = self.book.bakerBackgroundImagePortrait; if (backgroundPathPortrait != nil) { - backgroundPathPortrait = [book.path stringByAppendingPathComponent:backgroundPathPortrait]; - backgroundImagePortrait = [[UIImage imageWithContentsOfFile:backgroundPathPortrait] retain]; + backgroundPathPortrait = [self.book.path stringByAppendingPathComponent:backgroundPathPortrait]; + backgroundImagePortrait = [UIImage imageWithContentsOfFile:backgroundPathPortrait]; } + } + - (void)viewWillAppear:(BOOL)animated { if (!currentPageWillAppearUnderModal) { @@ -210,6 +210,78 @@ - (void)handleApplicationWillResignActive:(NSNotification *)notification { [self saveBookStatusWithScrollIndex]; adjustViewsOnAppDidBecomeActive = YES; } + +// SOCIAL MEDIA INTEGRATION - START + +// Reduce Image size before sharing +- (UIImage *)resizeImage:(UIImage*)image { + + int maxw=320; + CGSize newSize = CGSizeMake(maxw, image.size.height * (maxw/image.size.width)); + + CGRect newRect = CGRectIntegral(CGRectMake(0, 0, newSize.width, newSize.height)); + CGImageRef imageRef = image.CGImage; + + UIGraphicsBeginImageContextWithOptions(newSize, NO, 0); + CGContextRef context = UIGraphicsGetCurrentContext(); + + // Set the quality level to use when rescaling + CGContextSetInterpolationQuality(context, kCGInterpolationHigh); + CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, newSize.height); + + CGContextConcatCTM(context, flipVertical); + // Draw into the context; this scales the image + CGContextDrawImage(context, newRect, imageRef); + + // Get the resized image from the context and a UIImage + CGImageRef newImageRef = CGBitmapContextCreateImage(context); + UIImage *newImage = [UIImage imageWithCGImage:newImageRef]; + + CGImageRelease(newImageRef); + UIGraphicsEndImageContext(); + + return newImage; +} + + + +- (void) shareBtnAction:(UIButton *)sender { + + if (![self checkScreeshotForPage:_currentPageNumber andOrientation:[self getCurrentInterfaceOrientation:self.interfaceOrientation]]) { + [self takeScreenshotFromView:_currPage forPage:_currentPageNumber andOrientation:[self getCurrentInterfaceOrientation:self.interfaceOrientation]]; + } + + NSString *screenshotFile = [cachedScreenshotsPath stringByAppendingPathComponent:[NSString stringWithFormat:@"screenshot-%@-%i.jpg", [self getCurrentInterfaceOrientation:self.interfaceOrientation], _currentPageNumber]]; + + // items to share + + NSString *appName=[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"]; + NSString *url = [ _book.url stringByReplacingOccurrencesOfString:@"book" withString:@"http"]; + + NSString *message = [NSString stringWithFormat:NSLocalizedString(@"SOCIAL_SHARE_BUTTON_MESSAGE", nil), _book.title, appName, url ]; + + + UIImage *image = [UIImage imageWithContentsOfFile: screenshotFile]; + UIImage *small = [self resizeImage:image]; + + NSArray *items = @[message, small]; + + + // create the controller + UIActivityViewController *controller = [[UIActivityViewController alloc]initWithActivityItems:items applicationActivities:nil]; + + // Exclude Irrelevant Parts + controller.excludedActivityTypes = @[UIActivityTypePostToWeibo,UIActivityTypePrint,UIActivityTypeCopyToPasteboard,UIActivityTypeAssignToContact,UIActivityTypeSaveToCameraRoll]; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + [self presentViewController:controller animated:YES completion:nil]; + } else { + UIPopoverController *popup = [[UIPopoverController alloc] initWithContentViewController:controller]; + [popup presentPopoverFromBarButtonItem:self.shareButton permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; + } + +} +// SOCIAL MEDIA INTEGRATION - END + - (void)viewDidAppear:(BOOL)animated { if (!currentPageWillAppearUnderModal) { @@ -218,11 +290,11 @@ - (void)viewDidAppear:(BOOL)animated { if (![self forceOrientationUpdate]) { [self willRotateToInterfaceOrientation:self.interfaceOrientation duration:0]; - [self performSelector:@selector(hideBars:) withObject:[NSNumber numberWithBool:YES] afterDelay:0.5]; + [self performSelector:@selector(hideBars:) withObject:@YES afterDelay:0.5]; // Condition to make sure we only call startReading the first time this callback is invoked // Fixes page reload on coming back from fullscreen video (#611) - if (currPage == nil) { + if (self.currPage == nil) { [self startReading]; } @@ -258,20 +330,20 @@ - (BOOL)loadBookWithBookPath:(NSString *)bookPath { // ****** SET STARTING PAGE int lastPageViewed = [bookStatus.page intValue]; - int bakerStartAtPage = [book.bakerStartAtPage intValue]; - currentPageNumber = 1; + int bakerStartAtPage = [self.book.bakerStartAtPage intValue]; + self.currentPageNumber = 1; if (currentPageFirstLoading && lastPageViewed != 0) { - currentPageNumber = lastPageViewed; + self.currentPageNumber = lastPageViewed; } else if (bakerStartAtPage < 0) { - currentPageNumber = MAX(1, totalPages + bakerStartAtPage + 1); + self.currentPageNumber = MAX(1, totalPages + bakerStartAtPage + 1); } else if (bakerStartAtPage > 0) { - currentPageNumber = MIN(totalPages, bakerStartAtPage); + self.currentPageNumber = MIN(totalPages, bakerStartAtPage); } - bookStatus.page = [NSNumber numberWithInt:currentPageNumber]; + bookStatus.page = @(self.currentPageNumber); // ****** SET SCREENSHOTS FOLDER - NSString *screenshotFolder = book.bakerPageScreenshots; + NSString *screenshotFolder = self.book.bakerPageScreenshots; if (screenshotFolder) { // When a screenshots folder is specified in book.json cachedScreenshotsPath = [bookPath stringByAppendingPathComponent:screenshotFolder]; @@ -283,7 +355,6 @@ - (BOOL)loadBookWithBookPath:(NSString *)bookPath { } NSLog(@"[BakerView] Screenshots stored at: %@", cachedScreenshotsPath); - [cachedScreenshotsPath retain]; return YES; } @@ -298,23 +369,20 @@ - (void)cleanupBookEnvironment { - (void)resetPageSlots { //NSLog(@"[BakerView] Reset leftover page slot"); - if (currPage) { - [currPage setDelegate:nil]; - [currPage removeFromSuperview]; - [currPage release]; + if (self.currPage) { + [self.currPage setDelegate:nil]; + [self.currPage removeFromSuperview]; } if (nextPage) { [nextPage setDelegate:nil]; [nextPage removeFromSuperview]; - [nextPage release]; } if (prevPage) { [prevPage setDelegate:nil]; [prevPage removeFromSuperview]; - [prevPage release]; } - currPage = nil; + self.currPage = nil; nextPage = nil; prevPage = nil; } @@ -323,7 +391,7 @@ - (void)resetPageDetails { for (NSMutableDictionary *details in pageDetails) { for (NSString *key in details) { - UIView *value = [details objectForKey:key]; + UIView *value = details[key]; [value removeFromSuperview]; } } @@ -331,23 +399,23 @@ - (void)resetPageDetails { [pageDetails removeAllObjects]; } - (void)buildPageArray { - for (id page in book.contents) { + for (id page in self.book.contents) { NSString *pageFile = nil; if ([page isKindOfClass:[NSString class]]) { - pageFile = [book.path stringByAppendingPathComponent:page]; + pageFile = [self.book.path stringByAppendingPathComponent:page]; } else if ([page isKindOfClass:[NSDictionary class]]) { - pageFile = [book.path stringByAppendingPathComponent:[page objectForKey:@"url"]]; + pageFile = [self.book.path stringByAppendingPathComponent:page[@"url"]]; } - if ([[NSFileManager defaultManager] fileExistsAtPath:pageFile]) { + if (pageFile != nil && [[NSFileManager defaultManager] fileExistsAtPath:pageFile]) { [pages addObject:pageFile]; } else { - NSLog(@"[BakerView] ERROR: Page %@ does not exist in %@.", page, book.path); + NSLog(@"[BakerView] ERROR: Page %@ does not exist in %@.", page, self.book.path); } } - totalPages = [pages count]; + totalPages = (int)[pages count]; NSLog(@"[BakerView] Pages in this book: %d", totalPages); } - (void)startReading { @@ -362,9 +430,8 @@ - (void)startReading { if (indexViewController != nil) { // first of all, we need to clean the indexview if it exists. [indexViewController.view removeFromSuperview]; - [indexViewController release]; } - indexViewController = [[IndexViewController alloc] initWithBook:book fileName:INDEX_FILE_NAME webViewDelegate:self]; + indexViewController = [[BKRIndexViewController alloc] initWithBook:self.book fileName:INDEX_FILE_NAME webViewDelegate:self]; [self.view addSubview:indexViewController.view]; [indexViewController loadContent]; @@ -372,12 +439,12 @@ - (void)startReading { [self addPageLoading:0]; - if ([book.bakerRendering isEqualToString:@"three-cards"]) { - if (currentPageNumber != totalPages) { + if ([self.book.bakerRendering isEqualToString:@"three-cards"]) { + if (self.currentPageNumber != totalPages) { [self addPageLoading:+1]; } - if (currentPageNumber != 1) { + if (self.currentPageNumber != 1) { [self addPageLoading:-1]; } } @@ -389,30 +456,28 @@ - (void)buildPageDetails { for (int i = 0; i < totalPages; i++) { - UIColor *foregroundColor = [Utils colorWithHexString:book.bakerPageNumbersColor]; + UIColor *foregroundColor = [BKRUtils colorWithHexString:self.book.bakerPageNumbersColor]; // ****** Background UIImageView *backgroundView = [[UIImageView alloc] initWithFrame:CGRectMake(pageWidth * i, 0, pageWidth, pageHeight)]; [self setImageFor:backgroundView]; - [scrollView addSubview:backgroundView]; - [backgroundView release]; + [self.scrollView addSubview:backgroundView]; // ****** Spinners UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; spinner.backgroundColor = [UIColor clearColor]; spinner.color = foregroundColor; - spinner.alpha = [book.bakerPageNumbersAlpha floatValue]; + spinner.alpha = [self.book.bakerPageNumbersAlpha floatValue]; CGRect frame = spinner.frame; frame.origin.x = pageWidth * i + (pageWidth - frame.size.width) / 2; frame.origin.y = (pageHeight - frame.size.height) / 2; spinner.frame = frame; - [scrollView addSubview:spinner]; + [self.scrollView addSubview:spinner]; [spinner startAnimating]; - [spinner release]; // ****** Numbers @@ -421,22 +486,20 @@ - (void)buildPageDetails { number.font = [UIFont fontWithName:@"Helvetica" size:40.0]; number.textColor = foregroundColor; number.textAlignment = NSTextAlignmentCenter; - number.alpha = [book.bakerPageNumbersAlpha floatValue]; + number.alpha = [self.book.bakerPageNumbersAlpha floatValue]; number.text = [NSString stringWithFormat:@"%d", i + 1]; - if ([book.bakerStartAtPage intValue] < 0) { + if ([self.book.bakerStartAtPage intValue] < 0) { number.text = [NSString stringWithFormat:@"%d", totalPages - i]; } - [scrollView addSubview:number]; - [number release]; + [self.scrollView addSubview:number]; // ****** Title - PageTitleLabel *title = [[PageTitleLabel alloc]initWithFile:[pages objectAtIndex: i] color:foregroundColor alpha:[book.bakerPageNumbersAlpha floatValue]]; + BKRPageTitleLabel *title = [[BKRPageTitleLabel alloc]initWithFile:pages[i] color:foregroundColor alpha:[self.book.bakerPageNumbersAlpha floatValue]]; [title setX:(pageWidth * i + ((pageWidth - title.frame.size.width) / 2)) Y:(pageHeight / 2 + 20)]; - [scrollView addSubview:title]; - [title release]; + [self.scrollView addSubview:title]; // ****** Store instances for later use @@ -457,10 +520,10 @@ - (void)setImageFor:(UIImageView *)view { } - (void)updateBookLayout { //NSLog(@"[BakerView] Prevent page from changing until layout is updated"); - [self lockPage:[NSNumber numberWithBool:YES]]; + [self lockPage:@YES]; [self showPageDetails]; - if ([book.bakerRendering isEqualToString:@"screenshots"]) { + if ([self.book.bakerRendering isEqualToString:@"screenshots"]) { // TODO: BE SURE TO KNOW THE CORRECT CURRENT PAGE! [self removeScreenshots]; [self updateScreenshots]; @@ -468,53 +531,48 @@ - (void)updateBookLayout { [self adjustScrollViewPosition]; - [self setFrame:[self frameForPage:currentPageNumber] forPage:currPage]; - [self setFrame:[self frameForPage:currentPageNumber + 1] forPage:nextPage]; - [self setFrame:[self frameForPage:currentPageNumber - 1] forPage:prevPage]; + [self setFrame:[self frameForPage:self.currentPageNumber] forPage:self.currPage]; + [self setFrame:[self frameForPage:self.currentPageNumber + 1] forPage:nextPage]; + [self setFrame:[self frameForPage:self.currentPageNumber - 1] forPage:prevPage]; - [scrollView scrollRectToVisible:[self frameForPage:currentPageNumber] animated:NO]; + [self.scrollView scrollRectToVisible:[self frameForPage:self.currentPageNumber] animated:NO]; //NSLog(@"[BakerView] Unlock page changing"); - [self lockPage:[NSNumber numberWithBool:NO]]; + [self lockPage:@NO]; } + - (void)adjustScrollViewPosition { int scrollViewY = 0; - - if (SYSTEM_VERSION_LESS_THAN(@"7.0") && !barsHidden) { - scrollViewY = -20; - } - [UIView animateWithDuration:UINavigationControllerHideShowBarDuration - animations:^{ scrollView.frame = CGRectMake(0, scrollViewY, pageWidth, pageHeight); } - completion:^(BOOL finished) {}]; + animations:^{ self.scrollView.frame = CGRectMake(0, scrollViewY, pageWidth, pageHeight); } + completion:nil]; } -- (void)setPageSize:(NSString *)orientation { + +- (void)setPageSize:(NSString*)orientation { NSLog(@"[BakerView] Set size for orientation: %@", orientation); - pageWidth = screenBounds.size.width; - pageHeight = screenBounds.size.height; + pageWidth = [[UIScreen mainScreen] bkrWidthForOrientationName:orientation]; + pageHeight = [[UIScreen mainScreen] bkrHeightForOrientationName:orientation]; - if ([orientation isEqualToString:@"landscape"]) { - pageWidth = screenBounds.size.height; - pageHeight = screenBounds.size.width; - } + [self setTappableAreaSizeForOrientation:orientation]; - [self setTappableAreaSize]; - - scrollView.contentSize = CGSizeMake(pageWidth * totalPages, pageHeight); + self.scrollView.contentSize = CGSizeMake(pageWidth * totalPages, pageHeight); } -- (void)setTappableAreaSize { +- (void)setTappableAreaSizeForOrientation:(NSString*)orientation { //NSLog(@"[BakerView] Set tappable area size"); - int tappableAreaSize = screenBounds.size.width/16; - if (screenBounds.size.width < 768) { - tappableAreaSize = screenBounds.size.width/8; + CGFloat screenWidth = [[UIScreen mainScreen] bkrWidthForOrientationName:orientation]; + int tappableAreaSize; + if ([orientation isEqualToString:@"portrait"]) { + tappableAreaSize = screenWidth / 16; + } else { + tappableAreaSize = screenWidth / 7; } - upTapArea = CGRectMake(tappableAreaSize, 0, pageWidth - (tappableAreaSize * 2), tappableAreaSize); - downTapArea = CGRectMake(tappableAreaSize, pageHeight - tappableAreaSize, pageWidth - (tappableAreaSize * 2), tappableAreaSize); - leftTapArea = CGRectMake(0, tappableAreaSize, tappableAreaSize, pageHeight - (tappableAreaSize * 2)); - rightTapArea = CGRectMake(pageWidth - tappableAreaSize, tappableAreaSize, tappableAreaSize, pageHeight - (tappableAreaSize * 2)); + upTapArea = CGRectMake(tappableAreaSize, 0, screenWidth - (tappableAreaSize * 2), tappableAreaSize); + downTapArea = CGRectMake(tappableAreaSize, screenWidth - tappableAreaSize, screenWidth - (tappableAreaSize * 2), tappableAreaSize); + leftTapArea = CGRectMake(0, tappableAreaSize, tappableAreaSize, screenWidth - (tappableAreaSize * 2)); + rightTapArea = CGRectMake(screenWidth - tappableAreaSize, tappableAreaSize, tappableAreaSize, screenWidth - (tappableAreaSize * 2)); } - (void)showPageDetails { //NSLog(@"[BakerView] Show page details for the book pages"); @@ -522,19 +580,19 @@ - (void)showPageDetails { // TODO: IS THIS NEEDED ? for (NSMutableDictionary *details in pageDetails) { for (NSString *key in details) { - UIView *value = [details objectForKey:key]; + UIView *value = details[key]; value.hidden = YES; } } for (int i = 0; i < totalPages; i++) { - if (pageDetails.count > i && [pageDetails objectAtIndex:i] != nil) { + if (pageDetails.count > i && pageDetails[i] != nil) { - NSDictionary *details = [NSDictionary dictionaryWithDictionary:[pageDetails objectAtIndex:i]]; + NSDictionary *details = [NSDictionary dictionaryWithDictionary:pageDetails[i]]; for (NSString *key in details) { - UIView *value = [details objectForKey:key]; + UIView *value = details[key]; if (value != nil) { CGRect frame = value.frame; @@ -579,9 +637,9 @@ - (void)showPageDetails { } } - (void)setFrame:(CGRect)frame forPage:(UIWebView *)page { - if (page && [page.superview isEqual:scrollView]) { + if (page && [page.superview isEqual:self.scrollView]) { page.frame = frame; - [scrollView bringSubviewToFront:page]; + [self.scrollView bringSubviewToFront:page]; } } @@ -591,7 +649,6 @@ - (void)setupWebView:(UIWebView *)webView { if (webViewBackground == nil) { webViewBackground = webView.backgroundColor; - [webViewBackground retain]; } webView.backgroundColor = [UIColor clearColor]; @@ -599,9 +656,10 @@ - (void)setupWebView:(UIWebView *)webView { webView.delegate = self; - webView.mediaPlaybackRequiresUserAction = ![book.bakerMediaAutoplay boolValue]; - webView.scalesPageToFit = [book.zoomable boolValue]; - BOOL verticalBounce = [book.bakerVerticalBounce boolValue]; + webView.mediaPlaybackRequiresUserAction = ![self.book.bakerMediaAutoplay boolValue]; + webView.allowsInlineMediaPlayback = YES; + webView.scalesPageToFit = [self.book.zoomable boolValue]; + BOOL verticalBounce = [self.book.bakerVerticalBounce boolValue]; for (UIView *subview in webView.subviews) { if ([subview isKindOfClass:[UIScrollView class]]) { @@ -633,31 +691,31 @@ - (BOOL)changePage:(int)page { if (page < 1) { - currentPageNumber = 1; + self.currentPageNumber = 1; } else if (page > totalPages) { - currentPageNumber = totalPages; + self.currentPageNumber = totalPages; } - else if (page != currentPageNumber) + else if (page != self.currentPageNumber) { // While we are tapping, we don't want scrolling event to get in the way - scrollView.scrollEnabled = NO; + self.scrollView.scrollEnabled = NO; stackedScrollingAnimations++; - lastPageNumber = currentPageNumber; - currentPageNumber = page; + lastPageNumber = self.currentPageNumber; + self.currentPageNumber = page; - tapNumber = tapNumber + (lastPageNumber - currentPageNumber); + tapNumber = tapNumber + (lastPageNumber - self.currentPageNumber); - [self hideBars:[NSNumber numberWithBool:YES]]; - [scrollView scrollRectToVisible:[self frameForPage:currentPageNumber] animated:YES]; + [self hideBars:@YES]; + [self.scrollView scrollRectToVisible:[self frameForPage:self.currentPageNumber] animated:YES]; [self gotoPageDelayer]; pageChanged = YES; } - bookStatus.page = [NSNumber numberWithInt:currentPageNumber]; + bookStatus.page = @(self.currentPageNumber); return pageChanged; } @@ -674,17 +732,17 @@ - (void)gotoPageDelayer { } - (void)gotoPage { - NSString *path = [NSString stringWithString:[pages objectAtIndex:currentPageNumber - 1]]; + NSString *path = [NSString stringWithString:pages[self.currentPageNumber - 1]]; if ([[NSFileManager defaultManager] fileExistsAtPath:path] && tapNumber != 0) { //NSLog(@"[BakerView] Goto page -> %@", [[NSFileManager defaultManager] displayNameAtPath:path]); - if ([book.bakerRendering isEqualToString:@"three-cards"]) + if ([self.book.bakerRendering isEqualToString:@"three-cards"]) { // ****** THREE CARD VIEW METHOD // Dispatch blur event on old current page - [Utils webView:currPage dispatchHTMLEvent:@"blur"]; + [BKRUtils webView:self.currPage dispatchHTMLEvent:@"blur"]; // Calculate move direction and normalize tapNumber int direction = 1; @@ -700,15 +758,17 @@ - (void)gotoPage { // Moved away for more than 2 pages: RELOAD ALL pages [toLoad removeAllObjects]; - [currPage removeFromSuperview]; + [self.currPage removeFromSuperview]; [nextPage removeFromSuperview]; [prevPage removeFromSuperview]; [self addPageLoading:0]; - if (currentPageNumber < totalPages) + if (self.currentPageNumber < totalPages) { [self addPageLoading:+1]; - if (currentPageNumber > 1) + } + if (self.currentPageNumber > 1) { [self addPageLoading:-1]; + } } else { @@ -732,11 +792,11 @@ - (void)gotoPage { // Adjust pages slot in the stack to reflect the webviews pointer change for (int i = 0; i < [toLoad count]; i++) { - tmpSlot = -1 * [[[toLoad objectAtIndex:i] valueForKey:@"slot"] intValue]; - [[toLoad objectAtIndex:i] setObject:[NSNumber numberWithInt:tmpSlot] forKey:@"slot"]; + tmpSlot = -1 * [[toLoad[i] valueForKey:@"slot"] intValue]; + toLoad[i][@"slot"] = @(tmpSlot); } - [currPage removeFromSuperview]; + [self.currPage removeFromSuperview]; [self addPageLoading:0]; } else if (tapNumber == 1) { @@ -745,22 +805,22 @@ - (void)gotoPage { // ****** Move LEFT <<< [prevPage removeFromSuperview]; UIWebView *tmpView = prevPage; - prevPage = currPage; - currPage = nextPage; + prevPage = self.currPage; + self.currPage = nextPage; nextPage = tmpView; } else { // ****** Move RIGHT >>> [nextPage removeFromSuperview]; UIWebView *tmpView = nextPage; - nextPage = currPage; - currPage = prevPage; + nextPage = self.currPage; + self.currPage = prevPage; prevPage = tmpView; } // Adjust pages slot in the stack to reflect the webviews pointer change for (int i = 0; i < [toLoad count]; i++) { - tmpSlot = [[[toLoad objectAtIndex:i] valueForKey:@"slot"] intValue]; + tmpSlot = [[toLoad[i] valueForKey:@"slot"] intValue]; if (direction < 0) { if (tmpSlot == +1) { tmpSlot = 0; @@ -778,14 +838,14 @@ - (void)gotoPage { tmpSlot = -1; } } - [[toLoad objectAtIndex:i] setObject:[NSNumber numberWithInt:tmpSlot] forKey:@"slot"]; + toLoad[i][@"slot"] = @(tmpSlot); } // Since we are not loading anything we have to reset the delayer flag currentPageIsDelayingLoading = NO; // Dispatch focus event on new current page - [Utils webView:currPage dispatchHTMLEvent:@"focus"]; + [BKRUtils webView:self.currPage dispatchHTMLEvent:@"focus"]; // Dispatch BakerViewPage analytics event [[NSNotificationCenter defaultCenter] postNotificationName:@"BakerViewPage" object:self]; // -> Baker Analytics Event @@ -798,13 +858,13 @@ - (void)gotoPage { // REMOVE OTHER NEXT page from toLoad stack for (int i = 0; i < [toLoad count]; i++) { - if ([[[toLoad objectAtIndex:i] valueForKey:@"slot"] intValue] == +1) { + if ([[toLoad[i] valueForKey:@"slot"] intValue] == +1) { [toLoad removeObjectAtIndex:i]; } } // PRELOAD NEXT page - if (currentPageNumber < totalPages) { + if (self.currentPageNumber < totalPages) { [self addPageLoading:+1]; } @@ -812,13 +872,13 @@ - (void)gotoPage { // REMOVE OTHER PREV page from toLoad stack for (int i = 0; i < [toLoad count]; i++) { - if ([[[toLoad objectAtIndex:i] valueForKey:@"slot"] intValue] == -1) { + if ([[toLoad[i] valueForKey:@"slot"] intValue] == -1) { [toLoad removeObjectAtIndex:i]; } } // PRELOAD PREV page - if (currentPageNumber > 1) { + if (self.currentPageNumber > 1) { [self addPageLoading:-1]; } } @@ -831,12 +891,12 @@ - (void)gotoPage { tapNumber = 0; [toLoad removeAllObjects]; - [currPage removeFromSuperview]; + [self.currPage removeFromSuperview]; [self updateScreenshots]; - if (![self checkScreeshotForPage:currentPageNumber andOrientation:[self getCurrentInterfaceOrientation:self.interfaceOrientation]]) { - [self lockPage:[NSNumber numberWithBool:YES]]; + if (![self checkScreeshotForPage:self.currentPageNumber andOrientation:[self getCurrentInterfaceOrientation:self.interfaceOrientation]]) { + [self lockPage:@YES]; } [self addPageLoading:0]; @@ -847,15 +907,15 @@ - (void)gotoPage { - (void)lockPage:(NSNumber *)lock { if ([lock boolValue]) { - if (scrollView.scrollEnabled) { - scrollView.scrollEnabled = NO; + if (self.scrollView.scrollEnabled) { + self.scrollView.scrollEnabled = NO; } currentPageIsLocked = YES; } else { if (stackedScrollingAnimations == 0) { - scrollView.scrollEnabled = [book.bakerPageTurnSwipe boolValue]; // YES by default, NO if specified + self.scrollView.scrollEnabled = [self.book.bakerPageTurnSwipe boolValue]; // YES by default, NO if specified } currentPageIsLocked = NO; } @@ -863,8 +923,8 @@ - (void)lockPage:(NSNumber *)lock { - (void)addPageLoading:(int)slot { //NSLog(@"[BakerView] Add page to the loding queue"); - NSArray *objs = [NSArray arrayWithObjects:[NSNumber numberWithInt:slot], [NSNumber numberWithInt:currentPageNumber + slot], nil]; - NSArray *keys = [NSArray arrayWithObjects:@"slot", @"page", nil]; + NSArray *objs = @[@(slot), @(self.currentPageNumber + slot)]; + NSArray *keys = @[@"slot", @"page"]; if (slot == 0) { [toLoad insertObject:[NSMutableDictionary dictionaryWithObjects:objs forKeys:keys] atIndex:0]; @@ -875,8 +935,8 @@ - (void)addPageLoading:(int)slot { - (void)handlePageLoading { if ([toLoad count] != 0) { - int slot = [[[toLoad objectAtIndex:0] valueForKey:@"slot"] intValue]; - int page = [[[toLoad objectAtIndex:0] valueForKey:@"page"] intValue]; + int slot = [[toLoad[0] valueForKey:@"slot"] intValue]; + int page = [[toLoad[0] valueForKey:@"page"] intValue]; //NSLog(@"[BakerView] Handle loading of slot %d with page %d", slot, page); @@ -887,7 +947,7 @@ - (void)handlePageLoading { - (void)loadSlot:(int)slot withPage:(int)page { //NSLog(@"[BakerView] Setting up slot %d with page %d.", slot, page); - UIWebView *webView = [[[UIWebView alloc] init] autorelease]; + UIWebView *webView = [[UIWebView alloc] init]; [self setupWebView:webView]; webView.frame = [self frameForPage:page]; @@ -897,17 +957,16 @@ - (void)loadSlot:(int)slot withPage:(int)page { // Since pointers can change at any time we've got to handle them directly on a slot basis. // Save the page pointer to a temp view to avoid code redundancy make Baker go apeshit. if (slot == 0) { - if (page == currentPageNumber) + if (page == self.currentPageNumber) [[NSNotificationCenter defaultCenter] postNotificationName:@"BakerViewPage" object:self]; // -> Baker Analytics Event - if (currPage) { - currPage.delegate = nil; - if ([currPage isLoading]) { - [currPage stopLoading]; + if (self.currPage) { + self.currPage.delegate = nil; + if ([self.currPage isLoading]) { + [self.currPage stopLoading]; } - [currPage release]; } - currPage = [webView retain]; + self.currPage = webView; currentPageHasChanged = YES; } else if (slot == +1) { @@ -917,9 +976,8 @@ - (void)loadSlot:(int)slot withPage:(int)page { if ([nextPage isLoading]) { [nextPage stopLoading]; } - [nextPage release]; } - nextPage = [webView retain]; + nextPage = webView; } else if (slot == -1) { @@ -928,20 +986,19 @@ - (void)loadSlot:(int)slot withPage:(int)page { if ([prevPage isLoading]) { [prevPage stopLoading]; } - [prevPage release]; } - prevPage = [webView retain]; + prevPage = webView; } - ((UIScrollView *)[[webView subviews] objectAtIndex:0]).pagingEnabled = [book.bakerVerticalPagination boolValue]; + ((UIScrollView *)[webView subviews][0]).pagingEnabled = [self.book.bakerVerticalPagination boolValue]; - [scrollView addSubview:webView]; + [self.scrollView addSubview:webView]; [self loadWebView:webView withPage:page]; } - (BOOL)loadWebView:(UIWebView*)webView withPage:(int)page { - NSString *path = [NSString stringWithString:[pages objectAtIndex:page - 1]]; + NSString *path = [NSString stringWithString:pages[page - 1]]; if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { NSLog(@"[BakerView] Loading: %@", [[NSFileManager defaultManager] displayNameAtPath:path]); @@ -961,12 +1018,12 @@ - (void)loadModalWebView:(NSURL *)url { //NSLog(@"[BakerView] Loading a Modal WebView with URL: %@", url.absoluteString); [[NSNotificationCenter defaultCenter] postNotificationName:@"BakerViewModalBrowser" object:self]; // -> Baker Analytics Event - myModalViewController = [[[ModalViewController alloc] initWithUrl:url] autorelease]; + myModalViewController = [[BKRModalWebViewController alloc] initWithURL:url]; myModalViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical; myModalViewController.delegate = self; // Hide the IndexView before opening modal web view - [self hideBars:[NSNumber numberWithBool:YES]]; + [self hideBars:@YES]; [self presentViewController:myModalViewController animated:YES completion:nil]; @@ -986,7 +1043,7 @@ - (CGRect)frameForPage:(int)page { } - (void)scrollViewWillBeginDragging:(UIScrollView *)scroll { //NSLog(@"[BakerView] Scrollview will begin dragging"); - [self hideBars:[NSNumber numberWithBool:YES]]; + [self hideBars:@YES]; } - (void)scrollViewDidEndDragging:(UIScrollView *)scroll willDecelerate:(BOOL)decelerate { //NSLog(@"[BakerView] Scrollview did end dragging"); @@ -999,12 +1056,12 @@ - (void)scrollViewDidEndDecelerating:(UIScrollView *)scroll { int page = (int)(scroll.contentOffset.x / pageWidth) + 1; NSLog(@"[BakerView] Swiping to page: %d", page); - if (currentPageNumber != page) { + if (self.currentPageNumber != page) { - lastPageNumber = currentPageNumber; - currentPageNumber = page; + lastPageNumber = self.currentPageNumber; + self.currentPageNumber = page; - tapNumber = tapNumber + (lastPageNumber - currentPageNumber); + tapNumber = tapNumber + (lastPageNumber - self.currentPageNumber); currentPageIsDelayingLoading = YES; [self gotoPage]; @@ -1016,7 +1073,7 @@ - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scroll { stackedScrollingAnimations--; if (stackedScrollingAnimations == 0) { //NSLog(@"[BakerView] Scroll enabled"); - scroll.scrollEnabled = [book.bakerPageTurnSwipe boolValue]; // YES by default, NO if specified + scroll.scrollEnabled = [self.book.bakerPageTurnSwipe boolValue]; // YES by default, NO if specified } } @@ -1063,18 +1120,36 @@ - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *) // ****** Handle: file:// NSLog(@"[BakerView] Page is a link with scheme file:// --> load internal link"); - anchorFromURL = [[url fragment] retain]; + anchorFromURL = [url fragment]; NSString *file = [[url relativePath] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; - int page = [pages indexOfObject:file]; + NSUInteger page = [pages indexOfObject:file]; if (page == NSNotFound) { + NSString *params = [url query]; + NSLog(@"[BakerView] Opening a relative URL: %@", [url absoluteString]); + + if (params != nil) + { + NSRegularExpression *referrerModalRegex = [NSRegularExpression regularExpressionWithPattern:URL_OPEN_MODALLY options:NSRegularExpressionCaseInsensitive error:NULL]; + NSUInteger matchesModal = [referrerModalRegex numberOfMatchesInString:params options:0 range:NSMakeRange(0, [params length])]; + + if (matchesModal) + { + NSLog(@"[BakerView] Link contain param '%@' --> open link modally", URL_OPEN_MODALLY); + + NSLog(@"[BakerView] Opening in Modal Panel with URL: %@", [url absoluteString]); + [self loadModalWebView:[NSURL URLWithString:[url absoluteString]]]; + + return NO; + } + } // ****** Internal link, but not one of the book pages --> load page anyway return YES; } page = page + 1; - if (![self changePage:page] && ![webView isEqual:indexViewController.view]) + if (![self changePage:(int)page] && ![webView isEqual:indexViewController.view]) { if (anchorFromURL == nil) { return YES; @@ -1100,21 +1175,20 @@ - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *) // Check array count to see if we have parameters to query if ([tempArray count] == 2) { - NSArray *keyValuePairs = [[tempArray objectAtIndex:1] componentsSeparatedByString:@"&"]; + NSArray *keyValuePairs = [tempArray[1] componentsSeparatedByString:@"&"]; for (NSString *queryString in keyValuePairs) { NSArray *keyValuePair = [queryString componentsSeparatedByString:@"="]; if (keyValuePair.count == 2) { - [queryDictionary setObject:[keyValuePair objectAtIndex:1] forKey:[keyValuePair objectAtIndex:0]]; + queryDictionary[keyValuePair[0]] = keyValuePair[1]; } } } - NSString *email = ([tempArray objectAtIndex:0]) ? [tempArray objectAtIndex:0] : [url resourceSpecifier]; - NSString *subject = [queryDictionary objectForKey:@"subject"]; - NSString *body = [queryDictionary objectForKey:@"body"]; + NSString *email = (tempArray[0]) ? tempArray[0] : [url resourceSpecifier]; + NSString *subject = queryDictionary[@"subject"]; + NSString *body = queryDictionary[@"body"]; - [queryDictionary release]; if ([MFMailComposeViewController canSendMail]) { @@ -1123,7 +1197,7 @@ - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *) mailer.mailComposeDelegate = self; mailer.modalPresentationStyle = UIModalPresentationPageSheet; - [mailer setToRecipients:[NSArray arrayWithObject:[email stringByReplacingOccurrencesOfString:@"mailto:" withString:@""]]]; + [mailer setToRecipients:@[[email stringByReplacingOccurrencesOfString:@"mailto:" withString:@""]]]; [mailer setSubject:[subject stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; [mailer setMessageBody:[body stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] isHTML:NO]; @@ -1132,7 +1206,6 @@ - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *) currentPageWillAppearUnderModal = YES; - [mailer release]; } else { @@ -1145,7 +1218,7 @@ - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *) else { // Display error message - [Utils showAlertWithTitle:NSLocalizedString(@"MAILTO_ALERT_TITLE", nil) + [BKRUtils showAlertWithTitle:NSLocalizedString(@"MAILTO_ALERT_TITLE", nil) message:NSLocalizedString(@"MAILTO_ALERT_MESSAGE", nil) buttonTitle:NSLocalizedString(@"MAILTO_ALERT_CLOSE", nil)]; } @@ -1159,11 +1232,26 @@ - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *) [[UIApplication sharedApplication] openURL:url]; } else { NSLog(@"[BakerView] ERROR: No installed application to open '%@'. An application to handle the '%@' URL scheme is required.", url, [url scheme]); - [Utils webView:currPage dispatchHTMLEvent:@"urlnothandled" withParams:[NSDictionary dictionaryWithObject:url forKey:@"url"]]; + [BKRUtils webView:self.currPage dispatchHTMLEvent:@"urlnothandled" withParams:@{@"url": url}]; } return NO; } + else if ([[url scheme] isEqualToString:@"baker-toc://show"]) + { + [self showBars]; + return NO; + } + else if ([[url scheme] isEqualToString:@"baker-toc://hide"]) + { + [self hideBars:@YES]; + return NO; + } + else if ([[url scheme] isEqualToString:@"baker-toc://toggle"]) + { + [self toggleBars]; + return NO; + } else { // **************************************************************************************************** OPEN OUTSIDE BAKER @@ -1188,7 +1276,7 @@ - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *) // Generate new URL without // We are regexp-ing three things: the string alone, the string first with other content, the string with other content in any other position - NSString *pattern = [[[NSString alloc] initWithFormat:@"\\?%@$|(?<=\\?)%@&?|()&?%@", URL_OPEN_EXTERNAL, URL_OPEN_EXTERNAL, URL_OPEN_EXTERNAL] autorelease]; + NSString *pattern = [[NSString alloc] initWithFormat:@"\\?%@$|(?<=\\?)%@&?|()&?%@", URL_OPEN_EXTERNAL, URL_OPEN_EXTERNAL, URL_OPEN_EXTERNAL]; NSRegularExpression *replacerRegexp = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:NULL]; NSString *oldURL = [url absoluteString]; //NSLog(@"[BakerView] replacement pattern: %@", [replacerRegexp pattern]); @@ -1205,7 +1293,7 @@ - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *) // Generate new URL without // We are regexp-ing three things: the string alone, the string first with other content, the string with other content in any other position - NSString *pattern = [[[NSString alloc] initWithFormat:@"\\?%@$|(?<=\\?)%@&?|()&?%@", URL_OPEN_MODALLY, URL_OPEN_MODALLY, URL_OPEN_MODALLY] autorelease]; + NSString *pattern = [[NSString alloc] initWithFormat:@"\\?%@$|(?<=\\?)%@&?|()&?%@", URL_OPEN_MODALLY, URL_OPEN_MODALLY, URL_OPEN_MODALLY]; NSRegularExpression *replacerRegexp = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:NULL]; NSString *oldURL = [url absoluteString]; //NSLog(@"[BakerView] replacement pattern: %@", [replacerRegexp pattern]); @@ -1233,7 +1321,7 @@ - (void)webViewDidStartLoad:(UIWebView *)webView { } - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { // Sent if a web view failed to load content. - if ([webView isEqual:currPage]) { + if ([webView isEqual:self.currPage]) { NSLog(@"[BakerView] ERROR: CurrPage failed to load content with error: %@", error); } else if ([webView isEqual:prevPage]) { NSLog(@"[BakerView] ERROR: PrevPage failed to load content with error: %@", error); @@ -1247,7 +1335,7 @@ - (void)webViewDidFinishLoad:(UIWebView *)webView { if (webView.hidden == YES) { - if ([webView isEqual:currPage]) { + if ([webView isEqual:self.currPage]) { currentPageHasChanged = NO; [self setCurrentPageHeight]; } @@ -1255,17 +1343,17 @@ - (void)webViewDidFinishLoad:(UIWebView *)webView { [webView removeFromSuperview]; webView.hidden = NO; - if ([book.bakerRendering isEqualToString:@"three-cards"]) { + if ([self.book.bakerRendering isEqualToString:@"three-cards"]) { [self webView:webView hidden:NO animating:YES]; } else { - [self takeScreenshotFromView:webView forPage:currentPageNumber andOrientation:[self getCurrentInterfaceOrientation:self.interfaceOrientation]]; + [self takeScreenshotFromView:webView forPage:self.currentPageNumber andOrientation:[self getCurrentInterfaceOrientation:self.interfaceOrientation]]; } [self handlePageLoading]; } /** CHECK IF META TAG SAYS HTML FILE SHOULD BE PAGED **/ - [webView.scrollView setPagingEnabled:[Utils webViewShouldBePaged:webView forBook:book]]; + [webView.scrollView setPagingEnabled:[BKRUtils webViewShouldBePaged:webView forBook:self.book]]; } - (void)webView:(UIWebView *)webView hidden:(BOOL)status animating:(BOOL)animating { @@ -1276,21 +1364,21 @@ - (void)webView:(UIWebView *)webView hidden:(BOOL)status animating:(BOOL)animati webView.hidden = NO; webView.alpha = 0.0; - [scrollView addSubview:webView]; + [self.scrollView addSubview:webView]; [UIView animateWithDuration:0.5 animations:^{ webView.alpha = 1.0; } completion:^(BOOL finished) { [self webViewDidAppear:webView animating:animating]; }]; } else { - [scrollView addSubview:webView]; + [self.scrollView addSubview:webView]; [self webViewDidAppear:webView animating:animating]; } } - (void)webViewDidAppear:(UIWebView *)webView animating:(BOOL)animating { - if ([webView isEqual:currPage]) + if ([webView isEqual:self.currPage]) { - [Utils webView:webView dispatchHTMLEvent:@"focus"]; + [BKRUtils webView:webView dispatchHTMLEvent:@"focus"]; // If is the first time i load something in the currPage web view... if (currentPageFirstLoading) @@ -1329,6 +1417,7 @@ - (void)webView:(UIWebView *)webView setCorrectOrientation:(UIInterfaceOrientati jsOrientationGetter = @"window.__defineGetter__('orientation', function() { return -90; });"; break; default: + jsOrientationGetter = nil; break; } @@ -1339,12 +1428,12 @@ - (void)webView:(UIWebView *)webView setCorrectOrientation:(UIInterfaceOrientati - (void)removeScreenshots { for (NSNumber *key in attachedScreenshotLandscape) { - UIView *value = [attachedScreenshotLandscape objectForKey:key]; + UIView *value = attachedScreenshotLandscape[key]; [value removeFromSuperview]; } for (NSNumber *key in attachedScreenshotPortrait) { - UIView *value = [attachedScreenshotPortrait objectForKey:key]; + UIView *value = attachedScreenshotPortrait[key]; [value removeFromSuperview]; } @@ -1383,12 +1472,12 @@ - (void)updateScreenshots { for (NSNumber *num in attachedScreenshot) [completeSet addObject:num]; - for (int i = MAX(1, currentPageNumber - MAX_SCREENSHOT_BEFORE_CP); i <= MIN(totalPages, currentPageNumber + MAX_SCREENSHOT_AFTER_CP); i++) + for (int i = MAX(1, self.currentPageNumber - MAX_SCREENSHOT_BEFORE_CP); i <= MIN(totalPages, self.currentPageNumber + MAX_SCREENSHOT_AFTER_CP); i++) { - NSNumber *num = [NSNumber numberWithInt:i]; + NSNumber *num = @(i); [supportSet addObject:num]; - if ([self checkScreeshotForPage:i andOrientation:interfaceOrientation] && ![attachedScreenshot objectForKey:num]) { + if ([self checkScreeshotForPage:i andOrientation:interfaceOrientation] && !attachedScreenshot[num]) { [self placeScreenshotForView:nil andPage:i andOrientation:interfaceOrientation]; [completeSet addObject:num]; } @@ -1397,12 +1486,10 @@ - (void)updateScreenshots { [completeSet minusSet:supportSet]; for (NSNumber *num in completeSet) { - [[attachedScreenshot objectForKey:num] removeFromSuperview]; + [attachedScreenshot[num] removeFromSuperview]; [attachedScreenshot removeObjectForKey:num]; } - [completeSet release]; - [supportSet release]; } - (BOOL)checkScreeshotForPage:(int)pageNumber andOrientation:(NSString *)interfaceOrientation { @@ -1442,7 +1529,7 @@ - (void)takeScreenshotFromView:(UIWebView *)webView forPage:(int)pageNumber andO } } - [self performSelector:@selector(lockPage:) withObject:[NSNumber numberWithBool:NO] afterDelay:0.1]; + [self performSelector:@selector(lockPage:) withObject:@NO afterDelay:0.1]; } if (!currentPageHasChanged && shouldRevealWebView) { @@ -1452,39 +1539,37 @@ - (void)takeScreenshotFromView:(UIWebView *)webView forPage:(int)pageNumber andO - (void)placeScreenshotForView:(UIWebView *)webView andPage:(int)pageNumber andOrientation:(NSString *)interfaceOrientation { int i = pageNumber - 1; - NSNumber *num = [NSNumber numberWithInt:pageNumber]; + NSNumber *num = @(pageNumber); NSString *screenshotFile = [cachedScreenshotsPath stringByAppendingPathComponent:[NSString stringWithFormat:@"screenshot-%@-%i.jpg", interfaceOrientation, pageNumber]]; UIImageView *screenshotView = [[UIImageView alloc] initWithImage:[UIImage imageWithContentsOfFile:screenshotFile]]; - + NSMutableDictionary *attachedScreenshot = attachedScreenshotPortrait; - CGSize pageSize = CGSizeMake(screenBounds.size.width, screenBounds.size.height); - - if ([interfaceOrientation isEqualToString:@"landscape"]) { - attachedScreenshot = attachedScreenshotLandscape; - pageSize = CGSizeMake(screenBounds.size.height, screenBounds.size.width); - } + + int pageSizeWidth = [[UIScreen mainScreen] bkrWidthForOrientationName:interfaceOrientation]; + int pageSizeHeight = [[UIScreen mainScreen] bkrHeightForOrientationName:interfaceOrientation]; + CGSize pageSize = CGSizeMake(pageSizeWidth, pageSizeHeight); screenshotView.frame = CGRectMake(pageSize.width * i, 0, pageSize.width, pageSize.height); BOOL alreadyPlaced = NO; - UIImageView *oldScreenshot = [attachedScreenshot objectForKey:num]; + UIImageView *oldScreenshot = attachedScreenshot[num]; if (oldScreenshot) { - [scrollView addSubview:screenshotView]; + [self.scrollView addSubview:screenshotView]; [attachedScreenshot removeObjectForKey:num]; [oldScreenshot removeFromSuperview]; alreadyPlaced = YES; } - [attachedScreenshot setObject:screenshotView forKey:num]; + attachedScreenshot[num] = screenshotView; if (webView == nil) { screenshotView.alpha = 0.0; - [scrollView addSubview:screenshotView]; + [self.scrollView addSubview:screenshotView]; [UIView animateWithDuration:0.5 animations:^{ screenshotView.alpha = 1.0; }]; } else if (webView != nil) @@ -1497,26 +1582,25 @@ - (void)placeScreenshotForView:(UIWebView *)webView andPage:(int)pageNumber andO { screenshotView.alpha = 0.0; - [scrollView addSubview:screenshotView]; + [self.scrollView addSubview:screenshotView]; [UIView animateWithDuration:0.5 animations:^{ screenshotView.alpha = 1.0; } completion:^(BOOL finished) { if (!currentPageHasChanged) { [self webView:webView hidden:NO animating:NO]; }}]; } } - [screenshotView release]; } #pragma mark - GESTURES - (void)handleInterceptedTouch:(NSNotification *)notification { NSDictionary *userInfo = notification.userInfo; - UITouch *touch = [userInfo objectForKey:@"touch"]; + UITouch *touch = userInfo[@"touch"]; BOOL shouldPropagateIndexInterceptedTouch = NO; if (touch.phase == UITouchPhaseBegan) { userIsScrolling = NO; - shouldPropagateInterceptedTouch = ([touch.view isDescendantOfView:scrollView]); + shouldPropagateInterceptedTouch = ([touch.view isDescendantOfView:self.scrollView]); shouldPropagateIndexInterceptedTouch = [touch.view isDescendantOfView:indexViewController.view]; } else if (touch.phase == UITouchPhaseMoved) { userIsScrolling = YES; @@ -1557,13 +1641,13 @@ - (void)userDidTap:(UITouch *)touch { int page = 0; if (CGRectContainsPoint(leftTapArea, tapPoint)) { NSLog(@"[BakerView] Tap LEFT >>>"); - page = currentPageNumber - 1; + page = self.currentPageNumber - 1; } else if (CGRectContainsPoint(rightTapArea, tapPoint)) { NSLog(@"[BakerView] Tap RIGHT <<<"); - page = currentPageNumber + 1; + page = self.currentPageNumber + 1; } - if ([book.bakerPageTurnTap boolValue]) [self changePage:page]; + if ([self.book.bakerPageTurnTap boolValue]) [self changePage:page]; } else if (touch.tapCount == 2) { //NSLog(@"[BakerView] Index Double Tap: Bars Toggled"); @@ -1573,15 +1657,15 @@ - (void)userDidTap:(UITouch *)touch { } - (void)userDidScroll:(UITouch *)touch { //NSLog(@"[BakerView] User scroll"); - [self hideBars:[NSNumber numberWithBool:YES]]; + [self hideBars:@YES]; - currPage.backgroundColor = webViewBackground; - currPage.opaque = YES; + self.currPage.backgroundColor = webViewBackground; + self.currPage.opaque = YES; } #pragma mark - PAGE SCROLLING - (void)setCurrentPageHeight { - for (UIView *subview in currPage.subviews) { + for (UIView *subview in self.currPage.subviews) { if ([subview isKindOfClass:[UIScrollView class]]) { CGSize size = ((UIScrollView *)subview).contentSize; //NSLog(@"[BakerView] Setting current page height from %d to %f", currentPageHeight, size.height); @@ -1591,7 +1675,7 @@ - (void)setCurrentPageHeight { } - (int)getCurrentPageOffset { - int currentPageOffset = [[currPage stringByEvaluatingJavaScriptFromString:@"window.scrollY;"] intValue]; + int currentPageOffset = [[self.currPage stringByEvaluatingJavaScriptFromString:@"window.scrollY;"] intValue]; if (currentPageOffset < 0) return 0; int currentPageMaxScroll = currentPageHeight - pageHeight; @@ -1606,7 +1690,7 @@ - (void)scrollUpCurrentPage:(int)targetOffset animating:(BOOL)animating { if (targetOffset < 0) targetOffset = 0; //NSLog(@"[BakerView] Scrolling page up to %d", targetOffset); - [self scrollPage:currPage to:[NSString stringWithFormat:@"%d", targetOffset] animating:animating]; + [self scrollPage:self.currPage to:[NSString stringWithFormat:@"%d", targetOffset] animating:animating]; } } - (void)scrollDownCurrentPage:(int)targetOffset animating:(BOOL)animating { @@ -1617,12 +1701,12 @@ - (void)scrollDownCurrentPage:(int)targetOffset animating:(BOOL)animating { if (targetOffset > currentPageMaxScroll) targetOffset = currentPageMaxScroll; //NSLog(@"[BakerView] Scrolling page down to %d", targetOffset); - [self scrollPage:currPage to:[NSString stringWithFormat:@"%d", targetOffset] animating:animating]; + [self scrollPage:self.currPage to:[NSString stringWithFormat:@"%d", targetOffset] animating:animating]; } } - (void)scrollPage:(UIWebView *)webView to:(NSString *)offset animating:(BOOL)animating { - [self hideBars:[NSNumber numberWithBool:YES]]; + [self hideBars:@YES]; NSString *jsCommand = [NSString stringWithFormat:@"window.scrollTo(0,%@);", offset]; if (animating) { @@ -1640,7 +1724,7 @@ - (void)handleAnchor:(BOOL)animating { return elem.offsetTop;\ })();", anchorFromURL]; - NSString *offsetString = [currPage stringByEvaluatingJavaScriptFromString:jsAnchorHandler]; + NSString *offsetString = [self.currPage stringByEvaluatingJavaScriptFromString:jsAnchorHandler]; if (![offsetString isEqualToString:@""]) { int offset = [offsetString intValue]; @@ -1672,7 +1756,7 @@ - (CGRect)getNewNavigationFrame:(BOOL)hidden { } } - (BOOL)prefersStatusBarHidden { - return barsHidden; + return self.barsHidden; } - (UIStatusBarAnimation)preferredStatusBarUpdateAnimation { return UIStatusBarAnimationSlide; @@ -1682,26 +1766,21 @@ - (void)toggleBars { if (!self.presentedViewController) { NSLog(@"[BakerView] Toggle bars visibility"); - if (barsHidden) { + if (self.barsHidden) { [self showBars]; } else { - [self hideBars:[NSNumber numberWithBool:YES]]; + [self hideBars:@YES]; } } } - (void)showBars { - barsHidden = NO; + self.barsHidden = NO; - if (SYSTEM_VERSION_LESS_THAN(@"7.0")) { - [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide]; - [self performSelector:@selector(showNavigationBar) withObject:nil afterDelay:0.1]; - } else { - [UIView animateWithDuration:UINavigationControllerHideShowBarDuration animations:^{ - [self setNeedsStatusBarAppearanceUpdate]; - }]; - [self.navigationController setNavigationBarHidden:NO animated:YES]; - } + [UIView animateWithDuration:UINavigationControllerHideShowBarDuration animations:^{ + [self setNeedsStatusBarAppearanceUpdate]; + }]; + [self.navigationController setNavigationBarHidden:NO animated:YES]; if(![indexViewController isDisabled]) { [[NSNotificationCenter defaultCenter] postNotificationName:@"BakerViewIndexOpen" object:self]; // -> Baker Analytics Event @@ -1726,45 +1805,20 @@ - (void)showNavigationBar { } - (void)hideBars:(NSNumber *)animated { - barsHidden = YES; + self.barsHidden = YES; BOOL animateHiding = [animated boolValue]; - if (SYSTEM_VERSION_LESS_THAN(@"7.0")) { - - CGRect newNavigationFrame = [self getNewNavigationFrame:YES]; - UINavigationBar *navigationBar = self.navigationController.navigationBar; - - if (animateHiding) { - [UIView animateWithDuration:0.3 - delay:0 - options:UIViewAnimationOptionCurveEaseOut - animations:^{ - navigationBar.frame = newNavigationFrame; - } - completion:^(BOOL finished) { - navigationBar.hidden = YES; - }]; - } else { - navigationBar.frame = newNavigationFrame; - navigationBar.hidden = YES; - } - - [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide]; - + if (animateHiding) { + [UIView animateWithDuration:UINavigationControllerHideShowBarDuration animations:^{ + [self setNeedsStatusBarAppearanceUpdate]; + }]; } else { - - if (animateHiding) { - [UIView animateWithDuration:UINavigationControllerHideShowBarDuration animations:^{ - [self setNeedsStatusBarAppearanceUpdate]; - }]; - } else { - [self setNeedsStatusBarAppearanceUpdate]; - } - - [self.navigationController setNavigationBarHidden:YES animated:animateHiding]; + [self setNeedsStatusBarAppearanceUpdate]; } + [self.navigationController setNavigationBarHidden:YES animated:animateHiding]; + if(![indexViewController isDisabled]) { [indexViewController setIndexViewHidden:YES withAnimation:YES]; } @@ -1780,15 +1834,15 @@ - (void)handleBookProtocol:(NSURL *)url url = [url URLByDeletingLastPathComponent]; } NSString *bookName = [[url lastPathComponent] stringByReplacingOccurrencesOfString:@".hpub" withString:@""]; - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:bookName forKey:@"ID"]; + NSDictionary *userInfo = @{@"ID": bookName}; [[NSNotificationCenter defaultCenter] postNotificationName:@"notification_book_protocol" object:nil userInfo:userInfo]; } #pragma mark - ORIENTATION - (NSString *)getCurrentInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { - if ([book.orientation isEqualToString:@"portrait"] || [book.orientation isEqualToString:@"landscape"]) { - return book.orientation; + if ([self.book.orientation isEqualToString:@"portrait"] || [self.book.orientation isEqualToString:@"landscape"]) { + return self.book.orientation; } else { if (UIInterfaceOrientationIsLandscape(interfaceOrientation)) { return @"landscape"; @@ -1799,11 +1853,11 @@ - (NSString *)getCurrentInterfaceOrientation:(UIInterfaceOrientation)interfaceOr } - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { - BOOL appOrientation = [supportedOrientation indexOfObject:[Utils stringFromInterfaceOrientation:interfaceOrientation]] != NSNotFound; + BOOL appOrientation = [supportedOrientation indexOfObject:[BKRUtils stringFromInterfaceOrientation:interfaceOrientation]] != NSNotFound; - if ([book.orientation isEqualToString:@"portrait"]) { + if ([self.book.orientation isEqualToString:@"portrait"]) { return appOrientation && UIInterfaceOrientationIsPortrait(interfaceOrientation); - } else if ([book.orientation isEqualToString:@"landscape"]) { + } else if ([self.book.orientation isEqualToString:@"landscape"]) { return appOrientation && UIInterfaceOrientationIsLandscape(interfaceOrientation); } else { return appOrientation; @@ -1813,9 +1867,9 @@ - (BOOL)shouldAutorotate { return YES; } - (NSUInteger)supportedInterfaceOrientations { - if ([book.orientation isEqualToString:@"portrait"]) { + if ([self.book.orientation isEqualToString:@"portrait"]) { return (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown); - } else if ([book.orientation isEqualToString:@"landscape"]) { + } else if ([self.book.orientation isEqualToString:@"landscape"]) { return UIInterfaceOrientationMaskLandscape; } else { return UIInterfaceOrientationMaskAll; @@ -1826,7 +1880,7 @@ - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrie [indexViewController willRotate]; // Notify the current loaded views - [self webView:currPage setCorrectOrientation:toInterfaceOrientation]; + [self webView:self.currPage setCorrectOrientation:toInterfaceOrientation]; if (nextPage) [self webView:nextPage setCorrectOrientation:toInterfaceOrientation]; if (prevPage) [self webView:prevPage setCorrectOrientation:toInterfaceOrientation]; @@ -1844,9 +1898,9 @@ - (BOOL)forceOrientationUpdate { shouldForceOrientationUpdate = NO; UIInterfaceOrientation interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation]; - if ( (UIInterfaceOrientationIsLandscape(interfaceOrientation) && [book.orientation isEqualToString:@"landscape"]) + if ( (UIInterfaceOrientationIsLandscape(interfaceOrientation) && [self.book.orientation isEqualToString:@"landscape"]) || - (UIInterfaceOrientationIsPortrait(interfaceOrientation) && [book.orientation isEqualToString:@"portrait"]) ) { + (UIInterfaceOrientationIsPortrait(interfaceOrientation) && [self.book.orientation isEqualToString:@"portrait"]) ) { //NSLog(@"[BakerView] Device and book orientations are in sync"); return NO; @@ -1854,7 +1908,11 @@ - (BOOL)forceOrientationUpdate { //NSLog(@"[BakerView] Device and book orientations are out of sync, force orientation update"); // Present and dismiss a vanilla view controller to trigger the orientation update - [self presentViewController:[UIViewController new] animated:NO completion:^{ [self dismissViewControllerAnimated:NO completion:nil]; }]; + [self presentViewController:[UIViewController new] animated:NO completion:^{ + dispatch_after(0, dispatch_get_main_queue(), ^{ + [self dismissViewControllerAnimated:NO completion:nil]; + }); + }]; return YES; } @@ -1865,6 +1923,7 @@ - (BOOL)forceOrientationUpdate { #pragma mark - MEMORY - (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; NSArray *viewControllers = self.navigationController.viewControllers; if ([viewControllers indexOfObject:self] == NSNotFound) { // Baker book is disappearing because it was popped from the navigation stack -> Baker book is closing @@ -1873,9 +1932,10 @@ - (void)viewWillDisappear:(BOOL)animated { } } - (void)saveBookStatusWithScrollIndex { - if (currPage != nil) { - bookStatus.scrollIndex = [currPage stringByEvaluatingJavaScriptFromString:@"window.scrollY;"]; + if (self.currPage != nil) { + bookStatus.scrollIndex = [self.currPage stringByEvaluatingJavaScriptFromString:@"window.scrollY;"]; } + bookStatus.page = @(self.currentPageNumber); [bookStatus save]; //NSLog(@"[BakerView] Saved status"); } @@ -1887,39 +1947,10 @@ - (void)didReceiveMemoryWarning { - (void)dealloc { // Set web views delegates to nil, mandatory before releasing UIWebview instances - currPage.delegate = nil; + self.currPage.delegate = nil; nextPage.delegate = nil; prevPage.delegate = nil; - - // Retained background images - [backgroundImageLandscape release]; - [backgroundImagePortrait release]; - - [attachedScreenshotLandscape release]; - [attachedScreenshotPortrait release]; - - // Release the kra... objects - [supportedOrientation release]; - - [cachedScreenshotsPath release]; - [defaultScreeshotsPath release]; - - [pageDetails release]; - [toLoad release]; - [pages release]; - - [indexViewController release]; - - [book release]; - [bookStatus release]; - [scrollView release]; - [currPage release]; - [nextPage release]; - [prevPage release]; - - [webViewBackground release]; - [super dealloc]; } #pragma mark - MF MAIL COMPOSER diff --git a/BakerView/BKRCore.h b/BakerView/BKRCore.h new file mode 100644 index 00000000..14192bd6 --- /dev/null +++ b/BakerView/BKRCore.h @@ -0,0 +1,43 @@ +// +// BakerCore.h +// Baker +// +// ========================================================================================== +// +// Copyright (c) 2014, Pieter Claerhout +// 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 Baker Framework nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written +// permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#ifndef Baker_BKRCore_h +#define Baker_BKRCore_h + +static inline BOOL BKR_IsEmpty(id thing) { + return thing == nil || [thing isEqual:[NSNull null]] + || ([thing respondsToSelector:@selector(length)] + && [(NSData *)thing length] == 0) + || ([thing respondsToSelector:@selector(count)] + && [(NSArray *)thing count] == 0); +} + +#endif diff --git a/BakerView/IndexViewController.h b/BakerView/BKRIndexViewController.h similarity index 84% rename from BakerView/IndexViewController.h rename to BakerView/BKRIndexViewController.h index bfe15cc4..cd693e45 100644 --- a/BakerView/IndexViewController.h +++ b/BakerView/BKRIndexViewController.h @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -30,9 +31,9 @@ // #import -#import "BakerBook.h" +#import "BKRBook.h" -@interface IndexViewController : UIViewController { +@interface BKRIndexViewController : UIViewController { NSString *fileName; UIScrollView *indexScrollView; @@ -52,11 +53,11 @@ CGSize cachedContentSize; } -@property (strong, nonatomic) BakerBook *book; +@property (nonatomic, strong) BKRBook *book; -- (id)initWithBook:(BakerBook *)bakerBook fileName:(NSString *)name webViewDelegate:(UIViewController *)delegate; +- (id)initWithBook:(BKRBook*)bakerBook fileName:(NSString*)name webViewDelegate:(UIViewController*)delegate; - (void)loadContent; -- (void)setBounceForWebView:(UIWebView *)webView bounces:(BOOL)bounces; +- (void)setBounceForWebView:(UIWebView*)webView bounces:(BOOL)bounces; - (void)setPageSizeForOrientation:(UIInterfaceOrientation)orientation; - (BOOL)isIndexViewHidden; - (BOOL)isDisabled; @@ -66,10 +67,10 @@ - (void)fadeOut; - (void)fadeIn; - (BOOL)stickToLeft; -- (CGSize)sizeFromContentOf:(UIView *)view; +- (CGSize)sizeFromContentOf:(UIWebView*)webView; - (void)setActualSize; - (void)adjustIndexView; - (void)setViewFrame:(CGRect)frame; -- (NSString *)indexPath; +- (NSString*)indexPath; @end diff --git a/BakerView/IndexViewController.m b/BakerView/BKRIndexViewController.m similarity index 76% rename from BakerView/IndexViewController.m rename to BakerView/BKRIndexViewController.m index bc3f015a..39f75e4b 100644 --- a/BakerView/IndexViewController.m +++ b/BakerView/BKRIndexViewController.m @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -29,89 +30,74 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -#import "IndexViewController.h" -#import "BakerViewController.h" +#import "BKRIndexViewController.h" +#import "BKRBookViewController.h" -@implementation IndexViewController +#import "UIScreen+BakerExtensions.h" -@synthesize book; +@implementation BKRIndexViewController -- (id)initWithBook:(BakerBook *)bakerBook fileName:(NSString *)name webViewDelegate:(UIViewController *)delegate { +#pragma mark - Initialization + +- (id)initWithBook:(BKRBook*)bakerBook fileName:(NSString*)name webViewDelegate:(UIViewController*)delegate { self = [super init]; if (self) { - self.book = bakerBook; + _book = bakerBook; - fileName = name; + fileName = name; webViewDelegate = delegate; - disabled = NO; - indexWidth = 0; + disabled = NO; + indexWidth = 0; indexHeight = 0; [self setPageSizeForOrientation:[UIApplication sharedApplication].statusBarOrientation]; + } return self; } -- (void)dealloc -{ - [book release]; - [indexScrollView release]; - - [super dealloc]; -} #pragma mark - View lifecycle -// Implement loadView to create a view hierarchy programmatically, without using a nib. -- (void)loadView -{ +- (void)loadView { + // Initialization to 1x1px is required to get sizeThatFits to work UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 1024, 1, 1)]; webView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; - webView.delegate = self; - - webView.backgroundColor = [UIColor clearColor]; - [webView setOpaque:NO]; - + webView.delegate = self; + webView.backgroundColor = [UIColor clearColor]; + webView.opaque = NO; self.view = webView; for (UIView *subView in webView.subviews) { if ([subView isKindOfClass:[UIScrollView class]]) { - indexScrollView = [(UIScrollView *)subView retain]; + indexScrollView = (UIScrollView *)subView; break; } } - [webView release]; [self loadContent]; + } -- (void)setBounceForWebView:(UIWebView *)webView bounces:(BOOL)bounces { +- (void)setBounceForWebView:(UIWebView*)webView bounces:(BOOL)bounces { indexScrollView.bounces = bounces; } - (void)setPageSizeForOrientation:(UIInterfaceOrientation)orientation { - CGRect screenBounds = [[UIScreen mainScreen] bounds]; - - if (orientation == UIInterfaceOrientationLandscapeLeft || orientation == UIInterfaceOrientationLandscapeRight) { - pageWidth = screenBounds.size.height; - pageHeight = screenBounds.size.width; - } else { - pageWidth = screenBounds.size.width; - pageHeight = screenBounds.size.height; - } - + pageWidth = [[UIScreen mainScreen] bkrWidthForOrientation:orientation]; + pageHeight = [[UIScreen mainScreen] bkrHeightForOrientation:orientation]; NSLog(@"[IndexView] Set IndexView size to %dx%d", pageWidth, pageHeight); } - (void)setActualSize { - actualIndexWidth = MIN(indexWidth, pageWidth); + actualIndexWidth = MIN(indexWidth, pageWidth); actualIndexHeight = MIN(indexHeight, pageHeight); } - (BOOL)isIndexViewHidden { - return ((BakerViewController*) webViewDelegate).barsHidden; + return ((BKRBookViewController*) webViewDelegate).barsHidden; } - (BOOL)isDisabled { @@ -132,19 +118,18 @@ - (void)setIndexViewHidden:(BOOL)hidden withAnimation:(BOOL)animation { } else { frame = CGRectMake(0, [self trueY] + pageHeight - indexHeight, actualIndexWidth, actualIndexHeight); } - } if (animation) { [UIView beginAnimations:@"slideIndexView" context:nil]; { [UIView setAnimationDuration:0.3]; - [self setViewFrame:frame]; } [UIView commitAnimations]; } else { [self setViewFrame:frame]; } + } - (int)trueY { @@ -152,8 +137,7 @@ - (int)trueY { // compensates for it, by exploiting the fact that the superview height is // slightly smaller then the viewport height when the origin's y needs to be adjusted. int height = self.view.superview.frame.size.height; - - if (height == 320 || height == 480 || height == 568 || height == 768 || height == 1024) { + if (height == 320 || height == 375 || height == 414 || height == 480 || height == 568 || height == 667 || height == 736 || height == 768 || height == 1024) { return 0; } else { return -20; @@ -162,16 +146,12 @@ - (int)trueY { - (void)setViewFrame:(CGRect)frame { self.view.frame = frame; - - // Orientation changes tend to screw the content size detection performed by the scrollView embedded in the webView. - // Let's show the scrollView who's boss. indexScrollView.contentSize = cachedContentSize; } - (void)fadeOut { [UIView beginAnimations:@"fadeOutIndexView" context:nil]; { [UIView setAnimationDuration:0.0]; - self.view.alpha = 0.0; } [UIView commitAnimations]; @@ -180,7 +160,6 @@ - (void)fadeOut { - (void)fadeIn { [UIView beginAnimations:@"fadeInIndexView" context:nil]; { [UIView setAnimationDuration:0.2]; - self.view.alpha = 1.0; } [UIView commitAnimations]; @@ -206,11 +185,11 @@ - (void)adjustIndexView { } - (void)loadContent { - NSString* path = [self indexPath]; + NSString* path = self.indexPath; - UIWebView *webView = (UIWebView*) self.view; - webView.mediaPlaybackRequiresUserAction = ![book.bakerMediaAutoplay boolValue]; - [self setBounceForWebView:webView bounces:[book.bakerIndexBounce boolValue]]; + UIWebView *webView = (UIWebView*)self.view; + webView.mediaPlaybackRequiresUserAction = ![self.book.bakerMediaAutoplay boolValue]; + [self setBounceForWebView:webView bounces:[self.book.bakerIndexBounce boolValue]]; //NSLog(@"[IndexView] Path to index view is %@", path); if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { @@ -221,15 +200,17 @@ - (void)loadContent { disabled = YES; } } --(void)webViewDidFinishLoad:(UIWebView *)webView { - id width = book.bakerIndexWidth; - id height = book.bakerIndexHeight; + +- (void)webViewDidFinishLoad:(UIWebView*)webView { + id width = self.book.bakerIndexWidth; + id height = self.book.bakerIndexHeight; if (width != nil) { indexWidth = (int)[width integerValue]; } else { indexWidth = [self sizeFromContentOf:webView].width; } + if (height != nil) { indexHeight = (int)[height integerValue]; } else { @@ -237,7 +218,6 @@ -(void)webViewDidFinishLoad:(UIWebView *)webView { } cachedContentSize = indexScrollView.contentSize; - // get correct contentsize if (cachedContentSize.width < indexWidth) { cachedContentSize = CGSizeMake(indexWidth, indexHeight); } @@ -255,20 +235,25 @@ - (BOOL)stickToLeft { return (actualIndexHeight > actualIndexWidth); } -- (CGSize)sizeFromContentOf:(UIView *)view { +- (CGSize)sizeFromContentOf:(UIWebView*)webView { + /* // Setting the frame to 1x1 is required to get meaningful results from sizeThatFits when // the orientation of the is anything but Portrait. // See: http://stackoverflow.com/questions/3936041/how-to-determine-the-content-size-of-a-uiwebview/3937599#3937599 CGRect frame = view.frame; - frame.size.width = 1; + frame.size.width = 1; frame.size.height = 1; view.frame = frame; - return [view sizeThatFits:CGSizeZero]; + */ + + CGFloat contentWidth = [[webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollWidth"] floatValue]; + CGFloat contentHeight = [[webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight"] floatValue]; + return CGSizeMake(contentWidth, contentHeight); } -- (NSString *)indexPath { - return [book.path stringByAppendingPathComponent:fileName]; +- (NSString*)indexPath { + return [self.book.path stringByAppendingPathComponent:fileName]; } @end diff --git a/BakerView/InterceptorWindow.h b/BakerView/BKRInterceptorWindow.h similarity index 91% rename from BakerView/InterceptorWindow.h rename to BakerView/BKRInterceptorWindow.h index a258637c..c3d413df 100644 --- a/BakerView/InterceptorWindow.h +++ b/BakerView/BKRInterceptorWindow.h @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -31,9 +32,10 @@ #import -@interface InterceptorWindow : UIWindow +@interface BKRInterceptorWindow : UIWindow #pragma mark - Events management -- (void)interceptEvent:(UIEvent *)event; + +- (void)interceptEvent:(UIEvent*)event; @end diff --git a/BakerView/InterceptorWindow.m b/BakerView/BKRInterceptorWindow.m similarity index 76% rename from BakerView/InterceptorWindow.m rename to BakerView/BKRInterceptorWindow.m index 37d54f04..903ec200 100644 --- a/BakerView/InterceptorWindow.m +++ b/BakerView/BKRInterceptorWindow.m @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -29,28 +30,25 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -#import "InterceptorWindow.h" +#import "BKRInterceptorWindow.h" -@implementation InterceptorWindow +@implementation BKRInterceptorWindow #pragma mark - Events management -- (void)sendEvent:(UIEvent *)event { - +- (void)sendEvent:(UIEvent*)event { [super sendEvent:event]; [self interceptEvent:event]; } -- (void)interceptEvent:(UIEvent *)event { - - if (event.type == UIEventTypeTouches) - { - NSSet *touches = [event allTouches]; - if (touches.count == 1) - { - UITouch *touch = touches.anyObject; - NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:touch, @"touch", nil]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"notification_touch_intercepted" object:nil userInfo:userInfo]; +- (void)interceptEvent:(UIEvent*)event { + if (event.type == UIEventTypeTouches) { + NSSet *touches = event.allTouches; + if (touches.count == 1) { + [[NSNotificationCenter defaultCenter] postNotificationName:@"notification_touch_intercepted" + object:nil + userInfo:@{@"touch": touches.anyObject} + ]; } } } diff --git a/BakerView/ModalViewController.h b/BakerView/BKRModalWebViewController.h similarity index 73% rename from BakerView/ModalViewController.h rename to BakerView/BKRModalWebViewController.h index 6e289da5..1220ecfa 100755 --- a/BakerView/ModalViewController.h +++ b/BakerView/BKRModalWebViewController.h @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -31,23 +32,20 @@ #import -@protocol modalWebViewDelegate; +@protocol BKRModalWebViewControllerDelegate; -@interface ModalViewController : UIViewController -{ - id delegate; - NSURL *myUrl; -} +@interface BKRModalWebViewController : UIViewController -@property (assign, nonatomic) id delegate; -@property (strong, nonatomic) UIWebView *webView; -@property (strong, nonatomic) UIToolbar *toolbar; -@property (strong, nonatomic) UIBarButtonItem *btnGoBack; -@property (strong, nonatomic) UIBarButtonItem *btnGoForward; -@property (strong, nonatomic) UIBarButtonItem *btnReload; -@property (strong, nonatomic) UIActivityIndicatorView *spinner; +@property (nonatomic, strong) NSURL *initialURL; +@property (nonatomic, weak) id delegate; +@property (nonatomic, strong) UIWebView *webView; +@property (nonatomic, strong) UIToolbar *toolbar; +@property (nonatomic, strong) UIBarButtonItem *btnGoBack; +@property (nonatomic, strong) UIBarButtonItem *btnGoForward; +@property (nonatomic, strong) UIBarButtonItem *btnReload; +@property (nonatomic, strong) UIActivityIndicatorView *spinner; -- (id)initWithUrl:(NSURL *)url; +- (id)initWithURL:(NSURL*)url; - (void)dismissAction; - (void)goBack; - (void)goForward; @@ -56,7 +54,7 @@ @end -@protocol modalWebViewDelegate +@protocol BKRModalWebViewControllerDelegate - (void)closeModalWebView; - (void)webView:(UIWebView *)webView setCorrectOrientation:(UIInterfaceOrientation)interfaceOrientation; diff --git a/BakerView/BKRModalWebViewController.m b/BakerView/BKRModalWebViewController.m new file mode 100755 index 00000000..53131e99 --- /dev/null +++ b/BakerView/BKRModalWebViewController.m @@ -0,0 +1,228 @@ +// +// ModalViewController.m +// Baker +// +// ========================================================================================== +// +// Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout +// 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 Baker Framework nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written +// permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// ========================================================================================== +// + +#import "BKRModalWebViewController.h" +#import "UIColor+BakerExtensions.h" +#import "BKRUtils.h" +#import "BKRSettings.h" + +#import "UIScreen+BakerExtensions.h" + +@implementation BKRModalWebViewController + +#pragma mark - Initialization + +- (id)initWithURL:(NSURL*)url { + self = [super init]; + if (self) { + _initialURL = url; + } + return self; +} + +#pragma mark - View Lifecycle + +- (void)loadView { + + [super loadView]; + + // ****** Buttons + UIBarButtonItem *btnClose = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"WEB_MODAL_CLOSE_BUTTON_TEXT", nil) + style:UIBarButtonItemStyleBordered + target:self + action:@selector(dismissAction)]; + + UIBarButtonItem *btnAction = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(openInSafari)]; + + self.btnGoBack = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"back"] style:UIBarButtonItemStylePlain target:self action:@selector(goBack)]; + self.btnGoBack.enabled = NO; + self.btnGoBack.width = 30; + + self.btnGoForward = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"forward"] style:UIBarButtonItemStylePlain target:self action:@selector(goForward)]; + self.btnGoForward.enabled = NO; + self.btnGoForward.width = 30; + + self.btnReload = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(reloadPage)]; + self.btnReload.enabled = NO; + self.btnGoForward.width = 30; + + btnClose.tintColor = [UIColor bkrColorWithHexString:[BKRSettings sharedSettings].issuesActionBackgroundColor]; + btnAction.tintColor = [UIColor bkrColorWithHexString:[BKRSettings sharedSettings].issuesActionBackgroundColor]; + self.btnGoBack.tintColor = [UIColor bkrColorWithHexString:[BKRSettings sharedSettings].issuesActionBackgroundColor]; + self.btnGoForward.tintColor = [UIColor bkrColorWithHexString:[BKRSettings sharedSettings].issuesActionBackgroundColor]; + self.btnReload.tintColor = [UIColor bkrColorWithHexString:[BKRSettings sharedSettings].issuesActionBackgroundColor]; + + self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + self.spinner.frame = CGRectMake(3, 3, 25, 25); + self.spinner.hidesWhenStopped = YES; + + [self.spinner startAnimating]; + + UIBarButtonItem *btnSpinner = [[UIBarButtonItem alloc] initWithCustomView:self.spinner]; + btnSpinner.width = 30; + + UIBarButtonItem *spacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; + + // ****** Add Toolbar + self.toolbar = [UIToolbar new]; + self.toolbar.barStyle = UIBarStyleDefault; + + // ****** Add items to toolbar + if ([self.initialURL.scheme isEqualToString:@"file"]) { + NSArray *items = @[btnClose, self.btnGoBack, self.btnGoForward, btnSpinner, spacer]; + [self.toolbar setItems:items animated:NO]; + } else { + NSArray *items = @[btnClose, self.btnGoBack, self.btnGoForward, self.btnReload, btnSpinner, spacer, btnAction]; + [self.toolbar setItems:items animated:NO]; + } + + // ****** Add WebView + self.webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 44, 1, 1)]; + self.webView.contentMode = UIViewContentModeScaleToFill; + self.webView.scalesPageToFit = YES; + self.webView.delegate = self; + + // ****** View + self.view = [UIView new]; + + // ****** Attach + [self.view addSubview:self.toolbar]; + [self.view addSubview:self.webView]; + + // ****** Set views starting frames according to current interface rotation + [self willRotateToInterfaceOrientation:self.interfaceOrientation duration:0]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.backgroundColor = [UIColor whiteColor]; + [self.webView loadRequest:[NSURLRequest requestWithURL:self.initialURL]]; +} + +- (void)dealloc { + [self.webView stopLoading]; + [self.webView removeFromSuperview]; + self.webView.delegate = nil; +} + +- (BOOL)prefersStatusBarHidden { + return YES; +} + +#pragma mark - UIWebViewDelegate + +- (void)webViewDidStartLoad:(UIWebView*)webViewIn { + // NSLog(@"[Modal] Loading '%@'", [webViewIn.request.URL absoluteString]); <-- this isn't returning the URL correctly, check + [self.spinner startAnimating]; +} + +- (void)webViewDidFinishLoad:(UIWebView*)webViewIn { + + //NSLog(@"[Modal] Finish loading."); + [self.delegate webView:webViewIn setCorrectOrientation:self.interfaceOrientation]; + + [self.spinner stopAnimating]; + + self.btnGoBack.enabled = [webViewIn canGoBack]; + self.btnGoForward.enabled = [webViewIn canGoForward]; + self.btnReload.enabled = YES; + +} + +- (void)webView:(UIWebView*)webViewIn didFailLoadWithError:(NSError*)error { + NSLog(@"[Modal] Failed to load '%@', error code %li", [webViewIn.request.URL absoluteString], (long)error.code); + if (error.code == -1009) { + + UILabel *errorLabel = [[UILabel alloc] initWithFrame:self.webView.frame]; + errorLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + errorLabel.textAlignment = NSTextAlignmentCenter; + errorLabel.textColor = [UIColor grayColor]; + errorLabel.text = NSLocalizedString(@"WEB_MODAL_FAILURE_MESSAGE", nil); + errorLabel.numberOfLines = 1; + + CGRect screenBounds = [[UIScreen mainScreen] bounds]; + if (MIN(screenBounds.size.width, screenBounds.size.height) < 768) { + errorLabel.font = [UIFont fontWithName:@"Helvetica" size:14.0]; + } else { + errorLabel.font = [UIFont fontWithName:@"Helvetica" size:18.0]; + } + + [self.view addSubview:errorLabel]; + } + + [self.spinner stopAnimating]; +} + +#pragma mark - Actions + +- (void)dismissAction { + [[self delegate] closeModalWebView]; +} + +- (void)goBack { + [self.webView goBack]; +} + +- (void)goForward { + [self.webView goForward]; +} + +- (void)reloadPage { + [self.webView reload]; +} + +- (void)openInSafari { + [[UIApplication sharedApplication] openURL:self.webView.request.URL]; +} + +#pragma mark - Orientation + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation { + return [self.delegate shouldAutorotateToInterfaceOrientation:orientation]; +} + +- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { + + CGFloat screenWidth = [[UIScreen mainScreen] bkrWidthForOrientation:toInterfaceOrientation]; + CGFloat screenHeight = [[UIScreen mainScreen] bkrHeightForOrientation:toInterfaceOrientation]; + + self.view.frame = CGRectMake(0, 0, screenWidth, screenHeight); + self.toolbar.frame = CGRectMake(0, 0, screenWidth, 44); + self.webView.frame = CGRectMake(0, 44, screenWidth, screenHeight - 44); + + [self.delegate webView:self.webView setCorrectOrientation:toInterfaceOrientation]; + +} + +@end diff --git a/BakerView/BKRSettings.h b/BakerView/BKRSettings.h new file mode 100644 index 00000000..fe9a86bf --- /dev/null +++ b/BakerView/BKRSettings.h @@ -0,0 +1,123 @@ +// +// BKRSettings.h +// Baker +// +// ========================================================================================== +// +// Copyright (c) 2014, Pieter Claerhout +// 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 Baker Framework nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written +// permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#import + +@interface BKRSettings : NSObject + +// Timeout for most network requests (in seconds) +@property (nonatomic, readonly) NSTimeInterval requestTimeout; + +@property (nonatomic, readonly) BOOL isNewsstand; + +// ---------------------------------------------------------------------------------------------------- +// Mandatory - This constant defines where the JSON file containing all the publications is located. +// For more information on this file, see: https://github.com/Simbul/baker/wiki/Newsstand-shelf-JSON +// E.g. @"http://example.com/shelf.json" +@property (nonatomic, readonly) NSString *newsstandManifestUrl; + +@property (nonatomic, readonly) BOOL newsstandLatestIssueCover; + +// ---------------------------------------------------------------------------------------------------- +// Optional - This constant specifies the URL to ping back when a user purchases an issue or a subscription. +// For more information, see: https://github.com/Simbul/baker/wiki/Baker-Server-API +// E.g. @"http://example.com/purchased" +@property (nonatomic, readonly) NSString *purchaseConfirmationUrl; + +// ---------------------------------------------------------------------------------------------------- +// Optional - This constant specifies a URL that will be used to retrieve the list of purchased issues. +// For more information, see: https://github.com/Simbul/baker/wiki/Baker-Server-API +// E.g. @"http://example.com/purchases" +@property (nonatomic, readonly) NSString *purchasesUrl; + +// ---------------------------------------------------------------------------------------------------- +// Optional - This constant specifies the URL to ping back when a user enables push notifications. +// For more information, see: https://github.com/Simbul/baker/wiki/Baker-Server-API +// E.g. @"http://example.com/post_apns_token" +@property (nonatomic, readonly) NSString *postApnsTokenUrl; + +// ---------------------------------------------------------------------------------------------------- +// Mandatory - The following two constants identify the subscriptions you set up in iTunesConnect. +// See: iTunes Connect -> Manage Your Application -> (Your application) -> Manage In App Purchases + +// This constant identifies a free subscription. +// E.g. @"com.example.MyBook.subscription.free" +@property (nonatomic, readonly) NSString *freeSubscriptionProductId; + +// This constant identifies one or more auto-renewable subscriptions. +@property (nonatomic, readonly) NSArray *autoRenewableSubscriptionProductIds; + +// Pull subscriptions localisations from iTunes or localizedString +@property (nonatomic, readonly) BOOL useiTunesConnectLocalizations; + +// Background color for issues cover (before downloading the actual cover) +@property (nonatomic, readonly) NSString *issuesCoverBackgroundColor; + +// Title for issues in the shelf +@property (nonatomic, readonly) NSString *issuesTitleFont; +@property (nonatomic, readonly) int issuesTitleFontSize; +@property (nonatomic, readonly) NSString *issuesTitleColor; + +// Info text for issues in the shelf +@property (nonatomic, readonly) NSString *issuesInfoFont; +@property (nonatomic, readonly) int issuesInfoFontSize; +@property (nonatomic, readonly) NSString *issuesInfoColor; + +@property (nonatomic, readonly) NSString *issuesPriceColor; + +// Download/read button for issues in the shelf +@property (nonatomic, readonly) NSString *issuesActionFont; +@property (nonatomic, readonly) int issuesActionFontSize; +@property (nonatomic, readonly) NSString *issuesActionBackgroundColor; +@property (nonatomic, readonly) NSString *issuesActionButtonColor; + +// Archive button for issues in the shelf +@property (nonatomic, readonly) NSString *issuesArchiveFont; +@property (nonatomic, readonly) int issuesArchiveFontSize; +@property (nonatomic, readonly) NSString *issuesArchiveBackgroundColor; +@property (nonatomic, readonly) NSString *issuesArchiveButtonColor; + +// Text and spinner for issues that are being loaded in the shelf +@property (nonatomic, readonly) NSString *issuesLoadingLabelColor; +@property (nonatomic, readonly) NSString *issuesLoadingSpinnerColor; + +// Progress bar for issues that are being downloaded in the shelf +@property (nonatomic, readonly) NSString *issuesProgressbarTintColor; + +// Shelf background customization +@property (nonatomic, readonly) NSDictionary *issuesShelfOptions; + +// Social Share Button +@property (nonatomic, readonly) BOOL showSocialShareButton; + ++ (BKRSettings*)sharedSettings; + +@end diff --git a/BakerView/BKRSettings.m b/BakerView/BKRSettings.m new file mode 100644 index 00000000..38986573 --- /dev/null +++ b/BakerView/BKRSettings.m @@ -0,0 +1,189 @@ +// +// BKRSettings.h +// Baker +// +// ========================================================================================== +// +// Copyright (c) 2014, Pieter Claerhout +// 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 Baker Framework nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written +// permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +// +// !! IMPORTANT !! +// DO NOT ALTER THIS FILE. SETTINGS ARE DEFINED IN SETTINGS.PLIST INSTEAD! +// !! IMPORTANT !! +// + +#import "BKRSettings.h" + +#pragma mark - Private + +@interface BKRSettings () + +@property (nonatomic, readonly) NSDictionary *settings; + +@end + +@implementation BKRSettings + +#pragma mark - Shared Instance + ++ (BKRSettings*)sharedSettings { + static BKRSettings *_sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _sharedInstance = [[self alloc] init]; + }); + return _sharedInstance; +} + +#pragma mark - Instance Methods + +- (id)init { + self = [super init]; + if (self) { + + NSString *settingsPath = [[NSBundle mainBundle] pathForResource:@"settings" ofType:@"plist"]; + if ([[NSFileManager defaultManager] fileExistsAtPath:settingsPath]) { + _settings = [NSDictionary dictionaryWithContentsOfFile:settingsPath]; + } else { + _settings = @{}; + } + + NSLog(@"Settings: %@", _settings); + + _requestTimeout = [self doubleSettingForKey:@"requestTimeout" withDefault:15]; + _isNewsstand = [self boolSettingForKey:@"isNewsstand" withDefault:YES]; + _newsstandLatestIssueCover = [self boolSettingForKey:@"newsstandLatestIssueCover" withDefault:YES]; + + _newsstandManifestUrl = [self stringSettingForKey:@"newsstandManifestUrl" withDefault:@"http://bakerframework.com/demo/shelf.json"]; + _purchaseConfirmationUrl = [self stringSettingForKey:@"purchaseConfirmationUrl" withDefault:@""]; + _purchasesUrl = [self stringSettingForKey:@"purchasesUrl" withDefault:@""]; + _postApnsTokenUrl = [self stringSettingForKey:@"postApnsTokenUrl" withDefault:@""]; + _freeSubscriptionProductId = [self stringSettingForKey:@"freeSubscriptionProductId" withDefault:@""]; + _autoRenewableSubscriptionProductIds = [self arraySettingForKey:@"autoRenewableSubscriptionProductIds" withDefault:@[]]; + + _useiTunesConnectLocalizations = [self boolSettingForKey:@"useiTunesConnectLocalizations" withDefault:YES]; + + _issuesCoverBackgroundColor = [self stringSettingForKey:@"issuesCoverBackgroundColor" withDefault:@"#ffffff"]; + + _issuesTitleFont = [self stringSettingForKey:@"issuesTitleFont" withDefault:@"Helvetica"]; + _issuesTitleFontSize = [self intSettingForKey:@"issuesTitleFontSize" withDefault:15]; + _issuesTitleColor = [self stringSettingForKey:@"issuesTitleColor" withDefault:@"#000000"]; + + _issuesInfoFont = [self stringSettingForKey:@"issuesInfoFont" withDefault:@"Helvetica"]; + _issuesInfoFontSize = [self intSettingForKey:@"issuesInfoFontSize" withDefault:15]; + _issuesInfoColor = [self stringSettingForKey:@"issuesInfoColor" withDefault:@"#929292"]; + + _issuesPriceColor = [self stringSettingForKey:@"issuesPriceColor" withDefault:@"#bc242a"]; + + _issuesActionFont = [self stringSettingForKey:@"issuesActionFont" withDefault:@"Helvetica-Bold"]; + _issuesActionFontSize = [self intSettingForKey:@"issuesActionFontSize" withDefault:11]; + _issuesActionBackgroundColor = [self stringSettingForKey:@"issuesActionBackgroundColor" withDefault:@"#bc242a"]; + _issuesActionButtonColor = [self stringSettingForKey:@"issuesActionButtonColor" withDefault:@"#ffffff"]; + + _issuesArchiveFont = [self stringSettingForKey:@"issuesArchiveFont" withDefault:@"Helvetica-Bold"]; + _issuesArchiveFontSize = [self intSettingForKey:@"issuesArchiveFontSize" withDefault:11]; + _issuesArchiveBackgroundColor = [self stringSettingForKey:@"issuesArchiveBackgroundColor" withDefault:@"#bc242a"]; + _issuesArchiveButtonColor = [self stringSettingForKey:@"issuesArchiveButtonColor" withDefault:@"#ffffff"]; + + _issuesLoadingLabelColor = [self stringSettingForKey:@"issuesLoadingLabelColor" withDefault:@"#bc242a"]; + _issuesLoadingSpinnerColor = [self stringSettingForKey:@"issuesLoadingSpinnerColor" withDefault:@"#929292"]; + + _issuesProgressbarTintColor = [self stringSettingForKey:@"issuesProgressbarTintColor" withDefault:@"#bc242a"]; + + _issuesShelfOptions = [self dictionarySettingForKey:@"issuesShelfOptions" withDefault:@{}]; + _showSocialShareButton = [self boolSettingForKey:@"showSocialShareButton" withDefault:NO]; + + } + return self; +} + +#pragma mark - Helpers + +- (NSString*)stringSettingForKey:(NSString*)setting withDefault:(NSString*)defaultValue { + if (self.settings && [self.settings objectForKey:setting]) { + return [self.settings objectForKey:setting]; + } else { + return defaultValue; + } +} + +- (NSDictionary*)dictionarySettingForKey:(NSString*)setting withDefault:(NSDictionary*)defaultValue { + if (self.settings && [self.settings objectForKey:setting]) { + return [self.settings objectForKey:setting]; + } else { + return defaultValue; + } +} + +- (NSArray*)arraySettingForKey:(NSString*)setting withDefault:(NSArray*)defaultValue { + if (self.settings && [self.settings objectForKey:setting]) { + return [self.settings objectForKey:setting]; + } else { + return defaultValue; + } +} + +- (NSDate*)dateSettingForKey:(NSString*)setting withDefault:(NSDate*)defaultValue { + if (self.settings && [self.settings objectForKey:setting]) { + return [self.settings objectForKey:setting]; + } else { + return defaultValue; + } +} + +- (NSNumber*)numberSettingForKey:(NSString*)setting withDefault:(NSNumber*)defaultValue { + if (self.settings && [self.settings objectForKey:setting]) { + return [self.settings objectForKey:setting]; + } else { + return defaultValue; + } +} + +- (BOOL)boolSettingForKey:(NSString*)setting withDefault:(BOOL)defaultValue { + if (self.settings && [self.settings objectForKey:setting]) { + return [[self.settings objectForKey:setting] boolValue]; + } else { + return defaultValue; + } +} + +- (int)intSettingForKey:(NSString*)setting withDefault:(int)defaultValue { + if (self.settings && [self.settings objectForKey:setting]) { + return [[self.settings objectForKey:setting] intValue]; + } else { + return defaultValue; + } +} + +- (double)doubleSettingForKey:(NSString*)setting withDefault:(double)defaultValue { + if (self.settings && [self.settings objectForKey:setting]) { + return [[self.settings objectForKey:setting] doubleValue]; + } else { + return defaultValue; + } +} + +@end diff --git a/BakerView/BKRShelfViewLayout.h b/BakerView/BKRShelfViewLayout.h new file mode 100644 index 00000000..a1c36834 --- /dev/null +++ b/BakerView/BKRShelfViewLayout.h @@ -0,0 +1,18 @@ +// +// BKRShelfViewLayout.h +// Baker +// +// Created by Tobias Strebitzer on 10/11/14. +// +// + +#import + +@interface BKRShelfViewLayout : UICollectionViewFlowLayout + +@property (nonatomic, readonly) BOOL isSticky; +@property (nonatomic, readonly) BOOL isStretch; + +- (id)initWithSticky:(BOOL)sticky stretch:(BOOL)stretch; + +@end diff --git a/BakerView/BKRShelfViewLayout.m b/BakerView/BKRShelfViewLayout.m new file mode 100644 index 00000000..eab78e31 --- /dev/null +++ b/BakerView/BKRShelfViewLayout.m @@ -0,0 +1,132 @@ +// +// BKRShelfViewLayout.m +// Baker +// +// Created by Tobias Strebitzer on 10/11/14. +// +// + +#import "BKRShelfViewLayout.h" + +@implementation BKRShelfViewLayout + +- (id)initWithSticky:(BOOL)sticky stretch:(BOOL)stretch { + + self = [super init]; + if (self) { + _isSticky = sticky; + _isStretch = stretch; + } + + return self; +} + +- (NSArray *) layoutAttributesForElementsInRect:(CGRect)rect { + + // Get attributes + NSMutableArray *attributes = [[super layoutAttributesForElementsInRect:rect] mutableCopy]; + + // Sticky header + if(_isSticky) { + NSMutableIndexSet *missingSections = [NSMutableIndexSet indexSet]; + for (NSUInteger idx=0; idx<[attributes count]; idx++) { + UICollectionViewLayoutAttributes *layoutAttributes = attributes[idx]; + + if (layoutAttributes.representedElementCategory == UICollectionElementCategoryCell) { + [missingSections addIndex:layoutAttributes.indexPath.section]; + } + if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) { + [attributes removeObjectAtIndex:idx]; + idx--; + } + } + + // layout all headers needed for the rect + [missingSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:idx]; + UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath]; + if (layoutAttributes != nil) { + [attributes addObject:layoutAttributes]; + } + }]; + } + + // Stretch header + if(_isStretch) { + // Calculate offset + UICollectionView *collectionView = [self collectionView]; + UIEdgeInsets insets = [collectionView contentInset]; + CGPoint offset = [collectionView contentOffset]; + CGFloat minY = -insets.top; + + // Check if we've pulled below past the lowest position + if (offset.y < minY) { + + // Figure out how much we've pulled down + CGFloat deltaY = fabs(offset.y - minY); + + for (UICollectionViewLayoutAttributes *attrs in attributes) { + + // Locate the header attributes + NSString *kind = [attrs representedElementKind]; + if (kind == UICollectionElementKindSectionHeader) { + + // Adjust the header's height and y based on how much the user + // has pulled down. + CGSize headerSize = [self headerReferenceSize]; + CGRect headerRect = [attrs frame]; + headerRect.size.height = MAX(minY, headerSize.height + deltaY); + headerRect.origin.y = headerRect.origin.y - deltaY; + [attrs setFrame:headerRect]; + break; + } + } + } + } + + return attributes; +} + +- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { + UICollectionViewLayoutAttributes *attributes = [super layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath]; + if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { + UICollectionView * const cv = self.collectionView; + CGPoint const contentOffset = cv.contentOffset; + CGPoint nextHeaderOrigin = CGPointMake(INFINITY, INFINITY); + + if (indexPath.section+1 < [cv numberOfSections]) { + UICollectionViewLayoutAttributes *nextHeaderAttributes = [super layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:[NSIndexPath indexPathForItem:0 inSection:indexPath.section+1]]; + nextHeaderOrigin = nextHeaderAttributes.frame.origin; + } + + CGRect frame = attributes.frame; + if (self.scrollDirection == UICollectionViewScrollDirectionVertical) { + frame.origin.y = MIN(MAX(contentOffset.y, frame.origin.y), nextHeaderOrigin.y - CGRectGetHeight(frame)); + } + else { // UICollectionViewScrollDirectionHorizontal + frame.origin.x = MIN(MAX(contentOffset.x, frame.origin.x), nextHeaderOrigin.x - CGRectGetWidth(frame)); + } + attributes.zIndex = 1024; + attributes.frame = frame; + } + return attributes; +} + +- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { + UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath]; + return attributes; +} +- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { + UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath]; + return attributes; +} + +- (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBound { + return YES; +} + +- (UICollectionViewScrollDirection)scrollDirection { + return UICollectionViewScrollDirectionVertical; +} + +@end diff --git a/BakerView/BakerBook.m b/BakerView/BakerBook.m deleted file mode 100644 index 1ac00de5..00000000 --- a/BakerView/BakerBook.m +++ /dev/null @@ -1,481 +0,0 @@ -// -// BakerBook.m -// Baker -// -// ========================================================================================== -// -// Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi -// 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 Baker Framework nor the names of its contributors may be used to -// endorse or promote products derived from this software without specific prior written -// permission. -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT -// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// - -#import "BakerBook.h" -#import "NSString+Extensions.h" - -@implementation BakerBook - -#pragma mark - HPub parameters synthesis - -@synthesize hpub; -@synthesize title; -@synthesize date; - -@synthesize author; -@synthesize creator; -@synthesize publisher; - -@synthesize url; -@synthesize cover; - -@synthesize orientation; -@synthesize zoomable; - -@synthesize contents; - -#pragma mark - Baker HPub extensions synthesis - -@synthesize bakerBackground; -@synthesize bakerBackgroundImagePortrait; -@synthesize bakerBackgroundImageLandscape; -@synthesize bakerPageNumbersColor; -@synthesize bakerPageNumbersAlpha; -@synthesize bakerPageScreenshots; - -@synthesize bakerRendering; -@synthesize bakerVerticalBounce; -@synthesize bakerVerticalPagination; -@synthesize bakerPageTurnTap; -@synthesize bakerPageTurnSwipe; -@synthesize bakerMediaAutoplay; - -@synthesize bakerIndexWidth; -@synthesize bakerIndexHeight; -@synthesize bakerIndexBounce; -@synthesize bakerStartAtPage; - -#pragma mark - Book status synthesis - -@synthesize ID; -@synthesize path; -@synthesize isBundled; -@synthesize screenshotsPath; -@synthesize screenshotsWritable; -@synthesize currentPage; -@synthesize lastScrollIndex; -@synthesize lastOpenedDate; - -#pragma mark - Init - -- (id)initWithBookPath:(NSString *)bookPath bundled:(BOOL)bundled -{ - if (![[NSFileManager defaultManager] fileExistsAtPath:bookPath]) { - return nil; - } - - self = [self initWithBookJSONPath:[bookPath stringByAppendingPathComponent:@"book.json"]]; - if (self) { - [self updateBookPath:bookPath bundled:bundled]; - } - - return self; -} -- (id)initWithBookJSONPath:(NSString *)bookJSONPath -{ - if (![[NSFileManager defaultManager] fileExistsAtPath:bookJSONPath]) { - return nil; - } - - NSError* error = nil; - NSData* bookJSON = [NSData dataWithContentsOfFile:bookJSONPath options:0 error:&error]; - if (error) { - NSLog(@"[BakerBook] ERROR reading 'book.json': %@", error.localizedDescription); - return nil; - } - - NSDictionary* bookData = [NSJSONSerialization JSONObjectWithData:bookJSON - options:0 - error:&error]; - if (error) { - NSLog(@"[BakerBook] ERROR parsing 'book.json': %@", error.localizedDescription); - return nil; - } - - return [self initWithBookData:bookData]; -} -- (id)initWithBookData:(NSDictionary *)bookData -{ - self = [super init]; - if (self && [self loadBookData:bookData]) { - NSString *baseID = [self.title stringByAppendingFormat:@" %@", [self.url stringSHAEncoded]]; - self.ID = [self sanitizeForPath:baseID]; - - NSLog(@"[BakerBook] 'book.json' parsed successfully. Book '%@' created with id '%@'.", self.title, self.ID); - return self; - } - - return nil; -} -- (NSString *)sanitizeForPath:(NSString *)string -{ - NSError *error = nil; - NSString *newString; - NSRegularExpression *regex; - - // Strip everything except numbers, ASCII letters and spaces - regex = [NSRegularExpression regularExpressionWithPattern:@"[^1-9a-z ]" options:NSRegularExpressionCaseInsensitive error:&error]; - newString = [regex stringByReplacingMatchesInString:string options:0 range:NSMakeRange(0, [string length]) withTemplate:@""]; - - // Replace spaces with dashes - regex = [NSRegularExpression regularExpressionWithPattern:@" +" options:NSRegularExpressionCaseInsensitive error:&error]; - newString = [regex stringByReplacingMatchesInString:newString options:0 range:NSMakeRange(0, [newString length]) withTemplate:@"-"]; - - return [newString lowercaseString]; -} -- (BOOL)loadBookData:(NSDictionary *)bookData -{ - if (![self validateBookJSON:bookData withRequirements:[NSArray arrayWithObjects:@"title", @"author", @"url", @"contents", nil]]) { - return NO; - } - - self.hpub = [bookData objectForKey:@"hpub"]; - self.title = [bookData objectForKey:@"title"]; - self.date = [bookData objectForKey:@"date"]; - - if ([[bookData objectForKey:@"author"] isKindOfClass:[NSArray class]]) { - self.author = [bookData objectForKey:@"author"]; - } else { - self.author = [NSArray arrayWithObject:[bookData objectForKey:@"author"]]; - } - - if ([[bookData objectForKey:@"creator"] isKindOfClass:[NSArray class]]) { - self.creator = [bookData objectForKey:@"creator"]; - } else if([[bookData objectForKey:@"creator"] isKindOfClass:[NSString class]]) { - self.creator = [NSArray arrayWithObject:[bookData objectForKey:@"creator"]]; - } - - self.publisher = [bookData objectForKey:@"publisher"]; - - self.url = [bookData objectForKey:@"url"]; - self.cover = [bookData objectForKey:@"cover"]; - - self.orientation = [bookData objectForKey:@"orientation"]; - self.zoomable = [bookData objectForKey:@"zoomable"]; - - // TODO: create an array of n BakerPage objects - self.contents = [bookData objectForKey:@"contents"]; - - self.bakerBackground = [bookData objectForKey:@"-baker-background"]; - self.bakerBackgroundImagePortrait = [bookData objectForKey:@"-baker-background-image-portrait"]; - self.bakerBackgroundImageLandscape = [bookData objectForKey:@"-baker-background-image-landscape"]; - self.bakerPageNumbersColor = [bookData objectForKey:@"-baker-page-numbers-color"]; - self.bakerPageNumbersAlpha = [bookData objectForKey:@"-baker-page-numbers-alpha"]; - self.bakerPageScreenshots = [bookData objectForKey:@"-baker-page-screenshots"]; - - self.bakerRendering = [bookData objectForKey:@"-baker-rendering"]; - self.bakerVerticalBounce = [bookData objectForKey:@"-baker-vertical-bounce"]; - self.bakerVerticalPagination = [bookData objectForKey:@"-baker-vertical-pagination"]; - self.bakerPageTurnTap = [bookData objectForKey:@"-baker-page-turn-tap"]; - self.bakerPageTurnSwipe = [bookData objectForKey:@"-baker-page-turn-swipe"]; - self.bakerMediaAutoplay = [bookData objectForKey:@"-baker-media-autoplay"]; - - self.bakerIndexWidth = [bookData objectForKey:@"-baker-index-width"]; - self.bakerIndexHeight = [bookData objectForKey:@"-baker-index-height"]; - self.bakerIndexBounce = [bookData objectForKey:@"-baker-index-bounce"]; - self.bakerStartAtPage = [bookData objectForKey:@"-baker-start-at-page"]; - - [self loadBookJSONDefault]; - - return YES; -} -- (void)loadBookJSONDefault -{ - if (self.hpub == nil) { - self.hpub = [NSNumber numberWithInt:1]; - } - - if (self.bakerBackground == nil) { - self.bakerBackground = @"#000000"; - } - if (self.bakerPageNumbersColor == nil) { - self.bakerPageNumbersColor = @"#ffffff"; - } - if (self.bakerPageNumbersAlpha == nil) { - self.bakerPageNumbersAlpha = [NSNumber numberWithFloat:0.3]; - } - - if (self.bakerRendering == nil) { - self.bakerRendering = @"screenshots"; - } - if (self.bakerVerticalBounce == nil) { - self.bakerVerticalBounce = [NSNumber numberWithBool:YES]; - } - if (self.bakerVerticalPagination == nil) { - self.bakerVerticalPagination = [NSNumber numberWithBool:NO]; - } - - if (self.bakerPageTurnTap == nil) { - self.bakerPageTurnTap = [NSNumber numberWithBool:YES]; - } - - if (self.bakerPageTurnSwipe == nil) { - self.bakerPageTurnSwipe = [NSNumber numberWithBool:YES]; - } - if (self.bakerMediaAutoplay == nil) { - self.bakerMediaAutoplay = [NSNumber numberWithBool:NO]; - } - - if (self.bakerIndexBounce == nil) { - self.bakerIndexBounce = [NSNumber numberWithBool:NO]; - } - if (self.bakerStartAtPage == nil) { - self.bakerStartAtPage = [NSNumber numberWithInt:1]; - } -} - - -#pragma mark - HPub validation - -- (BOOL)validateBookJSON:(NSDictionary *)bookData withRequirements:(NSArray *)requirements -{ - for (NSString *param in requirements) { - if ([bookData objectForKey:param] == nil) { - NSLog(@"[BakerBook] ERROR: param '%@' is missing. Add it to 'book.json'.", param); - return NO; - } - } - - for (NSString *param in bookData) { - //NSLog(@"[BakerBook] Validating 'book.json' param: '%@'.", param); - - id obj = [bookData objectForKey:param]; - if ([obj isKindOfClass:[NSArray class]] && ![self validateArray:(NSArray *)obj forParam:param]) { - return NO; - } else if ([obj isKindOfClass:[NSString class]] && ![self validateString:(NSString *)obj forParam:param]) { - return NO; - } else if ([obj isKindOfClass:[NSNumber class]] && ![self validateNumber:(NSNumber *)obj forParam:param]) { - return NO; - } - } - - return YES; -} -- (BOOL)validateArray:(NSArray *)array forParam:(NSString *)param -{ - NSArray *shouldBeArray = [NSArray arrayWithObjects:@"author", - @"creator", - @"contents", nil]; - - - if (![self matchParam:param againstParamsArray:shouldBeArray]) { - NSLog(@"[BakerBook] ERROR: param '%@' should not be an Array. Check it in 'book.json'.", param); - return NO; - } - - if (([param isEqualToString:@"author"] || [param isEqualToString:@"contents"]) && [array count] == 0) { - NSLog(@"[BakerBook] ERROR: param '%@' is empty. Fill it in 'book.json'.", param); - return NO; - } - - for (id obj in array) { - if ([param isEqualToString:@"author"] && (![obj isKindOfClass:[NSString class]] || [(NSString *)obj isEqualToString:@""])) { - NSLog(@"[BakerBook] ERROR: param 'author' is empty. Fill it in 'book.json'."); - return NO; - } else if ([param isEqualToString:@"contents"]) { - if ([obj isKindOfClass:[NSDictionary class]] && ![self validateBookJSON:(NSDictionary *)obj withRequirements:[NSArray arrayWithObjects:@"url", nil]]) { - NSLog(@"[BakerBook] ERROR: param 'contents' is not validating. Check it in 'book.json'."); - return NO; - } - } else if (![obj isKindOfClass:[NSString class]]) { - NSLog(@"[BakerBook] ERROR: param '%@' type is wrong. Check it in 'book.json'.", param); - return NO; - } - } - - return YES; -} -- (BOOL)validateString:(NSString *)string forParam:(NSString *)param -{ - NSArray *shouldBeString = [NSArray arrayWithObjects:@"title", - @"date", - @"author", - @"creator", - @"publisher", - @"url", - @"cover", - @"orientation", - @"-baker-background", - @"-baker-background-image-portrait", - @"-baker-background-image-landscape", - @"-baker-page-numbers-color", - @"-baker-page-screenshots", - @"-baker-rendering", nil]; - - - if (![self matchParam:param againstParamsArray:shouldBeString]) { - NSLog(@"[BakerBook] ERROR: param '%@' should not be a String. Check it in 'book.json'.", param); - return NO; - } - - if (([param isEqualToString:@"title"] || [param isEqualToString:@"author"] || [param isEqualToString:@"url"]) && [string isEqualToString:@""]) { - NSLog(@"[BakerBook] ERROR: param '%@' is empty. Fill it in 'book.json'.", param); - return NO; - } - - if (([param isEqualToString:@"-baker-background"] || [param isEqualToString:@"-baker-page-numbers-color"]) /*&& TODO: not a valid hex*/) { - // return NO; - } - - if ([param isEqualToString:@"-baker-rendering"] && (![string isEqualToString:@"screenshots"] && ![string isEqualToString:@"three-cards"])) { - NSLog(@"Error: param \"-baker-rendering\" should be equal to \"screenshots\" or \"three-cards\" but it's not"); - NSLog(@"[BakerBook] ERROR: param '-baker-rendering' must be equal to 'screenshots' or 'three-cards'. Check it in 'book.json'."); - return NO; - } - - return YES; -} -- (BOOL)validateNumber:(NSNumber *)number forParam:(NSString *)param -{ - NSArray *shouldBeNumber = [NSArray arrayWithObjects:@"hpub", - @"zoomable", - @"-baker-page-numbers-alpha", - @"-baker-vertical-bounce", - @"-baker-vertical-pagination", - @"-baker-page-turn-tap", - @"-baker-page-turn-swipe", - @"-baker-media-autoplay", - @"-baker-index-width", - @"-baker-index-height", - @"-baker-index-bounce", - @"-baker-start-at-page", nil]; - - - if (![self matchParam:param againstParamsArray:shouldBeNumber]) { - NSLog(@"[BakerBook] ERROR: param '%@' should not be a Number. Check it in 'book.json'.", param); - return NO; - } - - return YES; -} -- (BOOL)matchParam:(NSString *)param againstParamsArray:(NSArray *)paramsArray -{ - for (NSString *match in paramsArray) { - if ([param isEqualToString:match]) { - return YES; - } - } - - return NO; -} - -#pragma mark - Book status management - -- (BOOL)updateBookPath:(NSString *)bookPath bundled:(BOOL)bundled -{ - NSFileManager *fileManager = [NSFileManager defaultManager]; - if (![fileManager fileExistsAtPath:bookPath]) { - return NO; - } - - self.path = bookPath; - self.isBundled = [NSNumber numberWithBool:bundled]; - - self.screenshotsPath = [bookPath stringByAppendingPathComponent:self.bakerPageScreenshots]; - self.screenshotsWritable = [NSNumber numberWithBool:YES]; - - if (bundled) { - if (![fileManager fileExistsAtPath:self.screenshotsPath]) { - // TODO: generate writableBookPath in app private documents/books/self.ID; - NSString *writableBookPath = @"writableBookPath"; - self.screenshotsPath = [writableBookPath stringByAppendingPathComponent:self.bakerPageScreenshots]; - } else { - self.screenshotsWritable = [NSNumber numberWithBool:NO]; - } - } - - if (![fileManager fileExistsAtPath:self.screenshotsPath]) { - return [fileManager createDirectoryAtPath:self.screenshotsPath withIntermediateDirectories:YES attributes:nil error:nil]; - } - - return YES; -} -- (void)openBook -{ - // TODO: restore book status from app private documents/statuses/self.ID.json -} -- (void)closeBook -{ - // TODO: serialize with JSONKit and save in app private documents/statuses/self.ID.json -} - -#pragma mark - Memory management - -- (void)dealloc -{ - [hpub release]; - [title release]; - [date release]; - - [author release]; - [creator release]; - [publisher release]; - - [url release]; - [cover release]; - - [orientation release]; - [zoomable release]; - - [contents release]; - - [bakerBackground release]; - [bakerBackgroundImagePortrait release]; - [bakerBackgroundImageLandscape release]; - [bakerPageNumbersColor release]; - [bakerPageNumbersAlpha release]; - [bakerPageScreenshots release]; - - [bakerRendering release]; - [bakerVerticalBounce release]; - [bakerVerticalPagination release]; - [bakerPageTurnTap release]; - [bakerPageTurnSwipe release]; - [bakerMediaAutoplay release]; - - [bakerIndexWidth release]; - [bakerIndexHeight release]; - [bakerIndexBounce release]; - [bakerStartAtPage release]; - - [ID release]; - [path release]; - [isBundled release]; - [screenshotsPath release]; - [screenshotsWritable release]; - [currentPage release]; - [lastScrollIndex release]; - [lastOpenedDate release]; - - [super dealloc]; -} - -@end diff --git a/BakerView/ModalViewController.m b/BakerView/ModalViewController.m deleted file mode 100755 index 8df1d73d..00000000 --- a/BakerView/ModalViewController.m +++ /dev/null @@ -1,315 +0,0 @@ -// -// ModalViewController.m -// Baker -// -// ========================================================================================== -// -// Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi -// 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 Baker Framework nor the names of its contributors may be used to -// endorse or promote products derived from this software without specific prior written -// permission. -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT -// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// ========================================================================================== -// -// USAGE: -// -// In the header (.h), add to @interface: -// -// ModalViewController *modal; -// -// -// In the controller (.m) use this function: -// -// - (void)loadModalWebView:(NSURL *) url { -// // initialize -// myModalViewController = [[[ModalViewController alloc] initWithUrl:url] autorelease]; -// myModalViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical; -// myModalViewController.delegate = self; -// -// // hide the IndexView before opening modal web view -// [self hideStatusBar]; -// -// // check if iOS4 or 5 -// if ([self respondsToSelector:@selector(presentViewController:animated:completion:)]) -// // iOS 5 -// [self presentViewController:myModalViewController animated:YES completion:nil]; -// else -// // iOS 4 -// [self presentModalViewController:myModalViewController animated:YES]; -// } -// - -#import "ModalViewController.h" -#import "UIColor+Extensions.h" -#import "UIConstants.h" -#import "Utils.h" - -@implementation ModalViewController - -@synthesize delegate; -@synthesize webView; -@synthesize toolbar; -@synthesize btnGoBack; -@synthesize btnGoForward; -@synthesize btnReload; -@synthesize spinner; - -#pragma mark - INIT -- (id)initWithUrl:(NSURL *)url { - /**************************************************************************************************** - * This is the main way you'll be using this object. - * Just create the object and call this function. - */ - - self = [super init]; - if (self) { - myUrl = url; - } - return self; -} - -#pragma mark - VIEW LIFECYCLE -- (void)loadView { - /**************************************************************************************************** - * Creates the UI: buttons, toolbar, webview and container view. - */ - - [super loadView]; - - - // ****** Buttons - UIBarButtonItem *btnClose = [[[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"WEB_MODAL_CLOSE_BUTTON_TEXT", nil) - style:UIBarButtonItemStyleBordered - target:self - action:@selector(dismissAction)] autorelease]; - - UIBarButtonItem *btnAction = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(openInSafari)] autorelease]; - - self.btnGoBack = [[[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"back"] style:UIBarButtonItemStylePlain target:self action:@selector(goBack)] autorelease]; - btnGoBack.enabled = NO; - btnGoBack.width = 30; - - self.btnGoForward = [[[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"forward"] style:UIBarButtonItemStylePlain target:self action:@selector(goForward)] autorelease]; - btnGoForward.enabled = NO; - btnGoForward.width = 30; - - self.btnReload = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(reloadPage)] autorelease]; - btnReload.enabled = NO; - btnGoForward.width = 30; - - if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")) { - btnClose.tintColor = [UIColor colorWithHexString:ISSUES_ACTION_BUTTON_BACKGROUND_COLOR]; - btnAction.tintColor = [UIColor colorWithHexString:ISSUES_ACTION_BUTTON_BACKGROUND_COLOR]; - btnGoBack.tintColor = [UIColor colorWithHexString:ISSUES_ACTION_BUTTON_BACKGROUND_COLOR]; - btnGoForward.tintColor = [UIColor colorWithHexString:ISSUES_ACTION_BUTTON_BACKGROUND_COLOR]; - btnReload.tintColor = [UIColor colorWithHexString:ISSUES_ACTION_BUTTON_BACKGROUND_COLOR]; - } - - self.spinner = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray] autorelease]; - spinner.frame = CGRectMake(3, 3, 25, 25); - spinner.hidesWhenStopped = YES; - - [spinner startAnimating]; - - UIBarButtonItem *btnSpinner = [[[UIBarButtonItem alloc] initWithCustomView:spinner] autorelease]; - btnSpinner.width = 30; - - UIBarButtonItem *spacer = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil] autorelease]; - - // ****** Add Toolbar - self.toolbar = [[UIToolbar new] autorelease]; - toolbar.barStyle = UIBarStyleDefault; - - - // ****** Add items to toolbar - NSArray *items = [NSArray arrayWithObjects: btnClose, btnGoBack, btnGoForward, btnReload, btnSpinner, spacer, btnAction, nil]; - [toolbar setItems:items animated:NO]; - - - // ****** Add WebView - self.webView = [[[UIWebView alloc] initWithFrame:CGRectMake(0, 44, 1, 1)] autorelease]; - webView.backgroundColor = [UIColor underPageBackgroundColor]; - webView.contentMode = UIViewContentModeScaleToFill; - webView.scalesPageToFit = YES; - webView.delegate = self; - - - // ****** View - self.view = [[UIView new] autorelease]; - - - // ****** Attach - [self.view addSubview:toolbar]; - [self.view addSubview:webView]; - - - // ****** Set views starting frames according to current interface rotation - [self willRotateToInterfaceOrientation:self.interfaceOrientation duration:0]; -} -- (void)viewDidLoad { - - [super viewDidLoad]; - - self.view.backgroundColor = [UIColor whiteColor]; - [webView loadRequest:[NSURLRequest requestWithURL:myUrl]]; -} -- (void)dealloc { - - [self.webView stopLoading]; - [self.webView removeFromSuperview]; - self.webView.delegate = nil; - - [btnGoBack release]; - [btnGoForward release]; - [btnReload release]; - - [spinner release]; - [toolbar release]; - - [webView release]; - - [super dealloc]; -} -- (BOOL)prefersStatusBarHidden { - return YES; -} - -#pragma mark - WEBVIEW -- (void)webViewDidStartLoad:(UIWebView *)webViewIn { - /**************************************************************************************************** - * Start loading a new page in the UIWebView. - */ - - // NSLog(@"[Modal] Loading '%@'", [webViewIn.request.URL absoluteString]); <-- this isn't returning the URL correctly, check - [spinner startAnimating]; -} -- (void)webViewDidFinishLoad:(UIWebView *)webViewIn { - /**************************************************************************************************** - * Triggered when the WebView finish. - * We reset the button status here. - */ - - //NSLog(@"[Modal] Finish loading."); - [[self delegate] webView:webViewIn setCorrectOrientation:self.interfaceOrientation]; - - // ****** Stop spinner - [spinner stopAnimating]; - - // ****** Update buttons - btnGoBack.enabled = [webViewIn canGoBack]; - btnGoForward.enabled = [webViewIn canGoForward]; - btnReload.enabled = YES; -} -- (void)webView:(UIWebView *)webViewIn didFailLoadWithError:(NSError *)error { - NSLog(@"[Modal] Failed to load '%@', error code %i", [webViewIn.request.URL absoluteString], [error code]); - if ([error code] == -1009) { - UILabel *errorLabel = [[[UILabel alloc] initWithFrame:self.webView.frame] autorelease]; - errorLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - errorLabel.textAlignment = NSTextAlignmentCenter; - errorLabel.textColor = [UIColor grayColor]; - errorLabel.text = NSLocalizedString(@"WEB_MODAL_FAILURE_MESSAGE", nil); - errorLabel.numberOfLines = 1; - - CGRect screenBounds = [[UIScreen mainScreen] bounds]; - if (screenBounds.size.width < 768) { - errorLabel.font = [UIFont fontWithName:@"Helvetica" size:14.0]; - } else { - errorLabel.font = [UIFont fontWithName:@"Helvetica" size:18.0]; - } - - [self.view addSubview:errorLabel]; - } - - // ****** Stop spinner - [spinner stopAnimating]; -} - -#pragma mark - ACTIONS -- (void)dismissAction { - /**************************************************************************************************** - * Close action, it calls the delegate object to unload itself. - */ - - [[self delegate] closeModalWebView]; -} -- (void)goBack { - /**************************************************************************************************** - * WebView back button. - */ - - [webView goBack]; -} -- (void)goForward { - /**************************************************************************************************** - * WebView forward button. - */ - - [webView goForward]; -} -- (void)reloadPage { - /**************************************************************************************************** - * WebView reload button. - */ - - [webView reload]; -} -- (void)openInSafari { - /**************************************************************************************************** - * Open in Safari. - * In the future this will trigger the panel to choose between different actions. - */ - - [[UIApplication sharedApplication] openURL:webView.request.URL]; -} - -#pragma mark - ORIENTATION -- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation { - /**************************************************************************************************** - * We'll use our delegate object to check if we can autorotate or not. - */ - - return [[self delegate] shouldAutorotateToInterfaceOrientation:orientation]; -} -- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration -{ - uint screenWidth = 0; - uint screenHeight = 0; - - if (UIInterfaceOrientationIsPortrait(toInterfaceOrientation)) - { - screenWidth = [[UIScreen mainScreen] bounds].size.width; - screenHeight = [[UIScreen mainScreen] bounds].size.height; - } - else if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) - { - screenWidth = [[UIScreen mainScreen] bounds].size.height; - screenHeight = [[UIScreen mainScreen] bounds].size.width; - } - - self.view.frame = CGRectMake(0, 0, screenWidth, screenHeight); - toolbar.frame = CGRectMake(0, 0, screenWidth, 44); - webView.frame = CGRectMake(0, 44, screenWidth, screenHeight - 44); - - [[self delegate] webView:webView setCorrectOrientation:toInterfaceOrientation]; -} - -@end diff --git a/BakerShelf/lib/NSString+Extensions.h b/BakerView/lib/BKRJSONStatus.h similarity index 87% rename from BakerShelf/lib/NSString+Extensions.h rename to BakerView/lib/BKRJSONStatus.h index 7e7b327b..7115cea5 100644 --- a/BakerShelf/lib/NSString+Extensions.h +++ b/BakerView/lib/BKRJSONStatus.h @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -31,11 +32,12 @@ #import -@interface NSString (Extensions) +@interface BKRJSONStatus : NSObject -#pragma mark - SHA management -- (NSString *)stringSHAEncoded; -+ (NSString *)encodeSHAString:(NSString *)str; -+ (NSString *)stringFromInterfaceOrientation:(UIInterfaceOrientation)orientation; +@property (nonatomic, copy) NSString *path; + +- (id)initWithJSONPath:(NSString*)JSONPath; +- (void)save:(NSDictionary*)status; +- (NSDictionary*)load; @end diff --git a/BakerView/lib/JSONStatus.m b/BakerView/lib/BKRJSONStatus.m similarity index 91% rename from BakerView/lib/JSONStatus.m rename to BakerView/lib/BKRJSONStatus.m index 5f7525b2..85da2a71 100644 --- a/BakerView/lib/JSONStatus.m +++ b/BakerView/lib/BKRJSONStatus.m @@ -1,10 +1,11 @@ // -// JSONStatus.m +// NSString+Extensions.h // Baker // // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -29,19 +30,19 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -#import "JSONStatus.h" -#import "Utils.h" +#import "BKRJSONStatus.h" +#import "BKRUtils.h" -@implementation JSONStatus +@implementation BKRJSONStatus @synthesize path; -- (id)initWithJSONPath:(NSString *)JSONPath +- (id)initWithJSONPath:(NSString*)JSONPath { self = [super init]; if (self) { - path = [JSONPath retain]; + path = JSONPath; [self createFileIfMissing]; [self load]; } @@ -49,7 +50,7 @@ - (id)initWithJSONPath:(NSString *)JSONPath return self; } -- (NSDictionary *)load { +- (NSDictionary*)load { NSError *error = nil; NSData* json = [NSData dataWithContentsOfFile:self.path options:0 error:&error]; if (error) { @@ -62,7 +63,7 @@ - (NSDictionary *)load { return retv; } -- (void)save:(NSDictionary *)status { +- (void)save:(NSDictionary*)status { NSError* error = nil; NSData* json = [NSJSONSerialization dataWithJSONObject:status options:0 @@ -95,10 +96,5 @@ - (void)createFileIfMissing { } } -- (void)dealloc { - [path release]; - - [super dealloc]; -} @end diff --git a/BakerView/lib/Utils.h b/BakerView/lib/BKRUtils.h similarity index 74% rename from BakerView/lib/Utils.h rename to BakerView/lib/BKRUtils.h index a1ec60f7..5dbef73e 100644 --- a/BakerView/lib/Utils.h +++ b/BakerView/lib/BKRUtils.h @@ -1,10 +1,11 @@ // -// Utils.h +// NSString+Extensions.h // Baker // // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -30,7 +31,7 @@ // #import -#import "BakerBook.h" +#import "BKRBook.h" // IOS VERSION COMPARISON MACROS #define SYSTEM_VERSION_EQUAL_TO(version) ([[[UIDevice currentDevice] systemVersion] compare:version options:NSNumericSearch] == NSOrderedSame) @@ -39,18 +40,17 @@ #define SYSTEM_VERSION_LESS_THAN(version) ([[[UIDevice currentDevice] systemVersion] compare:version options:NSNumericSearch] == NSOrderedAscending) #define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(version) ([[[UIDevice currentDevice] systemVersion] compare:version options:NSNumericSearch] != NSOrderedDescending) -@interface Utils : NSObject { +@interface BKRUtils : NSObject -} - -+ (UIColor *)colorWithRGBHex:(UInt32)hex; -+ (UIColor *)colorWithHexString:(NSString *)stringToConvert; -+ (NSString *)stringFromInterfaceOrientation:(UIInterfaceOrientation)orientation; -+ (BOOL)webViewShouldBePaged:(UIWebView*)webView forBook:(BakerBook *)book; -+ (NSString *)appID; -+ (NSDate *)dateWithFormattedString:(NSString *)string; -+ (void)showAlertWithTitle:(NSString *)title message:(NSString *)message buttonTitle:(NSString *)buttonTitle; -+ (void)webView:(UIWebView *)webView dispatchHTMLEvent:(NSString *)event; -+ (void)webView:(UIWebView *)webView dispatchHTMLEvent:(NSString *)event withParams:(NSDictionary *)params; ++ (UIColor*)colorWithRGBHex:(UInt32)hex; ++ (UIColor*)colorWithHexString:(NSString*)stringToConvert; ++ (CAGradientLayer *)gradientLayerFromHexString:(NSString *)startString toHexString:(NSString *)stopString; ++ (NSString*)stringFromInterfaceOrientation:(UIInterfaceOrientation)orientation; ++ (BOOL)webViewShouldBePaged:(UIWebView*)webView forBook:(BKRBook*)book; ++ (NSString*)appID; ++ (NSDate*)dateWithFormattedString:(NSString*)string; ++ (void)showAlertWithTitle:(NSString*)title message:(NSString*)message buttonTitle:(NSString*)buttonTitle; ++ (void)webView:(UIWebView*)webView dispatchHTMLEvent:(NSString*)event; ++ (void)webView:(UIWebView*)webView dispatchHTMLEvent:(NSString*)event withParams:(NSDictionary*)params; @end diff --git a/BakerView/lib/Utils.m b/BakerView/lib/BKRUtils.m similarity index 70% rename from BakerView/lib/Utils.m rename to BakerView/lib/BKRUtils.m index 581eff05..f80556a7 100644 --- a/BakerView/lib/Utils.m +++ b/BakerView/lib/BKRUtils.m @@ -1,10 +1,11 @@ // -// Utils.m +// NSString+Extensions.h // Baker // // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -35,12 +36,12 @@ elem.getAttribute('content');\ }" -#import "Utils.h" +#import "BKRUtils.h" #import -@implementation Utils +@implementation BKRUtils -+ (UIColor *)colorWithRGBHex:(UInt32)hex { ++ (UIColor*)colorWithRGBHex:(UInt32)hex { int r = (hex >> 16) & 0xFF; int g = (hex >> 8) & 0xFF; int b = (hex) & 0xFF; @@ -50,10 +51,15 @@ + (UIColor *)colorWithRGBHex:(UInt32)hex { blue:b / 255.0f alpha:1.0f]; } -+ (UIColor *)colorWithHexString:(NSString *)stringToConvert { - // Returns a UIColor by scanning the string for a hex number and passing that to (UIColor *)colorWithRGBHex:(UInt32)hex + ++ (UIColor*)colorWithHexString:(NSString*)stringToConvert { + // Returns a UIColor by scanning the string for a hex number and passing that to (UIColor*)colorWithRGBHex:(UInt32)hex // Skips any leading whitespace and ignores any trailing characters + if([stringToConvert isEqualToString:@"transparent"]) { + return [UIColor clearColor]; + } + NSString *hexString = [stringToConvert stringByReplacingOccurrencesOfString:@"#" withString:@""]; NSScanner *scanner = [NSScanner scannerWithString:hexString]; @@ -61,18 +67,35 @@ + (UIColor *)colorWithHexString:(NSString *)stringToConvert { if (![scanner scanHexInt:&hexNum]) { return nil; } - return [Utils colorWithRGBHex:hexNum]; + return [BKRUtils colorWithRGBHex:hexNum]; +} + ++ (CAGradientLayer *)gradientLayerFromHexString:(NSString *)startString toHexString:(NSString *)stopString { + UIColor *startColor = [self colorWithHexString:startString]; + UIColor *stopColor = [self colorWithHexString:stopString]; + + NSArray *gradientColors = [NSArray arrayWithObjects:(id)startColor.CGColor, (id)stopColor.CGColor, nil]; + NSArray *gradientLocations = [NSArray arrayWithObjects:[NSNumber numberWithInt:0.0],[NSNumber numberWithInt:1.0], nil]; + + CAGradientLayer *gradientLayer = [CAGradientLayer layer]; + gradientLayer.colors = gradientColors; + gradientLayer.locations = gradientLocations; + + return gradientLayer; } -+ (NSString *)stringFromInterfaceOrientation:(UIInterfaceOrientation)orientation { + ++ (NSString*)stringFromInterfaceOrientation:(UIInterfaceOrientation)orientation { switch (orientation) { case UIInterfaceOrientationPortrait: return @"UIInterfaceOrientationPortrait"; case UIInterfaceOrientationPortraitUpsideDown: return @"UIInterfaceOrientationPortraitUpsideDown"; case UIInterfaceOrientationLandscapeLeft: return @"UIInterfaceOrientationLandscapeLeft"; case UIInterfaceOrientationLandscapeRight: return @"UIInterfaceOrientationLandscapeRight"; + case UIInterfaceOrientationUnknown: return @"UIInterfaceOrientationUnknown"; } return nil; } -+ (BOOL)webViewShouldBePaged:(UIWebView*)webView forBook:(BakerBook *)book { + ++ (BOOL)webViewShouldBePaged:(UIWebView*)webView forBook:(BKRBook*)book { BOOL shouldBePaged = NO; NSString *pagePagination = [webView stringByEvaluatingJavaScriptFromString:ISPAGED_JS_SNIPPET]; @@ -85,15 +108,16 @@ + (BOOL)webViewShouldBePaged:(UIWebView*)webView forBook:(BakerBook *)book { return shouldBePaged; } -+ (NSString *)appID { + ++ (NSString*)appID { return [[NSBundle mainBundle] bundleIdentifier]; } -+ (NSDate *)dateWithFormattedString:(NSString *)string { ++ (NSDate*)dateWithFormattedString:(NSString*)string { static NSDateFormatter *dateFormat = nil; if (dateFormat == nil) { dateFormat = [[NSDateFormatter alloc] init]; - NSLocale *enUSPOSIXLocale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]; + NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; [dateFormat setLocale:enUSPOSIXLocale]; [dateFormat setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; [dateFormat setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; @@ -101,21 +125,21 @@ + (NSDate *)dateWithFormattedString:(NSString *)string { return [dateFormat dateFromString:string]; } -+ (void)showAlertWithTitle:(NSString *)title message:(NSString *)message buttonTitle:(NSString *)buttonTitle { ++ (void)showAlertWithTitle:(NSString*)title message:(NSString*)message buttonTitle:(NSString*)buttonTitle { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:message delegate:nil cancelButtonTitle:buttonTitle otherButtonTitles:nil]; [alert show]; - [alert release]; } -+ (void)webView:(UIWebView *)webView dispatchHTMLEvent:(NSString *)event { - [Utils webView:webView dispatchHTMLEvent:event withParams:[NSDictionary dictionary]]; ++ (void)webView:(UIWebView*)webView dispatchHTMLEvent:(NSString*)event { + [BKRUtils webView:webView dispatchHTMLEvent:event withParams:[NSDictionary dictionary]]; } -+ (void)webView:(UIWebView *)webView dispatchHTMLEvent:(NSString *)event withParams:(NSDictionary *)params { - __block NSMutableString *jsDispatchEvent = [NSMutableString stringWithFormat: + ++ (void)webView:(UIWebView*)webView dispatchHTMLEvent:(NSString*)event withParams:(NSDictionary*)params { + NSMutableString *jsDispatchEvent = [NSMutableString stringWithFormat: @"var bakerDispatchedEvent = document.createEvent('Events');\ bakerDispatchedEvent.initEvent('%@', false, false);", event]; [params enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { diff --git a/BakerView/lib/GTMNSString+HTML.m b/BakerView/lib/GTMNSString+HTML.m index 6853df07..708c88dc 100644 --- a/BakerView/lib/GTMNSString+HTML.m +++ b/BakerView/lib/GTMNSString+HTML.m @@ -21,7 +21,7 @@ #import "GTMNSString+HTML.h" typedef struct { - NSString *escapeSequence; + __unsafe_unretained NSString *escapeSequence; unichar uchar; } HTMLEscapeMap; diff --git a/BakerView/lib/JSONStatus.h b/BakerView/lib/JSONStatus.h deleted file mode 100644 index 82ae1249..00000000 --- a/BakerView/lib/JSONStatus.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// JSONStatus.h -// Baker -// -// ========================================================================================== -// -// Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi -// 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 Baker Framework nor the names of its contributors may be used to -// endorse or promote products derived from this software without specific prior written -// permission. -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT -// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// - -#import - -@interface JSONStatus : NSObject - -@property (copy, nonatomic) NSString *path; - -- (id)initWithJSONPath:(NSString *)JSONPath; -- (void)save:(NSDictionary *)status; -- (NSDictionary *)load; - -@end diff --git a/BakerShelf/lib/NSString+UUID.h b/BakerView/lib/NSObject+BakerExtensions.h similarity index 91% rename from BakerShelf/lib/NSString+UUID.h rename to BakerView/lib/NSObject+BakerExtensions.h index 11771e3e..d879d7b6 100644 --- a/BakerShelf/lib/NSString+UUID.h +++ b/BakerView/lib/NSObject+BakerExtensions.h @@ -1,11 +1,11 @@ // -// NSString+UUID.h +// NSString+Extensions.h // Baker -// See: http://oleb.net/blog/2011/09/how-to-replace-the-udid/ // // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -32,8 +32,8 @@ #import -@interface NSString (UUID) +@interface NSObject (BakerExtensions) -+ (NSString *)uuid; +- (NSString*)bkrCachePath; @end diff --git a/BakerView/lib/NSObject+BakerExtensions.m b/BakerView/lib/NSObject+BakerExtensions.m new file mode 100644 index 00000000..3d1a91f7 --- /dev/null +++ b/BakerView/lib/NSObject+BakerExtensions.m @@ -0,0 +1,17 @@ +// +// NSObject+Extensions.m +// Baker +// +// Created by Pieter Claerhout on 03/11/14. +// +// + +#import "NSObject+BakerExtensions.h" + +@implementation NSObject (BakerExtensions) + +- (NSString*)bkrCachePath { + return NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0]; +} + +@end diff --git a/BakerView/lib/NSString+BakerExtensions.h b/BakerView/lib/NSString+BakerExtensions.h new file mode 100644 index 00000000..b57d623a --- /dev/null +++ b/BakerView/lib/NSString+BakerExtensions.h @@ -0,0 +1,47 @@ +// +// NSString+Extensions.h +// Baker +// +// ========================================================================================== +// +// Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout +// 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 Baker Framework nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written +// permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#import + +@interface NSString (BakerExtensions) + +#pragma mark - SHA management + +- (NSString*)bkrStringSHAEncoded; ++ (NSString*)bkrEncodeSHAString:(NSString*)str; ++ (NSString*)bkrStringFromInterfaceOrientation:(UIInterfaceOrientation)orientation; + +#pragma mark - UUID + ++ (NSString*)bkrUUID; + +@end diff --git a/BakerShelf/lib/NSString+Extensions.m b/BakerView/lib/NSString+BakerExtensions.m similarity index 75% rename from BakerShelf/lib/NSString+Extensions.m rename to BakerView/lib/NSString+BakerExtensions.m index 4f50347e..0ce6a5a8 100644 --- a/BakerShelf/lib/NSString+Extensions.m +++ b/BakerView/lib/NSString+BakerExtensions.m @@ -1,10 +1,11 @@ // -// NSString+Extensions.m +// NSString+Extensions.h // Baker // // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -30,18 +31,17 @@ // #import -#import "NSString+Extensions.h" +#import "NSString+BakerExtensions.h" -@implementation NSString (Extensions) +@implementation NSString (BakerExtensions) #pragma mark - SHA management -- (NSString *)stringSHAEncoded -{ +- (NSString*)bkrStringSHAEncoded { const char *src = [self UTF8String]; unsigned char result[CC_SHA1_DIGEST_LENGTH]; - CC_SHA1(src, strlen(src), result); + CC_SHA1(src, (int)strlen(src), result); NSMutableString *sha = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2]; for (int i = 0; i < 8; i++) { @@ -50,19 +50,32 @@ - (NSString *)stringSHAEncoded return sha; } -+ (NSString *)encodeSHAString:(NSString *)str -{ - return [str stringSHAEncoded]; + ++ (NSString*)bkrEncodeSHAString:(NSString*)str { + return [str bkrStringSHAEncoded]; } -+ (NSString *)stringFromInterfaceOrientation:(UIInterfaceOrientation)orientation -{ + ++ (NSString*)bkrStringFromInterfaceOrientation:(UIInterfaceOrientation)orientation { switch (orientation) { case UIInterfaceOrientationPortrait: return @"UIInterfaceOrientationPortrait"; case UIInterfaceOrientationPortraitUpsideDown: return @"UIInterfaceOrientationPortraitUpsideDown"; case UIInterfaceOrientationLandscapeLeft: return @"UIInterfaceOrientationLandscapeLeft"; case UIInterfaceOrientationLandscapeRight: return @"UIInterfaceOrientationLandscapeRight"; + case UIInterfaceOrientationUnknown: return @"UIInterfaceOrientationUnknown"; } return nil; } +#pragma mark - UUID + ++ (NSString*)bkrUUID { + NSString *uuidString = nil; + CFUUIDRef uuid = CFUUIDCreate(NULL); + if (uuid) { + uuidString = (NSString *)CFBridgingRelease(CFUUIDCreateString(NULL, uuid)); + CFRelease(uuid); + } + return uuidString; +} + @end diff --git a/BakerView/lib/SSZipArchive.h b/BakerView/lib/SSZipArchive.h deleted file mode 100755 index 13a539f4..00000000 --- a/BakerView/lib/SSZipArchive.h +++ /dev/null @@ -1,51 +0,0 @@ -// -// SSZipArchive.h -// SSZipArchive -// -// Created by Sam Soffes on 7/21/10. -// Copyright (c) Sam Soffes 2010-2013. All rights reserved. -// - -#ifndef _SSZIPARCHIVE_H -#define _SSZIPARCHIVE_H - -#import -#include "minizip/unzip.h" - -@protocol SSZipArchiveDelegate; - -@interface SSZipArchive : NSObject - -// Unzip -+ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination; -+ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination overwrite:(BOOL)overwrite password:(NSString *)password error:(NSError **)error; - -+ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination delegate:(id)delegate; -+ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination overwrite:(BOOL)overwrite password:(NSString *)password error:(NSError **)error delegate:(id)delegate; - -// Zip -+ (BOOL)createZipFileAtPath:(NSString *)path withFilesAtPaths:(NSArray *)filenames; -+ (BOOL)createZipFileAtPath:(NSString *)path withContentsOfDirectory:(NSString *)directoryPath; - -- (id)initWithPath:(NSString *)path; -- (BOOL)open; -- (BOOL)writeFile:(NSString *)path; -- (BOOL)writeData:(NSData *)data filename:(NSString *)filename; -- (BOOL)close; - -@end - - -@protocol SSZipArchiveDelegate - -@optional - -- (void)zipArchiveWillUnzipArchiveAtPath:(NSString *)path zipInfo:(unz_global_info)zipInfo; -- (void)zipArchiveDidUnzipArchiveAtPath:(NSString *)path zipInfo:(unz_global_info)zipInfo unzippedPath:(NSString *)unzippedPath; - -- (void)zipArchiveWillUnzipFileAtIndex:(NSInteger)fileIndex totalFiles:(NSInteger)totalFiles archivePath:(NSString *)archivePath fileInfo:(unz_file_info)fileInfo; -- (void)zipArchiveDidUnzipFileAtIndex:(NSInteger)fileIndex totalFiles:(NSInteger)totalFiles archivePath:(NSString *)archivePath fileInfo:(unz_file_info)fileInfo; - -@end - -#endif /* _SSZIPARCHIVE_H */ diff --git a/BakerShelf/lib/UIColor+Extensions.h b/BakerView/lib/UIColor+BakerExtensions.h similarity index 60% rename from BakerShelf/lib/UIColor+Extensions.h rename to BakerView/lib/UIColor+BakerExtensions.h index 540f948f..0ad5e42b 100644 --- a/BakerShelf/lib/UIColor+Extensions.h +++ b/BakerView/lib/UIColor+BakerExtensions.h @@ -8,10 +8,11 @@ #import -@interface UIColor (UIColor_Extensions) +@interface UIColor (BakerExtensions) #pragma mark - Hex color management -+ (UIColor *)colorWithRGBHex:(UInt32)hex; -+ (UIColor *)colorWithHexString:(NSString *)stringToConvert; + ++ (UIColor*)bkrColorWithRGBHex:(UInt32)hex; ++ (UIColor*)bkrColorWithHexString:(NSString*)stringToConvert; @end diff --git a/BakerShelf/lib/UIColor+Extensions.m b/BakerView/lib/UIColor+BakerExtensions.m similarity index 73% rename from BakerShelf/lib/UIColor+Extensions.m rename to BakerView/lib/UIColor+BakerExtensions.m index c7611411..6de56596 100644 --- a/BakerShelf/lib/UIColor+Extensions.m +++ b/BakerView/lib/UIColor+BakerExtensions.m @@ -6,14 +6,13 @@ // Copyright 2011 Marco Natale Colombo. All rights reserved. // -#import "UIColor+Extensions.h" +#import "UIColor+BakerExtensions.h" -@implementation UIColor (UIColor_Extensions) +@implementation UIColor (BakerExtensions) #pragma mark - Hex color management -+ (UIColor *)colorWithRGBHex:(UInt32)hex -{ ++ (UIColor*)bkrColorWithRGBHex:(UInt32)hex { int r = (hex >> 16) & 0xFF; int g = (hex >> 8) & 0xFF; int b = (hex) & 0xFF; @@ -23,9 +22,9 @@ + (UIColor *)colorWithRGBHex:(UInt32)hex blue:b / 255.0f alpha:1.0f]; } -+ (UIColor *)colorWithHexString:(NSString *)stringToConvert -{ - // Returns a UIColor by scanning the string for a hex number and passing that to (UIColor *)colorWithRGBHex:(UInt32)hex + ++ (UIColor*)bkrColorWithHexString:(NSString*)stringToConvert { + // Returns a UIColor by scanning the string for a hex number and passing that to (UIColor*)colorWithRGBHex:(UInt32)hex // Skips any leading whitespace and ignores any trailing characters NSString *hexString = [stringToConvert stringByReplacingOccurrencesOfString:@"#" withString:@""]; @@ -35,7 +34,7 @@ + (UIColor *)colorWithHexString:(NSString *)stringToConvert if (![scanner scanHexInt:&hexNum]) { return nil; } - return [UIColor colorWithRGBHex:hexNum]; + return [UIColor bkrColorWithRGBHex:hexNum]; } @end diff --git a/BakerView/lib/UIScreen+BakerExtensions.h b/BakerView/lib/UIScreen+BakerExtensions.h new file mode 100644 index 00000000..d307e04b --- /dev/null +++ b/BakerView/lib/UIScreen+BakerExtensions.h @@ -0,0 +1,50 @@ +// +// NSString+Extensions.h +// Baker +// +// ========================================================================================== +// +// Copyright (c) 2014, Pieter Claerhout +// 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 Baker Framework nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written +// permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#import + +@interface UIScreen (BakerExtensions) + +- (CGFloat)bkrScreenWidthPortrait; +- (CGFloat)bkrScreenHeightPortrait; + +- (CGFloat)bkrScreenWidth; +- (CGFloat)bkrScreenHeight; + +- (NSString*)bkrLayoutName; + +- (CGFloat)bkrWidthForOrientationName:(NSString*)orientationName; +- (CGFloat)bkrHeightForOrientationName:(NSString*)orientationName; + +- (CGFloat)bkrWidthForOrientation:(UIInterfaceOrientation)orientation; +- (CGFloat)bkrHeightForOrientation:(UIInterfaceOrientation)orientation; + +@end diff --git a/BakerView/lib/UIScreen+BakerExtensions.m b/BakerView/lib/UIScreen+BakerExtensions.m new file mode 100644 index 00000000..670f7ea1 --- /dev/null +++ b/BakerView/lib/UIScreen+BakerExtensions.m @@ -0,0 +1,91 @@ +// +// NSString+Extensions.h +// Baker +// +// ========================================================================================== +// +// Copyright (c) 2014, Pieter Claerhout +// 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 Baker Framework nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written +// permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#import "UIScreen+BakerExtensions.h" + +@implementation UIScreen (BakerExtensions) + +- (CGFloat)bkrScreenWidthPortrait { + CGFloat screenSize1 = self.bounds.size.width; + CGFloat screenSize2 = self.bounds.size.height; + return MIN(screenSize1, screenSize2); +} + +- (CGFloat)bkrScreenHeightPortrait { + CGFloat screenSize1 = self.bounds.size.width; + CGFloat screenSize2 = self.bounds.size.height; + return MAX(screenSize1, screenSize2); +} + +- (CGFloat)bkrScreenWidth { + return self.bounds.size.width; +} + +- (CGFloat)bkrScreenHeight { + return self.bounds.size.height; +} + +- (NSString*)bkrLayoutName { + CGFloat screenWidth = [self bkrScreenWidthPortrait]; + CGFloat screenHeight = [self bkrScreenHeightPortrait]; + if (screenWidth == 320 && screenHeight == 480) { + screenHeight = 568; // Special case for iPhone 4s + } + return [NSString stringWithFormat:@"%.0fx%.0f", screenHeight, screenWidth]; +} + +- (CGFloat)bkrWidthForOrientationName:(NSString*)orientationName { + if ([orientationName isEqualToString:@"portrait"]) { + return [self bkrScreenWidthPortrait]; + } else { + return [self bkrScreenHeightPortrait]; + } +} + +- (CGFloat)bkrHeightForOrientationName:(NSString*)orientationName { + if ([orientationName isEqualToString:@"portrait"]) { + return [self bkrScreenHeightPortrait]; + } else { + return [self bkrScreenWidthPortrait]; + } +} + +- (CGFloat)bkrWidthForOrientation:(UIInterfaceOrientation)orientation { + NSString *orientationName = UIInterfaceOrientationIsPortrait(orientation) ? @"portrait" : @"landscape"; + return [self bkrWidthForOrientationName:orientationName]; +} + +- (CGFloat)bkrHeightForOrientation:(UIInterfaceOrientation)orientation { + NSString *orientationName = UIInterfaceOrientationIsPortrait(orientation) ? @"portrait" : @"landscape"; + return [self bkrHeightForOrientationName:orientationName]; +} + +@end diff --git a/BakerView/ui/PageTitleLabel.h b/BakerView/ui/BKRPageTitleLabel.h similarity index 84% rename from BakerView/ui/PageTitleLabel.h rename to BakerView/ui/BKRPageTitleLabel.h index 6a7da428..29416f31 100644 --- a/BakerView/ui/PageTitleLabel.h +++ b/BakerView/ui/BKRPageTitleLabel.h @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -31,10 +32,10 @@ #import -@interface PageTitleLabel : UILabel +@interface BKRPageTitleLabel : UILabel -- (id)initWithFile:(NSString *)path color:(UIColor *)color alpha:(float)alpha; -- (id)initWithFileContent:(NSString *)fileContent color:(UIColor *)color alpha:(float)alpha; -- (void)setX:(CGFloat) x Y:(CGFloat) y; +- (id)initWithFile:(NSString*)path color:(UIColor*)color alpha:(float)alpha; +- (id)initWithFileContent:(NSString*)fileContent color:(UIColor*)color alpha:(float)alpha; +- (void)setX:(CGFloat)x Y:(CGFloat)y; @end diff --git a/BakerView/ui/PageTitleLabel.m b/BakerView/ui/BKRPageTitleLabel.m similarity index 71% rename from BakerView/ui/PageTitleLabel.m rename to BakerView/ui/BKRPageTitleLabel.m index 1ac7b6ec..64b6cddd 100644 --- a/BakerView/ui/PageTitleLabel.m +++ b/BakerView/ui/BKRPageTitleLabel.m @@ -5,6 +5,7 @@ // ========================================================================================== // // Copyright (c) 2010-2013, Davide Casali, Marco Colombo, Alessandro Morandi +// Copyright (c) 2014, Andrew Krowczyk, Cédric Mériau, Pieter Claerhout // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -29,24 +30,26 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -#import "PageTitleLabel.h" -#import "Utils.h" +#import "BKRPageTitleLabel.h" +#import "BKRUtils.h" #import "GTMNSString+HTML.h" -@implementation PageTitleLabel +@implementation BKRPageTitleLabel -- (id)initWithFile:(NSString *)path color:(UIColor *)color alpha:(float)alpha { +#pragma mark - Initialization + +- (id)initWithFile:(NSString*)path color:(UIColor*)color alpha:(float)alpha { NSError *error = nil; NSString *fileContent = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error]; if (error == nil) { - return [self initWithFileContent:fileContent color:(UIColor *)color alpha:(float)alpha]; + return [self initWithFileContent:fileContent color:(UIColor*)color alpha:(float)alpha]; } else { NSLog(@"Error while loading %@ : %@ : Check that encoding is UTF8 for the file.", path, [error localizedDescription]); return [super init]; } } -- (id)initWithFileContent:(NSString *)fileContent color:(UIColor *)color alpha:(float)alpha { +- (id)initWithFileContent:(NSString*)fileContent color:(UIColor*)color alpha:(float)alpha { self = [super init]; if (self) { @@ -59,26 +62,30 @@ - (id)initWithFileContent:(NSString *)fileContent color:(UIColor *)color alpha:( UIFont *titleFont = [UIFont fontWithName:@"Helvetica" size:24.0]; CGRect screenBounds = [[UIScreen mainScreen] bounds]; - if (screenBounds.size.width < 768) { + if (MIN(screenBounds.size.width, screenBounds.size.height) < 768) { titleDimension = CGSizeMake(280, 134); titleFont = [UIFont fontWithName:@"Helvetica" size:15.0]; } - CGSize titleTextSize = [titleText sizeWithFont:titleFont constrainedToSize:titleDimension lineBreakMode:NSLineBreakByTruncatingTail]; + CGSize titleTextSize = [titleText boundingRectWithSize:titleDimension + options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine + attributes:@{NSFontAttributeName: titleFont} + context:nil].size; - self.frame = CGRectMake(0, 0, titleTextSize.width, titleTextSize.height); + self.frame = CGRectMake(0, 0, titleTextSize.width, titleTextSize.height); self.backgroundColor = [UIColor clearColor]; - self.textAlignment = NSTextAlignmentCenter; - self.lineBreakMode = NSLineBreakByTruncatingTail; - self.numberOfLines = 0; - self.textColor = color; - self.alpha = alpha; - self.font = titleFont; - self.text = titleText; + self.textAlignment = NSTextAlignmentCenter; + self.lineBreakMode = NSLineBreakByTruncatingTail; + self.numberOfLines = 0; + self.textColor = color; + self.alpha = alpha; + self.font = titleFont; + self.text = titleText; } } return self; } + - (void)setX:(CGFloat)x Y:(CGFloat)y { CGRect titleFrame = self.frame; titleFrame.origin.x = x; diff --git a/README.md b/README.md index 0f10aaa6..8037ab6a 100644 --- a/README.md +++ b/README.md @@ -35,17 +35,17 @@ It's easier done than said! ;) * Create a Hpub book.json file: title, author, pages, etc. (see below) * All the assets must be contained within the publication folder or its subfolders * Check the example book from the website for an hands-on example - * See either [Standalone tutorial](https://github.com/Simbul/baker/wiki/Tutorial-for-Standalone-App) or [Newsstand tutorial](https://github.com/Simbul/baker/wiki/Tutorial-for-Newsstand-with-Free-issues) for more info + * See either [Standalone tutorial](https://github.com/bakerframework/baker/wiki/Tutorial-for-Standalone-App) or [Newsstand tutorial for free subscriptions](https://github.com/bakerframework/baker/wiki/Tutorial-for-Newsstand-with-Free-issues) or [Newsstand tutorial for paid subscriptions](https://github.com/bakerframework/baker/wiki/Tutorial-for-Newsstand-with-In-App-Purchase) for more information 3. PUBLISH * Download the Baker Framework Xcode project from http://bakerframework.com (or GitHub). * Download Xcode from the Mac App Store or from the Apple Developer website. - * Decide if you want to release using the [Standalone tutorial](https://github.com/Simbul/baker/wiki/Tutorial-for-Standalone-App) or [Newsstand tutorial](https://github.com/Simbul/baker/wiki/Tutorial-for-Newsstand-with-Free-issues) mode and follow the tutorial accordingly. + * Decide if you want to release using the [Standalone tutorial](https://github.com/bakerframework/baker/wiki/Tutorial-for-Standalone-App) or [Newsstand tutorial](https://github.com/bakerframework/baker/wiki/Tutorial-for-Newsstand-with-Free-issues) mode and follow the tutorial accordingly. * Select the Baker Scheme (Simulator or Device) from the toolbar dropdown. * Run and check that everything works correctly _both_ on simulator and device. - * Check [this page](https://github.com/Simbul/baker/wiki/Problems-and-Debugging) if you encounter any problem. + * Check [this page](https://github.com/bakerframework/baker/wiki/Problems-and-Debugging) if you encounter any problem. * Create an Apple iPhone Developer account to publish on the App Store. - * If you are using Newsstand, follow the instruction on the Apple iPhone Developer website to create a new free subscription. + * If you are using Newsstand, follow the instructions on the Apple iPhone Developer website to create either your free subscription or paid subscription / issue In App Purchases * Follow the instructions on the Apple iPhone Developer website to submit your book to the app store. @@ -70,7 +70,7 @@ This is an example of a minimal book.json file: } ``` -For all the details and the advanced options, check the [Hpub specification on the wiki](https://github.com/Simbul/baker/wiki/hpub-specification). +For all the details and the advanced options, check the [Hpub specification on the wiki](https://github.com/bakerframework/baker/wiki/hpub-specification). SHELF.JSON @@ -92,7 +92,7 @@ This is an example of the shelf.json file that is downloaded by Baker in Newssta ] ``` -For all the details on how to create and use it, check the [Newsstand publications](https://github.com/Simbul/baker/wiki/4.0-tutorial-for-Newsstand). +For all the details on how to create and use it, check the [Newsstand publications](https://github.com/bakerframework/baker/wiki/4.0-tutorial-for-Newsstand). CREATE A BOOK FOR BOTH IPAD AND IPHONE @@ -115,7 +115,7 @@ To compile your application for iPhone follow these steps: BUGS AND FEEDBACK ----------------- -* Submit your bugs here: +* Submit your bugs here: * Give us your feedback at: * Follow us on Twitter: @@ -124,6 +124,25 @@ BUGS AND FEEDBACK CHANGELOG --------- +* **4.3** (11/12/2014) + * Added support for iOS 8, iOS 8.1, iPhone 6 and iPhone 6 Plus + * The Baker app is now a universal app + * Support for iOS 6 is dropped, Baker now requires iOS 7 or newer + * The shelf header is now a separate view that can be customized + * The embedded book "A Study Of Scarlet" is now responsive + * The shelf view now supports categories + * Newsstand icon is now updated dynamically + * Autoplay on UIWebView is now allowed + * Settings are now defined in the settings.plist file instead of code + * Many new config settings to customize the shelf + * Full cleanup of the code, move to ARC and move to modern Objective-C syntax + * Minor bug fixes and other resolved GitHub reported issues + +* **4.2.1** (26/01/2014) + * Branding changes. New Baker Framework logo assets + * Open local files in modal view. Action button not shown. Useful if you have local documents (html, pdf, etc...) related to the content of your Baker HPub + * Bugfixes related to iOS 7 app validation (Asset Catalog Errors) + * **4.2** (28/10/2013) * iOS 7 support * Text Kit support diff --git a/books/a-study-in-scarlet/Book Cover.html b/books/a-study-in-scarlet/Book Cover.html index 43411c19..b82a38f4 100644 --- a/books/a-study-in-scarlet/Book Cover.html +++ b/books/a-study-in-scarlet/Book Cover.html @@ -2,7 +2,7 @@ A Study In Scarlet - +