├── .gitattributes ├── hackfoldr-iOSTests ├── en.lproj │ └── InfoPlist.strings ├── hackfoldr-iOSTests-Info.plist ├── TestData │ └── sample.csv.json └── hackfoldr_iOSTests.m ├── hackfoldr-iOS ├── Images.xcassets │ ├── Contents.json │ ├── AppIcon.appiconset │ │ ├── Icon-76.png │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── Icon-76@2x.png │ │ ├── Icon-Small.png │ │ ├── Icon-Small-40.png │ │ ├── Icon-Small@2x.png │ │ ├── Icon-Small@3x.png │ │ ├── icon-83.5@2x.png │ │ ├── Icon-Small-40@2x.png │ │ ├── Icon-Small-40@3x.png │ │ ├── Icon-Small@2x-1.png │ │ ├── iTunesArtwork@2x.png │ │ ├── Icon-Small-40@2x-1.png │ │ └── Contents.json │ ├── CenterIcon.imageset │ │ ├── CenterIcon.pdf │ │ └── Contents.json │ ├── hackfoldr-icon.imageset │ │ ├── hackfoldr-icon.pdf │ │ └── Contents.json │ └── hackfoldr-icon-small.imageset │ │ ├── hackfoldr-icon-small.pdf │ │ └── Contents.json ├── en.lproj │ ├── Hackfoldr.strings │ └── InfoPlist.strings ├── zh-Hant.lproj │ ├── InfoPlist.strings │ └── Hackfoldr.strings ├── UIColor+Hackfoldr.h ├── MainViewController.h ├── hackfoldr-iOS.entitlements ├── HackfoldrClient+Store.h ├── HackfoldrHistory.m ├── main.m ├── TOWebViewController+HackfoldrField.h ├── QRCodeViewController.h ├── hackfoldr-iOS-Prefix.pch ├── NSURL+Hackfoldr.h ├── HackfoldrPage+CSSearchableItem.h ├── SettingViewController.h ├── UIColor+Hackfoldr.m ├── TOWebViewController+HackfoldrField.m ├── HackfoldrHistory.h ├── NSUserDefaults+DefaultHackfoldrPage.h ├── HackfoldrField.h ├── HackfoldrPage.h ├── ListFieldViewController.h ├── HackfoldrClient.h ├── HackoldrModel.xcdatamodeld │ └── HackoldrModel.xcdatamodel │ │ └── contents ├── HackfoldrPage+CSSearchableItem.m ├── AppDelegate.h ├── g0v.svg ├── NSUserDefaults+DefaultHackfoldrPage.m ├── NSURL+Hackfoldr.m ├── HackfoldrClient+Store.m ├── hackfoldr-iOS-Info.plist ├── LaunchScreen.storyboard ├── HackfoldrPage.m ├── HackfoldrClient.m ├── AppDelegate.m ├── QRCodeViewController.m ├── QRCode.storyboard ├── MainViewController.m ├── HackfoldrField.m ├── SettingViewController.m └── ListFieldViewController.m ├── hackfoldr-iOS.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata ├── xcshareddata │ └── xcschemes │ │ └── hackfoldr-iOS.xcscheme └── project.pbxproj ├── hackfoldr-iOS.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── WorkspaceSettings.xcsettings ├── .gitignore ├── Podfile ├── Readme.md ├── .travis.yml ├── LICENSE ├── script └── xcode-git-version.sh ├── Changelog.md └── Podfile.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj merge=mergepbx -------------------------------------------------------------------------------- /hackfoldr-iOSTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /hackfoldr-iOS/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /hackfoldr-iOS/en.lproj/Hackfoldr.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackfoldr/hackfoldr-iOS/HEAD/hackfoldr-iOS/en.lproj/Hackfoldr.strings -------------------------------------------------------------------------------- /hackfoldr-iOS/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | "NSCameraUsageDescription" = "Scan QR code"; 4 | -------------------------------------------------------------------------------- /hackfoldr-iOS/zh-Hant.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | "NSCameraUsageDescription" = "掃描二維條碼"; 4 | -------------------------------------------------------------------------------- /hackfoldr-iOS/zh-Hant.lproj/Hackfoldr.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackfoldr/hackfoldr-iOS/HEAD/hackfoldr-iOS/zh-Hant.lproj/Hackfoldr.strings -------------------------------------------------------------------------------- /hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackfoldr/hackfoldr-iOS/HEAD/hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackfoldr/hackfoldr-iOS/HEAD/hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackfoldr/hackfoldr-iOS/HEAD/hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackfoldr/hackfoldr-iOS/HEAD/hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackfoldr/hackfoldr-iOS/HEAD/hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /hackfoldr-iOS/Images.xcassets/CenterIcon.imageset/CenterIcon.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackfoldr/hackfoldr-iOS/HEAD/hackfoldr-iOS/Images.xcassets/CenterIcon.imageset/CenterIcon.pdf -------------------------------------------------------------------------------- /hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-Small-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackfoldr/hackfoldr-iOS/HEAD/hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-Small-40.png -------------------------------------------------------------------------------- /hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackfoldr/hackfoldr-iOS/HEAD/hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackfoldr/hackfoldr-iOS/HEAD/hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackfoldr/hackfoldr-iOS/HEAD/hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/icon-83.5@2x.png -------------------------------------------------------------------------------- /hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackfoldr/hackfoldr-iOS/HEAD/hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png -------------------------------------------------------------------------------- /hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackfoldr/hackfoldr-iOS/HEAD/hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png -------------------------------------------------------------------------------- /hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackfoldr/hackfoldr-iOS/HEAD/hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png -------------------------------------------------------------------------------- /hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/iTunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackfoldr/hackfoldr-iOS/HEAD/hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/iTunesArtwork@2x.png -------------------------------------------------------------------------------- /hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-Small-40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackfoldr/hackfoldr-iOS/HEAD/hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Icon-Small-40@2x-1.png -------------------------------------------------------------------------------- /hackfoldr-iOS/Images.xcassets/hackfoldr-icon.imageset/hackfoldr-icon.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackfoldr/hackfoldr-iOS/HEAD/hackfoldr-iOS/Images.xcassets/hackfoldr-icon.imageset/hackfoldr-icon.pdf -------------------------------------------------------------------------------- /hackfoldr-iOS/Images.xcassets/hackfoldr-icon-small.imageset/hackfoldr-icon-small.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackfoldr/hackfoldr-iOS/HEAD/hackfoldr-iOS/Images.xcassets/hackfoldr-icon-small.imageset/hackfoldr-icon-small.pdf -------------------------------------------------------------------------------- /hackfoldr-iOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /hackfoldr-iOS/Images.xcassets/hackfoldr-icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "hackfoldr-icon.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /hackfoldr-iOS/Images.xcassets/hackfoldr-icon-small.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "hackfoldr-icon-small.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /hackfoldr-iOS.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /hackfoldr-iOS.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /hackfoldr-iOS/UIColor+Hackfoldr.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Hackfoldr.h 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2017/3/16. 6 | // Copyright © 2017年 org.g0v. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIColor (Hackfoldr) 12 | 13 | + (UIColor *)hackfoldrGreenColor; 14 | + (UIColor *)hackfoldrIconWhite; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /hackfoldr-iOS/MainViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.h 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2014/6/21. 6 | // Copyright (c) 2014年 org.superbil. All rights reserved. 7 | // 8 | 9 | @class HackfoldrField; 10 | 11 | @interface MainViewController : UIViewController 12 | 13 | - (void)updateHackfoldrPageWithKey:(NSString *)hackfoldrKey; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /hackfoldr-iOS/hackfoldr-iOS.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.associated-domains 6 | 7 | applinks:hackfoldr.org 8 | applinks:*.hackfoldr.org 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /hackfoldr-iOS/HackfoldrClient+Store.h: -------------------------------------------------------------------------------- 1 | // 2 | // HackfoldrClient+Store.h 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2017/3/17. 6 | // Copyright © 2017年 org.g0v. All rights reserved. 7 | // 8 | 9 | #import "HackfoldrClient.h" 10 | 11 | @interface HackfoldrClient (Store) 12 | 13 | - (HackfoldrTaskCompletionSource *)hackfoldrPageTaskWithKey:(NSString *)hackfoldrKey rediredKey:(NSString *)rediredKey; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /hackfoldr-iOS/HackfoldrHistory.m: -------------------------------------------------------------------------------- 1 | // 2 | // HackfoldrHistory.m 3 | // hackfoldr-iOS 4 | // 5 | // Created by 舒特比 on 2015/4/3. 6 | // Copyright (c) 2015年 org.superbil. All rights reserved. 7 | // 8 | 9 | #import "HackfoldrHistory.h" 10 | 11 | 12 | @implementation HackfoldrHistory 13 | 14 | @dynamic createDate; 15 | @dynamic hackfoldrKey; 16 | @dynamic rediredKey; 17 | @dynamic refreshDate; 18 | @dynamic title; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /hackfoldr-iOS/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2014/6/21. 6 | // Copyright (c) 2014年 org.superbil. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /hackfoldr-iOS/TOWebViewController+HackfoldrField.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOWebViewController+HackfoldrField.h 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2014/6/25. 6 | // Copyright (c) 2014年 org.superbil. All rights reserved. 7 | // 8 | 9 | #import "TOWebViewController.h" 10 | 11 | @class HackfoldrField; 12 | 13 | @interface TOWebViewController (HackfoldrField) 14 | 15 | - (void)loadWithField:(HackfoldrField *)oneField; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /hackfoldr-iOS/Images.xcassets/CenterIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "CenterIcon.pdf", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /hackfoldr-iOS/QRCodeViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // QRCodeViewController.h 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2017/11/22. 6 | // Copyright © 2017年 org.g0v. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface QRCodeViewController : UIViewController 12 | 13 | + (instancetype)viewController; 14 | 15 | @property (copy) NSString *qrCodeString; 16 | 17 | @property (copy) void (^foundedResult)(NSString *result); 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /hackfoldr-iOS/hackfoldr-iOS-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | 18 | #ifndef DEBUG 19 | #define NSLog(...) {} 20 | #endif -------------------------------------------------------------------------------- /hackfoldr-iOS/NSURL+Hackfoldr.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSURL+Hackfoldr.h 3 | // hackfoldr-iOS 4 | // 5 | // Created by 舒特比 on 2016/12/17. 6 | // Copyright © 2016年 org.g0v. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSURL (Hackfoldr) 12 | 13 | + (BOOL)canHandleHackfoldrURL:(nonnull NSURL *)url; 14 | 15 | + (nullable NSString *)realKeyOfHackfoldrWithURL:(nonnull NSURL *)url; 16 | 17 | + (nonnull NSString *)validatorHackfoldrKey:(nonnull NSString *)newHackfoldrKey; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /hackfoldr-iOS/HackfoldrPage+CSSearchableItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // HackfoldrPage+CSSearchableItem.h 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2017/11/2. 6 | // Copyright © 2017年 org.g0v. All rights reserved. 7 | // 8 | 9 | #import "HackfoldrPage.h" 10 | #import 11 | 12 | @interface HackfoldrPage (CSSearchableItem) 13 | 14 | @property (nonatomic, readonly) CSSearchableItemAttributeSet *searchableAttributeSet; 15 | 16 | - (CSSearchableItem *)searchableItemWithDomain:(NSString *)domain; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /hackfoldr-iOS/SettingViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SettingViewController.h 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2017/11/2. 6 | // Copyright © 2017年 org.g0v. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface SettingViewController : XLFormViewController 16 | 17 | @property (nonatomic, copy, nullable) void (^updateHackfoldrPage)(NSString *pageKey, NSError * _Nullable error); 18 | 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /hackfoldr-iOS/UIColor+Hackfoldr.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Hackfoldr.m 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2017/3/16. 6 | // Copyright © 2017年 org.g0v. All rights reserved. 7 | // 8 | 9 | #import "UIColor+Hackfoldr.h" 10 | 11 | @implementation UIColor (Hackfoldr) 12 | 13 | + (UIColor *)hackfoldrGreenColor 14 | { 15 | return [UIColor colorWithRed:0.490 green:0.781 blue:0.225 alpha:1.000]; 16 | } 17 | 18 | + (UIColor *)hackfoldrIconWhite { 19 | return [UIColor colorWithRed:0.888 green:0.953 blue:0.826 alpha:1.000]; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /hackfoldr-iOS/TOWebViewController+HackfoldrField.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOWebViewController+HackfoldrField.m 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2014/6/25. 6 | // Copyright (c) 2014年 org.superbil. All rights reserved. 7 | // 8 | 9 | #import "TOWebViewController+HackfoldrField.h" 10 | 11 | #import "HackfoldrField.h" 12 | 13 | @implementation TOWebViewController (HackfoldrField) 14 | 15 | - (void)loadWithField:(HackfoldrField *)oneField 16 | { 17 | self.title = oneField.name; 18 | self.url = [NSURL URLWithString:oneField.urlString]; 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /hackfoldr-iOS/HackfoldrHistory.h: -------------------------------------------------------------------------------- 1 | // 2 | // HackfoldrHistory.h 3 | // hackfoldr-iOS 4 | // 5 | // Created by 舒特比 on 2015/4/3. 6 | // Copyright (c) 2015年 org.superbil. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | 13 | @interface HackfoldrHistory : NSManagedObject 14 | 15 | @property (nonatomic, strong) NSDate * createDate; 16 | @property (nonatomic, strong) NSDate * refreshDate; 17 | @property (nonatomic, strong) NSString * hackfoldrKey; 18 | @property (nonatomic, strong) NSString * rediredKey; 19 | @property (nonatomic, strong) NSString * title; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # MacOS 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | 24 | # CocoaPods 25 | # 26 | # We recommend against adding the Pods directory to your .gitignore. However 27 | # you should judge for yourself, the pros and cons are mentioned at: 28 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 29 | # 30 | Pods/ 31 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | 3 | platform :ios, "9.0" 4 | 5 | target "hackfoldr-iOS" do 6 | pod 'AFNetworking', '~> 3.1' 7 | pod 'AFNetworking/UIKit' 8 | pod 'Bolts', '~> 1.0' 9 | pod 'AFCSVParserResponseSerializer' 10 | pod 'MagicalRecord', '~> 2.2' 11 | 12 | pod 'XLForm', '~> 4.0.0' 13 | pod 'TOWebViewController' 14 | pod 'RATreeView' 15 | 16 | pod 'FontAwesomeKit', :git => 'https://github.com/PrideChung/FontAwesomeKit.git' 17 | pod 'FontAwesomeKit/FontAwesome', :git => 'https://github.com/PrideChung/FontAwesomeKit.git' 18 | pod 'PocketSVG' 19 | end 20 | 21 | target "hackfoldr-iOSTests" do 22 | pod 'OHHTTPStubs' 23 | end 24 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/hackfoldr/hackfoldr-iOS.svg?branch=master)](https://travis-ci.org/hackfoldr/hackfoldr-iOS) 2 | 3 | # hackfoldr-iOS # 4 | 5 | This is a simple iOS client for [hackfoldr](https://github.com/hackfoldr/hackfoldr) 6 | 7 | ## Why? ## 8 | 9 | Hackfoldr on mobile is simply not good enough on iOS. 10 | 11 | ## Use CocoaPods developer ## 12 | 13 | [CocoaPods](http://cocoapods.org/) is the easy way to install third-party iOS components. 14 | 15 | ```` 16 | pod install 17 | ```` 18 | 19 | ## Use Git-flow for teamwork ## 20 | 21 | [Git-Flow](http://nvie.com/posts/a-successful-git-branching-model/) 22 | 23 | ## License ## 24 | 25 | And of course: 26 | 27 | MIT: http://superbil.mit-license.org/ 28 | -------------------------------------------------------------------------------- /hackfoldr-iOS/NSUserDefaults+DefaultHackfoldrPage.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSUserDefaults+DefaultHackfoldrPage.h 3 | // hackfoldr-iOS 4 | // 5 | // Created by 舒特比 on 2015/1/28. 6 | // Copyright (c) 2015年 superbil.org All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSUserDefaults (DefaultHackfoldrPage) 12 | 13 | - (NSString *)stringOfDefaultHackfoldrPage; 14 | 15 | - (void)setDefaultHackfoldrPage:(NSString *)aString; 16 | 17 | - (void)removeDefaultHackfolderPage; 18 | 19 | @end 20 | 21 | @interface NSUserDefaults (CurrentHackfoldrPage) 22 | 23 | - (NSString *)stringOfCurrentHackfoldrPage; 24 | 25 | - (void)setCurrentHackfoldrPage:(NSString *)anString; 26 | 27 | - (void)removeCurrentHackfoldrPage; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /hackfoldr-iOSTests/hackfoldr-iOSTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode9.1 3 | 4 | branches: 5 | only: 6 | - master 7 | - develop 8 | 9 | env: 10 | global: 11 | - COCOAPODS_DISABLE_DETERMINISTIC_UUIDS=1 12 | 13 | before_install: 14 | - gem install xcpretty -N --no-ri --no-rdoc 15 | - SIMULATOR_ID=$(xcrun instruments -s devices | grep -io "$SIMULATOR \[.*\]" | grep -o "\[.*\]" | sed "s/^\[\(.*\)\]$/\1/") 16 | - pod repo update 17 | 18 | script: 19 | - set -o pipefail 20 | - open -b com.apple.iphonesimulator --args -CurrentDeviceUDID $SIMULATOR_ID 21 | - xcodebuild clean build -workspace hackfoldr-iOS.xcworkspace -scheme hackfoldr-iOS -sdk iphonesimulator GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES GCC_GENERATE_TEST_COVERAGE_FILES=YES | xcpretty 22 | 23 | after_success: 24 | - bash <(curl -s https://codecov.io/bash) 25 | -------------------------------------------------------------------------------- /hackfoldr-iOS/HackfoldrField.h: -------------------------------------------------------------------------------- 1 | // 2 | // HackfoldrField.h 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2014/6/22. 6 | // Copyright (c) 2014年 org.superbil. All rights reserved. 7 | // 8 | 9 | @interface HackfoldrField : NSObject 10 | 11 | - (instancetype)initWithFieldArray:(NSArray *)fields; 12 | 13 | @property (nonatomic, assign) NSUInteger index; 14 | @property (nonatomic, copy) NSString *urlString; 15 | @property (nonatomic, copy) NSString *name; 16 | @property (nonatomic, copy) NSString *actions; 17 | 18 | @property (nonatomic, copy) NSString *labelString; 19 | @property (nonatomic, strong) UIColor *labelColor; 20 | 21 | @property (nonatomic, assign) BOOL isSubItem; 22 | @property (nonatomic, assign) BOOL isCommentLine; 23 | 24 | @property (nonatomic, strong) NSMutableArray *subFields; 25 | 26 | - (BOOL)isEmpty; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /hackfoldr-iOS/HackfoldrPage.h: -------------------------------------------------------------------------------- 1 | // 2 | // HackfoldrPage.h 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2014/6/22. 6 | // Copyright (c) 2014年 org.superbil. All rights reserved. 7 | // 8 | 9 | #import "HackfoldrField.h" 10 | 11 | @interface HackfoldrPage : NSObject 12 | 13 | - (instancetype)initWithKey:(NSString *)hackfoldrKey fieldArray:(NSArray *)fieldArray; 14 | 15 | - (instancetype)initWithFieldArray:(NSArray *)fieldArray; 16 | 17 | @property (nonatomic, strong, readonly) NSString *key; 18 | 19 | @property (nonatomic, strong) NSString *pageTitle; 20 | 21 | /// Objcect in NSArray is |HackfoldrField| 22 | @property (nonatomic, strong, readonly) NSArray *cells; 23 | 24 | /** 25 | * rediredKey is redired key from A1 26 | * This is Hackfoldr 2.0 rule 27 | */ 28 | @property (nonatomic, strong, readonly) NSString *rediredKey; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /hackfoldr-iOS/ListFieldViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ListFieldViewController.h 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2014/8/31. 6 | // Copyright (c) 2014年 org.superbil. All rights reserved. 7 | // 8 | 9 | @class BFTask; 10 | @class RATreeView; 11 | @class HackfoldrPage; 12 | @class HackfoldrTaskCompletionSource; 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @interface ListFieldViewController : UIViewController 17 | 18 | + (instancetype)viewController; 19 | 20 | @property (nonatomic, strong) RATreeView *treeView; 21 | 22 | @property (nonatomic, strong) IBOutlet UIButton *settingButton; 23 | 24 | @property (nonatomic, assign) BOOL hideBackButton; 25 | 26 | @property (nonatomic, strong) HackfoldrPage *page; 27 | 28 | - (void)reloadPage; 29 | 30 | - (void)updateHackfoldrPageWithKey:(NSString *)hackfoldrKey; 31 | 32 | - (void)showSettingViewController; 33 | 34 | @end 35 | 36 | NS_ASSUME_NONNULL_END 37 | -------------------------------------------------------------------------------- /hackfoldr-iOS/HackfoldrClient.h: -------------------------------------------------------------------------------- 1 | // 2 | // HackfoldrClient.h 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2014/6/22. 6 | // Copyright (c) 2014年 org.superbil. All rights reserved. 7 | // 8 | 9 | #import "AFHTTPSessionManager.h" 10 | #import "Bolts.h" 11 | 12 | @class HackfoldrPage; 13 | 14 | @interface HackfoldrTaskCompletionSource : BFTaskCompletionSource 15 | 16 | + (HackfoldrTaskCompletionSource *)taskCompletionSource; 17 | @property (strong, nonatomic) NSURLSessionTask *connectionTask; 18 | 19 | @end 20 | 21 | 22 | @interface HackfoldrClient : AFHTTPSessionManager 23 | 24 | + (instancetype)sharedClient; 25 | 26 | - (HackfoldrTaskCompletionSource *)taskCompletionFromEthercalcWithKey:(NSString *)key; 27 | 28 | - (HackfoldrTaskCompletionSource *)taskCompletionFromGoogleSheetWithSheetKey:(NSString *)keyID; 29 | 30 | - (HackfoldrTaskCompletionSource *)taskCompletionWithKey:(NSString *)hackfoldrKey; 31 | 32 | @property (nonatomic, strong, readonly) HackfoldrPage *lastPage; 33 | 34 | @end -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright © 2016 Kai-Yuan Cheng 3 | 4 | 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: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | 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. -------------------------------------------------------------------------------- /hackfoldr-iOS/HackoldrModel.xcdatamodeld/HackoldrModel.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /hackfoldr-iOS/HackfoldrPage+CSSearchableItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // HackfoldrPage+CSSearchableItem.m 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2017/11/2. 6 | //Copyright © 2017年 org.g0v. All rights reserved. 7 | // 8 | 9 | #import "HackfoldrPage+CSSearchableItem.h" 10 | 11 | #import 12 | 13 | @implementation HackfoldrPage (CSSearchableItemAttributeSet) 14 | 15 | - (CSSearchableItemAttributeSet *)searchableAttributeSet { 16 | CSSearchableItemAttributeSet *attributeSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(__bridge NSString *)kUTTypeData]; 17 | attributeSet.title = self.pageTitle; 18 | attributeSet.contentDescription = self.key; 19 | attributeSet.keywords = @[@"Hackfoldr", self.key]; 20 | return attributeSet; 21 | } 22 | 23 | - (CSSearchableItem *)searchableItemWithDomain:(NSString *)domain { 24 | CSSearchableItem *item = [[CSSearchableItem alloc] initWithUniqueIdentifier:self.key 25 | domainIdentifier:domain 26 | attributeSet:self.searchableAttributeSet]; 27 | return item; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /hackfoldr-iOS/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // _oo0oo_ 3 | // o8888888o 4 | // 88" . "88 5 | // (| -_- |) 6 | // 0\ = /0 7 | // ___/`---'\___ 8 | // .' \\| |// '. 9 | // / \\||| : |||// \ 10 | // / _||||| -:- |||||- \ 11 | // | | \\\ - /// | | 12 | // | \_| ''\---/'' |_/ | 13 | // \ .-\__ '-' ___/-. / 14 | // ___'. .' /--.--\ `. .'___ 15 | // ."" '< `.___\_<|>_/___.' >' "". 16 | // | | : `- \`.;`\ _ /`;.`/ - ` : | | 17 | // \ \ `_. \_ __\ /__ _/ .-` / / 18 | // =====`-.____`.___ \_____/___.-`___.-'===== 19 | // `=---=' 20 | // 21 | // 22 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 23 | // 24 | // 佛祖保佑 永無BUG 25 | // 26 | // 27 | // 28 | // 29 | // AppDelegate.h 30 | // hackfoldr-iOS 31 | // 32 | // Created by Superbil on 2014/6/21. 33 | // Copyright (c) 2014年 org.superbil. All rights reserved. 34 | // 35 | 36 | #import 37 | 38 | @interface AppDelegate : UIResponder 39 | 40 | @property (strong, nonatomic) UIWindow *window; 41 | @property (strong, nonatomic) UIViewController *viewController; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /script/xcode-git-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script automatically sets the version and short version string of 4 | # an Xcode project from the Git repository containing the project. 5 | # 6 | # To use this script in Xcode 4, add the contents to a "Run Script" build 7 | # phase for your application target. 8 | 9 | set -o errexit 10 | set -o nounset 11 | 12 | INFO_PLIST="${BUILT_PRODUCTS_DIR}/${WRAPPER_NAME}/Info" 13 | 14 | # Use the latest version tag for CFBundleShortVersionString. I tag releases 15 | # in Git using the format v0.0.0; this assumes you're doing the same. 16 | # SHORT_VERSION=$(git --git-dir="${PROJECT_DIR}/.git" --work-tree="${PROJECT_DIR}" describe --dirty | sed -e 's/^v//' -e 's/g//') 17 | 18 | # I'd like to use the Git commit hash for CFBundleVersion. 19 | # VERSION=$(git --git-dir="${PROJECT_DIR}/.git" --work-tree="${PROJECT_DIR}" rev-parse --short HEAD) 20 | 21 | # But Apple wants this value to be a monotonically increasing integer, so 22 | # instead use the number of commits on the master branch. If you like to 23 | # play fast and loose with your Git history, this may cause you problems. 24 | # Thanks to @amrox for pointing out the issue and fix. 25 | VERSION=$(git --git-dir="${PROJECT_DIR}/.git" --work-tree="${PROJECT_DIR}" rev-list master | wc -l) 26 | 27 | # defaults write $INFO_PLIST CFBundleShortVersionString $SHORT_VERSION 28 | defaults write $INFO_PLIST CFBundleVersion $VERSION 29 | -------------------------------------------------------------------------------- /hackfoldr-iOS/g0v.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hackfoldr-iOS/NSUserDefaults+DefaultHackfoldrPage.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSUserDefaults+DefaultHackfoldrPage.m 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2015/1/28. 6 | // Copyright (c) 2015年 superbil.org All rights reserved. 7 | // 8 | 9 | #import "NSUserDefaults+DefaultHackfoldrPage.h" 10 | 11 | static NSString *kDefaultHackfoldrPage = @"Default Hackfoldr Page"; 12 | static NSString *kCurrentHackfoldrPage = @"Current Hackfoldr Page"; 13 | 14 | @implementation NSUserDefaults (DefaultHackfoldrPage) 15 | 16 | - (NSString *)stringOfDefaultHackfoldrPage 17 | { 18 | return [self objectForKey:kDefaultHackfoldrPage]; 19 | } 20 | 21 | - (void)setDefaultHackfoldrPage:(NSString *)aString 22 | { 23 | [self setObject:aString forKey:kDefaultHackfoldrPage]; 24 | } 25 | 26 | - (void)removeDefaultHackfolderPage 27 | { 28 | [self removeObjectForKey:kDefaultHackfoldrPage]; 29 | } 30 | 31 | @end 32 | 33 | @implementation NSUserDefaults (CurrentHackfoldrPage) 34 | 35 | - (NSString *)stringOfCurrentHackfoldrPage 36 | { 37 | NSString *current = [self objectForKey:kCurrentHackfoldrPage]; 38 | if (current && current.length > 0) { 39 | return current; 40 | } 41 | // When |currentHackfoldrPage| can't find, use defualt 42 | return [self stringOfDefaultHackfoldrPage]; 43 | } 44 | 45 | - (void)setCurrentHackfoldrPage:(NSString *)anString 46 | { 47 | [self setObject:anString forKey:kCurrentHackfoldrPage]; 48 | } 49 | 50 | - (void)removeCurrentHackfoldrPage 51 | { 52 | [self removeObjectForKey:kCurrentHackfoldrPage]; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ### 1.5 4 | 5 | - Fix warning, change to use new API 6 | - Update pods 7 | 8 | ### 1.4 9 | 10 | - Fix iPhone X layout problem 11 | - Fix table view will show two times 12 | - Add share QR code and scan QR code 13 | - Change to use XLForm 14 | - Update pods 15 | 16 | ### 1.3 17 | 18 | - Fix problem about display on iPhone Plus 19 | 20 | ### 1.2 21 | 22 | - Show list like trees [#20](https://github.com/hackfoldr/hackfoldr-iOS/issues/20) 23 | - Update icons, using [Font Awesome](http://fontawesome.io) 24 | - Support customized links, now one can use `hackfoldr://` to open hackfoldr [#18](https://github.com/hackfoldr/hackfoldr-iOS/issues/18) 25 | - Fix some labels with incorrect content [#22](https://github.com/hackfoldr/hackfoldr-iOS/issues/22) 26 | - Support subField with custom icons (Font Awesome) [#27](https://github.com/hackfoldr/hackfoldr-iOS/issues/27) 27 | - Show page details when link is linking to another hackfoldr page 28 | - Update pods 29 | 30 | ### 1.1 31 | 32 | - Support Safari 33 | - Update pods 34 | 35 | ### 1.0 36 | 37 | - Everything is fine, release app 38 | 39 | ## 1.0 beta-4 40 | 41 | - 修正網址的檢查方式 42 | - 讓 status bar 會顯示 43 | - 更新 QuickDialog 44 | 45 | ## 1.0 beta-3 46 | 47 | - 修正使用 redired A1 一定會 crash 的問題 48 | - 新增中文語系介面 49 | 50 | ## 1.0 beta-2 51 | 52 | - 修正 push list view 和 setting view 造成 crash 的問題 53 | 54 | ## 1.0 beta-1 55 | 56 | - 支援 Google sheets (key 超過40) 57 | - 支援 hackfoldr A1 重導向頁面功能 58 | 可使用 hackfoldr-iOS-testA1 測試 59 | - 重新調整使用流程 60 | - 支援 Travis CI 61 | - 支援使用 hackfoldr 1.0 and 2.0 的欄位定義 62 | - 支援使用 http://hackfoldr.org/ 的方式來設定新的頁面 63 | - 支援 page 歷史記錄 64 | -------------------------------------------------------------------------------- /hackfoldr-iOS/NSURL+Hackfoldr.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSURL+Hackfoldr.m 3 | // hackfoldr-iOS 4 | // 5 | // Created by 舒特比 on 2016/12/17. 6 | // Copyright © 2016年 org.g0v. All rights reserved. 7 | // 8 | 9 | #import "NSURL+Hackfoldr.h" 10 | 11 | @implementation NSURL (Hackfoldr) 12 | 13 | + (BOOL)canHandleHackfoldrURL:(nonnull NSURL *)url 14 | { 15 | if ([url.scheme isEqualToString:@"hackfoldr"]) { 16 | return YES; 17 | } else if ([url.host isEqualToString:@"hackfoldr.org"]) { 18 | if ([url.path isEqualToString:@"/about"]) { 19 | return NO; 20 | } 21 | return YES; 22 | } else if ([url.host isEqualToString:@"beta.hackfoldr.org"]) { 23 | return YES; 24 | } 25 | return NO; 26 | } 27 | 28 | + (nullable NSString *)realKeyOfHackfoldrWithURL:(nonnull NSURL *)url 29 | { 30 | if ([url.scheme isEqualToString:@"hackfoldr"]) { 31 | return url.host; 32 | } else if ([url.host isEqualToString:@"hackfoldr.org"] || [url.host isEqualToString:@"beta.hackfoldr.org"]) { 33 | return [url.path stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"/"]]; 34 | } 35 | return nil; 36 | } 37 | 38 | + (NSString *)validatorHackfoldrKey:(NSString *)hackfoldrKey 39 | { 40 | NSString *newHackfoldrKey = [hackfoldrKey copy]; 41 | // Find hackfoldr page key 42 | if ([newHackfoldrKey rangeOfString:@"://"].location != NSNotFound) { 43 | NSURL *hackfoldrURL = [NSURL URLWithString:newHackfoldrKey]; 44 | if ([NSURL canHandleHackfoldrURL:hackfoldrURL]) { 45 | newHackfoldrKey = [NSURL realKeyOfHackfoldrWithURL:hackfoldrURL]; 46 | } 47 | } 48 | 49 | // Remove white space and new line 50 | newHackfoldrKey = [newHackfoldrKey stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 51 | // Use escapes to encoding |newHackfoldrPage| 52 | newHackfoldrKey = [newHackfoldrKey stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]; 53 | return newHackfoldrKey; 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /hackfoldr-iOS/HackfoldrClient+Store.m: -------------------------------------------------------------------------------- 1 | // 2 | // HackfoldrClient+Store.m 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2017/3/17. 6 | // Copyright © 2017年 org.g0v. All rights reserved. 7 | // 8 | 9 | #import "HackfoldrClient+Store.h" 10 | 11 | #import 12 | 13 | #import "HackfoldrHistory.h" 14 | #import "HackfoldrPage.h" 15 | 16 | @implementation HackfoldrClient (Store) 17 | 18 | - (HackfoldrTaskCompletionSource *)hackfoldrPageTaskWithKey:(NSString *)hackfoldrKey rediredKey:(NSString *)rediredKey 19 | { 20 | NSString *key = hackfoldrKey; 21 | if (rediredKey) { 22 | key = rediredKey; 23 | } 24 | 25 | HackfoldrTaskCompletionSource *s = [[HackfoldrClient sharedClient] taskCompletionWithKey:key]; 26 | [[s.task continueWithSuccessBlock:^id _Nullable(BFTask * _Nonnull t) { 27 | HackfoldrPage *page = t.result; 28 | 29 | if (page.rediredKey) { 30 | NSLog(@"redired to:%@", page.rediredKey); 31 | return [self hackfoldrPageTaskWithKey:page.key rediredKey:page.rediredKey].task; 32 | } 33 | 34 | NSLog(@"page: %@", page); 35 | return t; 36 | }] continueWithSuccessBlock:^id _Nullable(BFTask * _Nonnull t) { 37 | HackfoldrPage *page = t.result; 38 | 39 | // Save |history| to core data 40 | HackfoldrHistory *history = [HackfoldrHistory MR_findFirstByAttribute:@"hackfoldrKey" withValue:page.key]; 41 | if (!history) { 42 | history = [HackfoldrHistory MR_createEntity]; 43 | history.createDate = [NSDate date]; 44 | history.refreshDate = [NSDate date]; 45 | history.hackfoldrKey = page.key; 46 | history.title = page.pageTitle; 47 | if (page.rediredKey) { 48 | history.rediredKey = page.rediredKey; 49 | } 50 | } else { 51 | history.refreshDate = [NSDate date]; 52 | history.title = page.pageTitle; 53 | if (page.rediredKey) { 54 | history.rediredKey = page.rediredKey; 55 | } 56 | } 57 | 58 | [[NSManagedObjectContext MR_defaultContext] MR_saveOnlySelfWithCompletion:nil]; 59 | return t; 60 | }]; 61 | return s; 62 | } 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /hackfoldr-iOS/hackfoldr-iOS-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | hackfoldr 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | hackfoldr 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.5 21 | CFBundleSignature 22 | ???? 23 | CFBundleURLTypes 24 | 25 | 26 | CFBundleTypeRole 27 | Editor 28 | CFBundleURLName 29 | org.g0v.hackfoldr 30 | CFBundleURLSchemes 31 | 32 | hackfoldr 33 | 34 | 35 | 36 | CFBundleVersion 37 | 1 38 | Fabric 39 | 40 | APIKey 41 | aa4bea7059a82c8870fd136c48413ae4cd41082c 42 | Kits 43 | 44 | 45 | KitInfo 46 | 47 | KitName 48 | Crashlytics 49 | 50 | 51 | 52 | ITSAppUsesNonExemptEncryption 53 | 54 | LSRequiresIPhoneOS 55 | 56 | NSCameraUsageDescription 57 | Scan QR code 58 | NSPhotoLibraryUsageDescription 59 | Access photo library 60 | UILaunchStoryboardName 61 | LaunchScreen 62 | UIRequiredDeviceCapabilities 63 | 64 | armv7 65 | 66 | UIStatusBarHidden 67 | 68 | UISupportedInterfaceOrientations 69 | 70 | UIInterfaceOrientationPortrait 71 | UIInterfaceOrientationLandscapeLeft 72 | UIInterfaceOrientationLandscapeRight 73 | UIInterfaceOrientationPortraitUpsideDown 74 | 75 | UISupportedInterfaceOrientations~ipad 76 | 77 | UIInterfaceOrientationPortrait 78 | UIInterfaceOrientationPortraitUpsideDown 79 | UIInterfaceOrientationLandscapeLeft 80 | UIInterfaceOrientationLandscapeRight 81 | 82 | UIViewControllerBasedStatusBarAppearance 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /hackfoldr-iOS/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /hackfoldr-iOS.xcodeproj/xcshareddata/xcschemes/hackfoldr-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - AFCSVParserResponseSerializer (1.0.0): 3 | - AFNetworking (~> 3.0) 4 | - CHCSVParser (~> 2.0) 5 | - AFNetworking (3.2.1): 6 | - AFNetworking/NSURLSession (= 3.2.1) 7 | - AFNetworking/Reachability (= 3.2.1) 8 | - AFNetworking/Security (= 3.2.1) 9 | - AFNetworking/Serialization (= 3.2.1) 10 | - AFNetworking/UIKit (= 3.2.1) 11 | - AFNetworking/NSURLSession (3.2.1): 12 | - AFNetworking/Reachability 13 | - AFNetworking/Security 14 | - AFNetworking/Serialization 15 | - AFNetworking/Reachability (3.2.1) 16 | - AFNetworking/Security (3.2.1) 17 | - AFNetworking/Serialization (3.2.1) 18 | - AFNetworking/UIKit (3.2.1): 19 | - AFNetworking/NSURLSession 20 | - Bolts (1.9.0): 21 | - Bolts/AppLinks (= 1.9.0) 22 | - Bolts/Tasks (= 1.9.0) 23 | - Bolts/AppLinks (1.9.0): 24 | - Bolts/Tasks 25 | - Bolts/Tasks (1.9.0) 26 | - CHCSVParser (2.1.0) 27 | - FontAwesomeKit (2.2.1): 28 | - FontAwesomeKit/Core (= 2.2.1) 29 | - FontAwesomeKit/FontAwesome (= 2.2.1) 30 | - FontAwesomeKit/FoundationIcons (= 2.2.1) 31 | - FontAwesomeKit/IonIcons (= 2.2.1) 32 | - FontAwesomeKit/Material (= 2.2.1) 33 | - FontAwesomeKit/Octicons (= 2.2.1) 34 | - FontAwesomeKit/Zocial (= 2.2.1) 35 | - FontAwesomeKit/Core (2.2.1) 36 | - FontAwesomeKit/FontAwesome (2.2.1): 37 | - FontAwesomeKit/Core 38 | - FontAwesomeKit/FoundationIcons (2.2.1): 39 | - FontAwesomeKit/Core 40 | - FontAwesomeKit/IonIcons (2.2.1): 41 | - FontAwesomeKit/Core 42 | - FontAwesomeKit/Material (2.2.1): 43 | - FontAwesomeKit/Core 44 | - FontAwesomeKit/Octicons (2.2.1): 45 | - FontAwesomeKit/Core 46 | - FontAwesomeKit/Zocial (2.2.1): 47 | - FontAwesomeKit/Core 48 | - MagicalRecord (2.3.2): 49 | - MagicalRecord/Core (= 2.3.2) 50 | - MagicalRecord/Core (2.3.2) 51 | - OHHTTPStubs (8.0.0): 52 | - OHHTTPStubs/Default (= 8.0.0) 53 | - OHHTTPStubs/Core (8.0.0) 54 | - OHHTTPStubs/Default (8.0.0): 55 | - OHHTTPStubs/Core 56 | - OHHTTPStubs/JSON 57 | - OHHTTPStubs/NSURLSession 58 | - OHHTTPStubs/OHPathHelpers 59 | - OHHTTPStubs/JSON (8.0.0): 60 | - OHHTTPStubs/Core 61 | - OHHTTPStubs/NSURLSession (8.0.0): 62 | - OHHTTPStubs/Core 63 | - OHHTTPStubs/OHPathHelpers (8.0.0) 64 | - PocketSVG (2.4.2) 65 | - RATreeView (2.1.2) 66 | - TOWebViewController (2.2.8): 67 | - TOWebViewController/Core (= 2.2.8) 68 | - TOWebViewController/Core (2.2.8) 69 | - XLForm (4.0.1) 70 | 71 | DEPENDENCIES: 72 | - AFCSVParserResponseSerializer 73 | - AFNetworking (~> 3.1) 74 | - AFNetworking/UIKit 75 | - Bolts (~> 1.0) 76 | - FontAwesomeKit (from `https://github.com/PrideChung/FontAwesomeKit.git`) 77 | - FontAwesomeKit/FontAwesome (from `https://github.com/PrideChung/FontAwesomeKit.git`) 78 | - MagicalRecord (~> 2.2) 79 | - OHHTTPStubs 80 | - PocketSVG 81 | - RATreeView 82 | - TOWebViewController 83 | - XLForm (~> 4.0.0) 84 | 85 | SPEC REPOS: 86 | https://github.com/cocoapods/specs.git: 87 | - AFCSVParserResponseSerializer 88 | - AFNetworking 89 | - Bolts 90 | - CHCSVParser 91 | - MagicalRecord 92 | - OHHTTPStubs 93 | - PocketSVG 94 | - RATreeView 95 | - TOWebViewController 96 | - XLForm 97 | 98 | EXTERNAL SOURCES: 99 | FontAwesomeKit: 100 | :git: https://github.com/PrideChung/FontAwesomeKit.git 101 | 102 | CHECKOUT OPTIONS: 103 | FontAwesomeKit: 104 | :commit: ed5eb02f4bcf76e218bb1c966c551a7b5240b6b4 105 | :git: https://github.com/PrideChung/FontAwesomeKit.git 106 | 107 | SPEC CHECKSUMS: 108 | AFCSVParserResponseSerializer: ff1f90868576cbfbedda7f0c2f31361af232b144 109 | AFNetworking: b6f891fdfaed196b46c7a83cf209e09697b94057 110 | Bolts: ac6567323eac61e203f6a9763667d0f711be34c8 111 | CHCSVParser: 3bcc7bdb72111a31222e95d57eacceefe4e537e8 112 | FontAwesomeKit: 3614979b3dbb0ec3f7fac1bd760b91f66c31db67 113 | MagicalRecord: 53bed74b4323b930992a725be713e53b37d19755 114 | OHHTTPStubs: 9cbce6364bec557cc3439aa6bb7514670d780881 115 | PocketSVG: c30139829206a43df2ec203880e3970531facba7 116 | RATreeView: 380cde0090cfde21dc6ac29137f49d3b768fdd28 117 | TOWebViewController: 953a24619d32f7aa1d9d4098a74885a9236f7348 118 | XLForm: b8d47a9a00fb6166981cb40de7169d70d611e9be 119 | 120 | PODFILE CHECKSUM: 1b60b2d866db7e542bbedcac696f4826fffe25a7 121 | 122 | COCOAPODS: 1.5.3 123 | -------------------------------------------------------------------------------- /hackfoldr-iOS/HackfoldrPage.m: -------------------------------------------------------------------------------- 1 | // 2 | // HackfoldrPage.m 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2014/6/22. 6 | // Copyright (c) 2014年 org.superbil. All rights reserved. 7 | // 8 | 9 | #import "HackfoldrPage.h" 10 | 11 | #import "HackfoldrField.h" 12 | 13 | @interface HackfoldrPage () 14 | @property (nonatomic, strong, readwrite) NSString *key; 15 | @property (nonatomic, strong) NSArray *fields; 16 | @property (nonatomic, strong, readwrite) NSString *pagetitle; 17 | @property (nonatomic, strong, readwrite) NSString *rediredKey; 18 | @end 19 | 20 | @implementation HackfoldrPage 21 | 22 | - (instancetype)initWithFieldArray:(NSArray *)fieldArray 23 | { 24 | return [self initWithKey:nil fieldArray:fieldArray]; 25 | } 26 | 27 | - (instancetype)initWithKey:(NSString *)hackfoldrKey fieldArray:(NSArray *)fieldArray 28 | { 29 | self = [super init]; 30 | if (!self) { 31 | return nil; 32 | } 33 | 34 | self.key = hackfoldrKey; 35 | 36 | [self updateWithArray:fieldArray]; 37 | 38 | return self; 39 | } 40 | 41 | - (instancetype)copyWithZone:(NSZone *)zone 42 | { 43 | HackfoldrPage *copy = [[HackfoldrPage allocWithZone:zone] init]; 44 | copy.key = [self.key copy]; 45 | copy.fields = [self.fields copy]; 46 | copy.pageTitle = [self.pageTitle copy]; 47 | copy.rediredKey = [self.rediredKey copy]; 48 | return copy; 49 | } 50 | 51 | - (NSArray *)cells 52 | { 53 | return self.fields; 54 | } 55 | 56 | - (void)updateWithArray:(NSArray *)fieldArray 57 | { 58 | if (!fieldArray || fieldArray.count == 0) { 59 | return; 60 | } 61 | 62 | // Check this page is redired page 63 | NSString *a1String = ((NSArray *)fieldArray.firstObject).firstObject; 64 | a1String = [a1String stringByReplacingOccurrencesOfString:@"\"" withString:@""]; 65 | // Check A1 is not comment field and length >= 40 66 | if ([a1String hasPrefix:@"#"] == NO && a1String.length >= 40) { 67 | self.rediredKey = a1String; 68 | // because this is redired page, ignore other things 69 | return; 70 | } 71 | 72 | NSMutableArray *sectionFields = [NSMutableArray array]; 73 | __block HackfoldrField *sectionField = nil; 74 | __block BOOL isFindTitle = NO; 75 | [fieldArray enumerateObjectsUsingBlock:^(NSArray *fields, NSUInteger idx, BOOL *stop) { 76 | HackfoldrField *field = [[HackfoldrField alloc] initWithFieldArray:fields]; 77 | 78 | if (field.isEmpty || field.isCommentLine) { 79 | return; 80 | } 81 | field.index = idx; 82 | 83 | // find first row isn't comment line and not empty 84 | if (!isFindTitle) { 85 | self.pageTitle = field.name; 86 | isFindTitle = YES; 87 | return; 88 | } 89 | 90 | // other row 91 | if (field.isSubItem == NO) { 92 | // add last |sectionField| 93 | if (sectionField) { 94 | [sectionFields addObject:sectionField]; 95 | } 96 | 97 | // Create new section field 98 | // When field have |urlString|, just put into a new section 99 | if (field.urlString.length == 0) { 100 | sectionField = field; 101 | } else { 102 | sectionField = [[HackfoldrField alloc] init]; 103 | [sectionField.subFields addObject:field]; 104 | } 105 | } else { 106 | // section could be nil 107 | if (!sectionField) { 108 | sectionField = [[HackfoldrField alloc] init]; 109 | } 110 | // add |field| to subFields 111 | [sectionField.subFields addObject:field]; 112 | } 113 | }]; 114 | // Check every section is been add or not 115 | if (sectionField) { 116 | [sectionFields addObject:sectionField]; 117 | } 118 | 119 | self.fields = sectionFields; 120 | } 121 | 122 | - (NSString *)description 123 | { 124 | NSMutableString *description = [NSMutableString string]; 125 | [description appendFormat:@"key: %@\n", self.key]; 126 | [description appendFormat:@"pageTitle: %@\n", self.pageTitle]; 127 | if (self.rediredKey) { 128 | [description appendFormat:@"rediredKey: %@", self.rediredKey]; 129 | } 130 | // [description appendFormat:@"cells: %@", self.fields]; 131 | return description; 132 | } 133 | 134 | @end 135 | -------------------------------------------------------------------------------- /hackfoldr-iOS/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "size" : "29x29", 15 | "idiom" : "iphone", 16 | "filename" : "Icon-Small@2x-1.png", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "Icon-Small@3x.png", 23 | "scale" : "3x" 24 | }, 25 | { 26 | "size" : "40x40", 27 | "idiom" : "iphone", 28 | "filename" : "Icon-Small-40@2x.png", 29 | "scale" : "2x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "Icon-Small-40@3x.png", 35 | "scale" : "3x" 36 | }, 37 | { 38 | "size" : "60x60", 39 | "idiom" : "iphone", 40 | "filename" : "Icon-60@2x.png", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "size" : "60x60", 45 | "idiom" : "iphone", 46 | "filename" : "Icon-60@3x.png", 47 | "scale" : "3x" 48 | }, 49 | { 50 | "idiom" : "ipad", 51 | "size" : "20x20", 52 | "scale" : "1x" 53 | }, 54 | { 55 | "idiom" : "ipad", 56 | "size" : "20x20", 57 | "scale" : "2x" 58 | }, 59 | { 60 | "size" : "29x29", 61 | "idiom" : "ipad", 62 | "filename" : "Icon-Small.png", 63 | "scale" : "1x" 64 | }, 65 | { 66 | "size" : "29x29", 67 | "idiom" : "ipad", 68 | "filename" : "Icon-Small@2x.png", 69 | "scale" : "2x" 70 | }, 71 | { 72 | "size" : "40x40", 73 | "idiom" : "ipad", 74 | "filename" : "Icon-Small-40.png", 75 | "scale" : "1x" 76 | }, 77 | { 78 | "size" : "40x40", 79 | "idiom" : "ipad", 80 | "filename" : "Icon-Small-40@2x-1.png", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "size" : "76x76", 85 | "idiom" : "ipad", 86 | "filename" : "Icon-76.png", 87 | "scale" : "1x" 88 | }, 89 | { 90 | "size" : "76x76", 91 | "idiom" : "ipad", 92 | "filename" : "Icon-76@2x.png", 93 | "scale" : "2x" 94 | }, 95 | { 96 | "size" : "83.5x83.5", 97 | "idiom" : "ipad", 98 | "filename" : "icon-83.5@2x.png", 99 | "scale" : "2x" 100 | }, 101 | { 102 | "size" : "1024x1024", 103 | "idiom" : "ios-marketing", 104 | "filename" : "iTunesArtwork@2x.png", 105 | "scale" : "1x" 106 | }, 107 | { 108 | "size" : "24x24", 109 | "idiom" : "watch", 110 | "scale" : "2x", 111 | "role" : "notificationCenter", 112 | "subtype" : "38mm" 113 | }, 114 | { 115 | "size" : "27.5x27.5", 116 | "idiom" : "watch", 117 | "scale" : "2x", 118 | "role" : "notificationCenter", 119 | "subtype" : "42mm" 120 | }, 121 | { 122 | "size" : "29x29", 123 | "idiom" : "watch", 124 | "role" : "companionSettings", 125 | "scale" : "2x" 126 | }, 127 | { 128 | "size" : "29x29", 129 | "idiom" : "watch", 130 | "role" : "companionSettings", 131 | "scale" : "3x" 132 | }, 133 | { 134 | "size" : "40x40", 135 | "idiom" : "watch", 136 | "scale" : "2x", 137 | "role" : "appLauncher", 138 | "subtype" : "38mm" 139 | }, 140 | { 141 | "size" : "44x44", 142 | "idiom" : "watch", 143 | "scale" : "2x", 144 | "role" : "appLauncher", 145 | "subtype" : "40mm" 146 | }, 147 | { 148 | "size" : "50x50", 149 | "idiom" : "watch", 150 | "scale" : "2x", 151 | "role" : "appLauncher", 152 | "subtype" : "44mm" 153 | }, 154 | { 155 | "size" : "86x86", 156 | "idiom" : "watch", 157 | "scale" : "2x", 158 | "role" : "quickLook", 159 | "subtype" : "38mm" 160 | }, 161 | { 162 | "size" : "98x98", 163 | "idiom" : "watch", 164 | "scale" : "2x", 165 | "role" : "quickLook", 166 | "subtype" : "42mm" 167 | }, 168 | { 169 | "size" : "108x108", 170 | "idiom" : "watch", 171 | "scale" : "2x", 172 | "role" : "quickLook", 173 | "subtype" : "44mm" 174 | }, 175 | { 176 | "idiom" : "watch-marketing", 177 | "size" : "1024x1024", 178 | "scale" : "1x" 179 | } 180 | ], 181 | "info" : { 182 | "version" : 1, 183 | "author" : "xcode" 184 | } 185 | } -------------------------------------------------------------------------------- /hackfoldr-iOS/HackfoldrClient.m: -------------------------------------------------------------------------------- 1 | // 2 | // HackfoldrClient.m 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2014/6/22. 6 | // Copyright (c) 2014年 org.superbil. All rights reserved. 7 | // 8 | 9 | #import "HackfoldrClient.h" 10 | 11 | #import "AFCSVParserResponseSerializer.h" 12 | #import "HackfoldrPage.h" 13 | 14 | @implementation HackfoldrTaskCompletionSource 15 | 16 | + (HackfoldrTaskCompletionSource *)taskCompletionSource 17 | { 18 | return [[HackfoldrTaskCompletionSource alloc] init]; 19 | } 20 | 21 | - (void)dealloc 22 | { 23 | [self.connectionTask cancel]; 24 | self.connectionTask = nil; 25 | } 26 | 27 | - (void)cancel 28 | { 29 | [self.connectionTask cancel]; 30 | [super cancel]; 31 | } 32 | 33 | @end 34 | 35 | #pragma mark - 36 | 37 | @interface HackfoldrClient () 38 | @property (nonatomic, strong, readwrite) HackfoldrPage *lastPage; 39 | @end 40 | 41 | @implementation HackfoldrClient 42 | 43 | + (instancetype)sharedClient 44 | { 45 | static dispatch_once_t onceToken; 46 | static HackfoldrClient *shareClient; 47 | dispatch_once(&onceToken, ^{ 48 | shareClient = [[HackfoldrClient alloc] initWithBaseURL:[NSURL URLWithString:@"https://ethercalc.org/"]]; 49 | }); 50 | return shareClient; 51 | } 52 | 53 | + (AFCSVParserResponseSerializer *)CSVSerializer 54 | { 55 | AFCSVParserResponseSerializer *serializer = [AFCSVParserResponseSerializer serializer]; 56 | serializer.usedEncoding = NSUTF8StringEncoding; 57 | return serializer; 58 | } 59 | 60 | - (instancetype)initWithBaseURL:(NSURL *)url 61 | { 62 | self = [super initWithBaseURL:url]; 63 | if (self) { 64 | self.requestSerializer = [AFHTTPRequestSerializer serializer]; 65 | self.responseSerializer = [AFJSONResponseSerializer serializer]; 66 | } 67 | return self; 68 | } 69 | 70 | - (HackfoldrTaskCompletionSource *)_taskCompletionWithPath:(NSString *)inPath 71 | { 72 | HackfoldrTaskCompletionSource *source = [HackfoldrTaskCompletionSource taskCompletionSource]; 73 | NSString *requestPath = [NSString stringWithFormat:@"%@.csv.json", inPath]; 74 | source.connectionTask = [self GET:requestPath parameters:nil progress:nil success:^(NSURLSessionDataTask *task, id fieldArray) { 75 | HackfoldrPage *page = [[HackfoldrPage alloc] initWithKey:inPath fieldArray:fieldArray]; 76 | self->_lastPage = [page copy]; 77 | [source setResult:self->_lastPage]; 78 | } failure:^(NSURLSessionDataTask *task, NSError *error) { 79 | [source setError:error]; 80 | }]; 81 | return source; 82 | } 83 | 84 | - (HackfoldrTaskCompletionSource *)taskCompletionFromEthercalcWithKey:(NSString *)key 85 | { 86 | return [self _taskCompletionWithPath:key]; 87 | } 88 | 89 | - (HackfoldrTaskCompletionSource *)taskCompletionFromGoogleSheetWithSheetKey:(NSString *)keyID 90 | { 91 | // example at https://docs.google.com/spreadsheets/d/176W720jq1zpjsOcsZTkSmwqhmm_hK4VFINK_aubF8sc/export?format=csv&gid=0 92 | HackfoldrTaskCompletionSource *source = [HackfoldrTaskCompletionSource taskCompletionSource]; 93 | 94 | AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:@"https://docs.google.com/"]]; 95 | manager.responseSerializer = [[self class] CSVSerializer]; 96 | 97 | NSString *requestKeyID = [keyID stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLPathAllowedCharacterSet]]; 98 | NSString *requestPath = [NSString stringWithFormat:@"spreadsheets/d/%@/export?format=csv&gid=0", requestKeyID]; 99 | source.connectionTask = [manager GET:requestPath parameters:nil progress:nil success:^(NSURLSessionDataTask *task, id csvFieldArray) { 100 | HackfoldrPage *page = [[HackfoldrPage alloc] initWithKey:keyID fieldArray:csvFieldArray]; 101 | self->_lastPage = [page copy]; 102 | [source setResult:self->_lastPage]; 103 | } failure:^(NSURLSessionDataTask *task, NSError *error) { 104 | NSLog(@"error:%@ %@", error, task.response); 105 | [source setError:error]; 106 | }]; 107 | 108 | return source; 109 | } 110 | 111 | - (HackfoldrTaskCompletionSource *)taskCompletionWithKey:(NSString *)hackfoldrKey 112 | { 113 | HackfoldrTaskCompletionSource *completionSource = nil; 114 | // check where the data come from, ethercalc or gsheet 115 | if (hackfoldrKey.length < 40) { 116 | completionSource = [self taskCompletionFromEthercalcWithKey:hackfoldrKey]; 117 | } else { 118 | completionSource = [self taskCompletionFromGoogleSheetWithSheetKey:hackfoldrKey]; 119 | } 120 | 121 | return completionSource; 122 | } 123 | 124 | @end 125 | -------------------------------------------------------------------------------- /hackfoldr-iOS/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2014/6/21. 6 | // Copyright (c) 2014年 org.superbil. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | #import 12 | #import 13 | 14 | #import "MainViewController.h" 15 | #import "NSURL+Hackfoldr.h" 16 | 17 | @implementation AppDelegate 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 20 | { 21 | // setup core data 22 | [MagicalRecord setupCoreDataStack]; 23 | 24 | self.viewController = [[UINavigationController alloc] initWithRootViewController:[[MainViewController alloc] init]]; 25 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 26 | self.window.rootViewController = self.viewController; 27 | [self.window makeKeyAndVisible]; 28 | 29 | return YES; 30 | } 31 | 32 | - (void)applicationWillResignActive:(UIApplication *)application 33 | { 34 | // 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. 35 | // 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. 36 | } 37 | 38 | - (void)applicationDidEnterBackground:(UIApplication *)application 39 | { 40 | // 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. 41 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 42 | } 43 | 44 | - (void)applicationWillEnterForeground:(UIApplication *)application 45 | { 46 | // 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. 47 | } 48 | 49 | - (void)applicationDidBecomeActive:(UIApplication *)application 50 | { 51 | // 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. 52 | } 53 | 54 | - (void)applicationWillTerminate:(UIApplication *)application 55 | { 56 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 57 | } 58 | 59 | - (BOOL)application:(UIApplication *)app handleOpenURL:(nonnull NSURL *)url 60 | { 61 | return [NSURL canHandleHackfoldrURL:url]; 62 | } 63 | 64 | - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options 65 | { 66 | NSLog(@"openURL: %@ options: %@", url, options); 67 | return [self tryUpdateHackfoldrPageKeyWithURL:url]; 68 | } 69 | 70 | - (BOOL)application:(UIApplication *)application 71 | openURL:(NSURL *)url 72 | sourceApplication:(NSString *)sourceApplication 73 | annotation:(id)annotation 74 | { 75 | NSLog(@"Calling Application Bundle ID: %@", sourceApplication); 76 | NSLog(@"URL scheme:%@", [url scheme]); 77 | NSLog(@"URL query: %@", [url query]); 78 | return [self tryUpdateHackfoldrPageKeyWithURL:url]; 79 | } 80 | 81 | - (void)updateCurrentHackfoldrPageWithKey:(NSString *)pageKey { 82 | if (!pageKey || pageKey.length == 0) return; 83 | 84 | // Find MainViewController 85 | __block MainViewController *vc = nil; 86 | [((UINavigationController *)self.viewController).viewControllers enumerateObjectsUsingBlock:^(__kindof UIViewController * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 87 | if ([obj isKindOfClass:[MainViewController class]]) { 88 | vc = obj; 89 | *stop = YES; 90 | } 91 | }]; 92 | if (!vc) return; 93 | 94 | dispatch_async(dispatch_get_main_queue(), ^{ 95 | [vc updateHackfoldrPageWithKey:pageKey]; 96 | }); 97 | } 98 | 99 | - (BOOL)tryUpdateHackfoldrPageKeyWithURL:(NSURL *)url { 100 | BOOL canHandle = [NSURL canHandleHackfoldrURL:url]; 101 | [self updateCurrentHackfoldrPageWithKey:url.host]; 102 | return canHandle; 103 | } 104 | 105 | - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray> * _Nullable))restorationHandler { 106 | if ([userActivity.activityType isEqualToString:CSSearchableItemActionType]) { 107 | NSLog(@"CSSearchableItemActionType"); 108 | NSString *uniqueIdentifier = userActivity.userInfo[CSSearchableItemActivityIdentifier]; 109 | NSLog(@"uniqueIdentifier %@", uniqueIdentifier); 110 | [self updateCurrentHackfoldrPageWithKey:uniqueIdentifier]; 111 | } 112 | return YES; 113 | } 114 | 115 | @end 116 | -------------------------------------------------------------------------------- /hackfoldr-iOSTests/TestData/sample.csv.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | "#A1: if not marked as comment (start with #), better less than 40 characters, or will be parsed as a google spreadsheer id", 4 | "#if you want to use google spreadsheet to save foldr data (usually when authorization is needed), fill in A1 with google spreadsheet id, and the same url will be redirected to gsheet.", 5 | "", 6 | "" 7 | ], 8 | [ 9 | "#url or item level. <: following links is level 0 (= top level)", 10 | "#title", 11 | "#option: hide/unsortable(foldr title), expand/collapse(subfoldr), blank(link)", 12 | "#label: (color) content. color: gray/black/green/red/blue/orange/purple/teal/yellow/pink/deep-blue/deep-green/deep-purple/" 13 | ], 14 | [ 15 | "", 16 | "", 17 | "", 18 | "" 19 | ], 20 | [ 21 | "", 22 | "Hackfoldr 使用教學", 23 | "", 24 | "" 25 | ], 26 | [ 27 | "https://g0v.hackpad.com/Hackfoldr-2.0--jfLiSxnllO6", 28 | "Hackfoldr 2.0 教學 - 儲存格語法", 29 | "", 30 | "" 31 | ], 32 | [ 33 | "https://g0v.hackpad.com/G7idRJqbG3I", 34 | "Hackfoldr 2.0 教學 - 自行架站", 35 | "", 36 | "" 37 | ], 38 | [ 39 | "https://g0v.hackpad.com/0.jmm9642fog5vcxr", 40 | "Hackfoldr 2.0 功能列表", 41 | "", 42 | "" 43 | ], 44 | [ 45 | "https://g0v.hackpad.com/Hackfoldr-2.0-7TMSpXasDWl", 46 | "Hackfoldr 2.0 專案開發文件", 47 | "", 48 | "" 49 | ], 50 | [ 51 | "", 52 | "", 53 | "", 54 | "" 55 | ], 56 | [ 57 | "", 58 | "功能示範區", 59 | "expand", 60 | "" 61 | ], 62 | [ 63 | "", 64 | "", 65 | "", 66 | "" 67 | ], 68 | [ 69 | "lalala", 70 | "#", 71 | "i am marked as comment", 72 | "" 73 | ], 74 | [ 75 | "http://logbot.g0v.tw/channel/g0v.tw/today", 76 | "測試 link item", 77 | "blank", 78 | "deep-green label" 79 | ], 80 | [ 81 | "http://logbot.g0v.tw/channel/g0v.tw/today", 82 | "測試 link item", 83 | "", 84 | "teal label" 85 | ], 86 | [ 87 | "", 88 | "", 89 | "", 90 | "" 91 | ], 92 | [ 93 | "http://g0v.tw", 94 | "測試 link item 測試 link item 測試 link item ", 95 | "", 96 | "green label" 97 | ], 98 | [ 99 | "http://logbot.g0v.tw/channel/g0v.tw/today", 100 | "測試 link item 測試 link item 測試 link item ", 101 | "blank", 102 | "black label" 103 | ], 104 | [ 105 | "http://g0v.tw", 106 | "測試 link item 測試 link item 測試 link item ", 107 | "", 108 | "故事告訴我們 label 不可以太長" 109 | ], 110 | [ 111 | "http://logbot.g0v.tw/channel/g0v.tw/today", 112 | "測試 link item", 113 | "", 114 | "blue redmine" 115 | ], 116 | [ 117 | "http://logbot.g0v.tw/channel/g0v.tw/today", 118 | "測試 link item", 119 | "", 120 | "green blue island" 121 | ], 122 | [ 123 | "http://logbot.g0v.tw/channel/g0v.tw/today", 124 | "測試 link item", 125 | "", 126 | "yellow green island" 127 | ], 128 | [ 129 | "http://logbot.g0v.tw/channel/g0v.tw/today", 130 | "測試 link item", 131 | "", 132 | "pink red green blue" 133 | ], 134 | [ 135 | "http://logbot.g0v.tw/channel/g0v.tw/today", 136 | "測試 link item", 137 | "", 138 | "purple legendary weapons" 139 | ], 140 | [ 141 | "http://logbot.g0v.tw/channel/g0v.tw/today", 142 | "測試 link item", 143 | "", 144 | "lol:warning" 145 | ], 146 | [ 147 | "http://logbot.g0v.tw/channel/g0v.tw/today", 148 | "測試 link item", 149 | "", 150 | "nice:important" 151 | ], 152 | [ 153 | "http://logbot.g0v.tw/channel/g0v.tw/today", 154 | "測試 link item", 155 | "", 156 | "oops:issue" 157 | ], 158 | [ 159 | "https://www.youtube.com/watch?v=WCX1XQ0S9Yk", 160 | "測試 link item", 161 | "", 162 | "green gxv:important" 163 | ], 164 | [ 165 | "https://www.flickr.com/photos/othree/sets/72157646878582219", 166 | "活動紀錄 flickr 相簿", 167 | "", 168 | "" 169 | ], 170 | [ 171 | "", 172 | "", 173 | "", 174 | "" 175 | ], 176 | [ 177 | "", 178 | "", 179 | "", 180 | "" 181 | ], 182 | [ 183 | "", 184 | "Code for Kaohsiung 暫存區 (等 mikimoto 搬好家後再刪掉)", 185 | "collapse", 186 | "" 187 | ], 188 | [ 189 | "", 190 | "", 191 | "", 192 | "" 193 | ], 194 | [ 195 | "", 196 | "進行中", 197 | "expand", 198 | "" 199 | ], 200 | [ 201 | "https://codeforkaohsiung.hackpad.com/-Open-Data--76L9xyVlmvN", 202 | "高雄市戶政資料 Open Data 化", 203 | "", 204 | "進行中:important" 205 | ], 206 | [ 207 | "", 208 | "", 209 | "", 210 | "" 211 | ], 212 | [ 213 | "", 214 | "討論中", 215 | "expand", 216 | "" 217 | ], 218 | [ 219 | "https://codeforkaohsiung.hackpad.com/t9Zs76X0xzb", 220 | "CityDashBoard for Kaohsiung", 221 | "", 222 | "green 孵化中" 223 | ], 224 | [ 225 | "https://codeforkaohsiung.hackpad.com/0VzJsbx9FfW", 226 | "我有新點子", 227 | "", 228 | "green 孵化中" 229 | ], 230 | [ 231 | "", 232 | "", 233 | "", 234 | "" 235 | ], 236 | [ 237 | "", 238 | "尋求協助或支援", 239 | "collapse", 240 | "" 241 | ], 242 | [ 243 | "https://g0v.hackpad.com/KjfdRZ08FZ3", 244 | "議員投票指南", 245 | "", 246 | "g0v:important" 247 | ], 248 | [ 249 | "http://comap.clkao.org/comap/KHH/0989", 250 | "全國投開票所鄉民 Geocode", 251 | "", 252 | "g0v:important" 253 | ], 254 | [ 255 | "", 256 | "", 257 | "", 258 | "" 259 | ], 260 | [ 261 | "", 262 | "", 263 | "", 264 | "" 265 | ], 266 | [ 267 | "https://github.com/codeforkaohsiung/", 268 | "Code for Kaohsiung Github", 269 | "", 270 | "" 271 | ], 272 | [ 273 | "http://codeforkaohsiung.slack.com", 274 | "Slack 交流頻道 (需邀請)", 275 | "blank", 276 | "" 277 | ] 278 | ] -------------------------------------------------------------------------------- /hackfoldr-iOS/QRCodeViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // QRCodeViewController.m 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2017/11/22. 6 | // Copyright © 2017年 org.g0v. All rights reserved. 7 | // 8 | 9 | #import "QRCodeViewController.h" 10 | 11 | #import 12 | 13 | @interface QRCodeViewController () 14 | @property IBOutlet NSLayoutConstraint *widthOfCenterViewConstraint; 15 | @property IBOutlet UIView *centerView; 16 | @property IBOutlet UIImageView *qrCodeImageView; 17 | @property IBOutlet UIView *cameraView; 18 | 19 | @property AVCaptureDevice *device; 20 | @property AVCaptureSession *session; 21 | @property AVCaptureVideoPreviewLayer *previewLayer; 22 | @end 23 | 24 | @implementation QRCodeViewController 25 | 26 | + (instancetype)viewController { 27 | return [[UIStoryboard storyboardWithName:@"QRCode" bundle:nil] instantiateViewControllerWithIdentifier:@"QRCodeViewController"]; 28 | } 29 | 30 | - (void)viewDidLoad { 31 | [super viewDidLoad]; 32 | // Do any additional setup after loading the view. 33 | } 34 | 35 | - (void)didReceiveMemoryWarning { 36 | [super didReceiveMemoryWarning]; 37 | // Dispose of any resources that can be recreated. 38 | } 39 | 40 | - (void)viewWillAppear:(BOOL)animated { 41 | [super viewWillAppear:animated]; 42 | 43 | if (self.qrCodeString) { 44 | self.title = NSLocalizedStringFromTable(@"Show QR Code", @"Hackfoldr", @"title of QR code view controller"); 45 | } else { 46 | self.title = NSLocalizedStringFromTable(@"Scan QR Code", @"Hackfoldr", @"title of QR code view controller"); 47 | } 48 | } 49 | 50 | - (void)viewDidAppear:(BOOL)animated { 51 | [super viewDidAppear:animated]; 52 | 53 | if (self.qrCodeString) { 54 | CGSize size = self.qrCodeImageView.frame.size; 55 | CIImage *ciImage = [self outputNormalImageWithString:self.qrCodeString imageSize:size]; 56 | self.qrCodeImageView.image = [self createNonInterpolatedUIImageFormCIImage:ciImage withImageSize:size]; 57 | self.cameraView.hidden = YES; 58 | } else { 59 | self.centerView.hidden = YES; 60 | #if !TARGET_IPHONE_SIMULATOR 61 | [self showCapture]; 62 | #endif 63 | } 64 | } 65 | 66 | #pragma mark - Private 67 | 68 | - (CIImage *)outputNormalImageWithString:(NSString *)string 69 | imageSize:(CGSize)imageSize { 70 | NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; 71 | 72 | CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"]; 73 | [filter setDefaults]; 74 | [filter setValue:data forKey:@"inputMessage"]; 75 | [filter setValue:@"H" forKey:@"inputCorrectionLevel"]; 76 | 77 | return [filter outputImage]; 78 | } 79 | 80 | - (UIImage *)createNonInterpolatedUIImageFormCIImage:(CIImage *)image 81 | withImageSize:(CGSize)imageSize { 82 | CGRect extent = CGRectIntegral(image.extent); 83 | CGFloat scale = MIN(imageSize.width / CGRectGetWidth(extent), imageSize.height / CGRectGetHeight(extent)); 84 | 85 | size_t width = CGRectGetWidth(extent) * scale; 86 | size_t height = CGRectGetHeight(extent) * scale; 87 | CGColorSpaceRef cs = CGColorSpaceCreateDeviceGray(); 88 | CGContextRef bitmapRef = CGBitmapContextCreate(nil, width, height, 8, 0, cs, (CGBitmapInfo)kCGImageAlphaNone); 89 | CIContext *context = [CIContext contextWithOptions:nil]; 90 | CGImageRef bitmapImage = [context createCGImage:image fromRect:extent]; 91 | CGContextSetInterpolationQuality(bitmapRef, kCGInterpolationNone); 92 | CGContextScaleCTM(bitmapRef, scale, scale); 93 | CGContextDrawImage(bitmapRef, extent, bitmapImage); 94 | 95 | CGImageRef scaledImage = CGBitmapContextCreateImage(bitmapRef); 96 | CGContextRelease(bitmapRef); 97 | CGImageRelease(bitmapImage); 98 | 99 | return [UIImage imageWithCGImage:scaledImage];; 100 | } 101 | 102 | - (void)showCapture { 103 | self.device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; 104 | 105 | AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:self.device error:nil]; 106 | AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc] init]; 107 | [output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()]; 108 | 109 | self.session = [[AVCaptureSession alloc] init]; 110 | [self.session setSessionPreset:AVCaptureSessionPresetHigh]; 111 | 112 | if ([self.session canAddInput:input]) { 113 | [self.session addInput:input]; 114 | } 115 | if ([self.session canAddOutput:output]) { 116 | [self.session addOutput:output]; 117 | } 118 | 119 | output.metadataObjectTypes = @[AVMetadataObjectTypeQRCode, AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code]; 120 | 121 | self.previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.session]; 122 | self.previewLayer.videoGravity = AVLayerVideoGravityResize;; 123 | self.previewLayer.frame = self.view.bounds; 124 | 125 | [self.cameraView.layer insertSublayer:self.previewLayer atIndex:0]; 126 | self.centerView.hidden = YES; 127 | 128 | [self.session startRunning]; 129 | } 130 | 131 | #pragma mark - AVCaptureMetadataOutputObjectsDelegate 132 | 133 | - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects 134 | fromConnection:(AVCaptureConnection *)connection { 135 | if (metadataObjects != nil && [metadataObjects count] > 0) { 136 | AVMetadataMachineReadableCodeObject *metadataObj = [metadataObjects objectAtIndex:0]; 137 | if (metadataObj.stringValue && metadataObj.stringValue.length > 0) { 138 | 139 | [self.session stopRunning]; 140 | 141 | if (self.foundedResult) { 142 | self.foundedResult(metadataObj.stringValue); 143 | } 144 | } 145 | } 146 | } 147 | 148 | @end 149 | -------------------------------------------------------------------------------- /hackfoldr-iOS/QRCode.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /hackfoldr-iOS/MainViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.m 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2014/6/21. 6 | // Copyright (c) 2014年 org.superbil. All rights reserved. 7 | // 8 | 9 | #import "MainViewController.h" 10 | 11 | #import 12 | #import 13 | 14 | #import "AppDelegate.h" 15 | // Model & Client 16 | #import "HackfoldrClient.h" 17 | #import "HackfoldrPage.h" 18 | // Category 19 | #import "HackfoldrClient+Store.h" 20 | #import "NSUserDefaults+DefaultHackfoldrPage.h" 21 | #import "UIColor+Hackfoldr.h" 22 | // ViewController 23 | #import 24 | #import "ListFieldViewController.h" 25 | 26 | @interface MainViewController () 27 | @property (nonatomic, strong) ListFieldViewController *listViewController; 28 | @property (nonatomic, strong) UIImageView *backgroundImageView; 29 | @end 30 | 31 | @implementation MainViewController 32 | 33 | - (void)viewDidLoad 34 | { 35 | [super viewDidLoad]; 36 | 37 | self.title = [[NSBundle mainBundle].infoDictionary objectForKey:@"CFBundleName"]; 38 | 39 | UIButton *reloadButton = [UIButton buttonWithType:UIButtonTypeSystem]; 40 | CGFloat iconSize = 24; 41 | reloadButton.frame = CGRectMake(0, 0, iconSize, iconSize); 42 | UIImage *reloadImage = [[FAKFontAwesome refreshIconWithSize:iconSize] imageWithSize:reloadButton.frame.size]; 43 | [reloadButton setImage:reloadImage forState:UIControlStateNormal]; 44 | [reloadButton addTarget:self 45 | action:@selector(reloadAction:) 46 | forControlEvents:UIControlEventTouchUpInside]; 47 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:reloadButton]; 48 | 49 | self.listViewController = [ListFieldViewController viewController]; 50 | 51 | UIImage *backgroundImage = [[UIImage imageNamed:@"hackfoldr-icon"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 52 | self.backgroundImageView = [[UIImageView alloc] initWithImage:backgroundImage]; 53 | self.backgroundImageView.tintColor = [UIColor hackfoldrIconWhite]; 54 | self.backgroundImageView.backgroundColor = [UIColor clearColor]; 55 | [self updateBackgroundImageWithSize:self.view.frame.size]; 56 | 57 | if (self.backgroundImageView) { 58 | [self.view addSubview:self.backgroundImageView]; 59 | } 60 | 61 | self.view.backgroundColor = [UIColor hackfoldrGreenColor]; 62 | 63 | [self mainNavigationController].delegate = self; 64 | 65 | [CSSearchableIndex.defaultSearchableIndex deleteAllSearchableItemsWithCompletionHandler:^(NSError * _Nullable error) { 66 | if (error) { 67 | NSLog(@"deleteAllSearchableItems %@", error); 68 | } 69 | }]; 70 | } 71 | 72 | - (void)didReceiveMemoryWarning 73 | { 74 | [super didReceiveMemoryWarning]; 75 | } 76 | 77 | #pragma mark - Setter & Getter 78 | 79 | - (UINavigationController *)mainNavigationController 80 | { 81 | return (UINavigationController *)((AppDelegate *)[UIApplication sharedApplication].delegate).viewController; 82 | } 83 | 84 | - (void)updateBackgroundImageWithSize:(CGSize)size 85 | { 86 | CGFloat imageSize = 176.f; 87 | self.backgroundImageView.frame = CGRectMake(size.width/2.f - imageSize/2.f, 88 | size.height/2.f - imageSize/2.f, 89 | imageSize, 90 | imageSize); 91 | } 92 | 93 | - (NSString *)hackfoldrPageKey 94 | { 95 | NSString *pageKey = [[NSUserDefaults standardUserDefaults] stringOfCurrentHackfoldrPage]; 96 | 97 | if (!pageKey || pageKey.length == 0) { 98 | NSString *defaultPage = @"hackfoldr-iOS"; 99 | 100 | [[NSUserDefaults standardUserDefaults] setDefaultHackfoldrPage:defaultPage]; 101 | [[NSUserDefaults standardUserDefaults] synchronize]; 102 | 103 | pageKey = [[NSUserDefaults standardUserDefaults] stringOfCurrentHackfoldrPage]; 104 | } 105 | 106 | return pageKey; 107 | } 108 | 109 | #pragma mark - View Flow 110 | 111 | - (void)viewDidAppear:(BOOL)animated 112 | { 113 | [super viewDidAppear:animated]; 114 | 115 | [self reloadAction:self]; 116 | } 117 | 118 | - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator 119 | { 120 | [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; 121 | 122 | [self updateBackgroundImageWithSize:size]; 123 | } 124 | 125 | #pragma mark - UINavigationControllerDelegate 126 | 127 | - (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated 128 | { 129 | 130 | } 131 | 132 | #pragma mark - Actions 133 | 134 | - (void)showListViewController 135 | { 136 | // When List view is showed, don't show again 137 | for (UIViewController *vc in self.navigationController.viewControllers) { 138 | if ([vc isKindOfClass:[self.listViewController class]] && vc == self.listViewController) { 139 | [self.navigationController popToViewController:vc animated:YES]; 140 | return; 141 | } 142 | } 143 | 144 | [self.navigationController pushViewController:self.listViewController animated:YES]; 145 | } 146 | 147 | - (void)reloadAction:(id)sender 148 | { 149 | HackfoldrTaskCompletionSource *completionSource = [[HackfoldrClient sharedClient] hackfoldrPageTaskWithKey:[self hackfoldrPageKey] rediredKey:nil]; 150 | 151 | NSString *title = NSLocalizedStringFromTable(@"Error", @"Hackfoldr", @"Error title"); 152 | NSString *cancelButtonTitle = NSLocalizedStringFromTable(@"Cancel", @"Hackfoldr", @"Alert Cancel button"); 153 | NSString *setupTitle = NSLocalizedStringFromTable(@"Setup Key", @"Hackfoldr", @"Alert Setup button"); 154 | 155 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleAlert]; 156 | [alert addAction:[UIAlertAction actionWithTitle:setupTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 157 | [self.listViewController showSettingViewController]; 158 | [alert dismissViewControllerAnimated:YES completion:nil]; 159 | }]]; 160 | 161 | [alert addAction:[UIAlertAction actionWithTitle:cancelButtonTitle style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { 162 | if ([HackfoldrClient sharedClient].lastPage) { 163 | [self showListViewController]; 164 | } 165 | [alert dismissViewControllerAnimated:YES completion:nil]; 166 | }]]; 167 | 168 | [completionSource.task continueWithBlock:^id _Nullable(BFTask * _Nonnull t) { 169 | if (t.error) { 170 | [self presentViewController:alert animated:YES completion:nil]; 171 | } 172 | return nil; 173 | }]; 174 | 175 | [completionSource.task continueWithSuccessBlock:^id _Nullable(BFTask * _Nonnull t) { 176 | [self showListViewController]; 177 | 178 | HackfoldrPage *page = t.result; 179 | self.listViewController.page = page; 180 | // Reload tableView 181 | [self.listViewController reloadPage]; 182 | return nil; 183 | }]; 184 | } 185 | 186 | #pragma mark - Public method 187 | 188 | - (void)updateHackfoldrPageWithKey:(NSString *)hackfoldrKey 189 | { 190 | [self.listViewController updateHackfoldrPageWithKey:hackfoldrKey]; 191 | } 192 | 193 | @end 194 | -------------------------------------------------------------------------------- /hackfoldr-iOSTests/hackfoldr_iOSTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // hackfoldr_iOSTests.m 3 | // hackfoldr-iOSTests 4 | // 5 | // Created by Superbil on 2014/6/21. 6 | // Copyright (c) 2014年 org.superbil. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | #import "HackfoldrClient.h" 14 | #import "HackfoldrField.h" 15 | #import "HackfoldrPage.h" 16 | #import "NSURL+Hackfoldr.h" 17 | 18 | @interface AnnotatedRequestSerializer : AFHTTPRequestSerializer @end 19 | @implementation AnnotatedRequestSerializer 20 | - (NSMutableURLRequest *)requestWithMethod:(NSString *)method 21 | URLString:(NSString *)URLString 22 | parameters:(NSDictionary *)parameters 23 | error:(NSError * __autoreleasing *)error { 24 | NSMutableURLRequest* req = [super requestWithMethod:method URLString:URLString parameters:parameters error:error]; 25 | [NSURLProtocol setProperty:parameters forKey:@"parameters" inRequest:req]; 26 | [NSURLProtocol setProperty:req.HTTPBody forKey:@"HTTPBody" inRequest:req]; 27 | return req; 28 | } 29 | @end 30 | 31 | 32 | @interface HackfoldrClient (UnitTest) 33 | - (instancetype)initWithBaseURLForUnitTest:(NSURL *)url; 34 | @end 35 | 36 | @implementation HackfoldrClient (UnitTest) 37 | 38 | - (instancetype)initWithBaseURLForUnitTest:(NSURL *)url { 39 | self = [super initWithBaseURL:url]; 40 | if (self) { 41 | self.requestSerializer = [AnnotatedRequestSerializer serializer]; 42 | self.responseSerializer = [AFJSONResponseSerializer serializer]; 43 | } 44 | return self; 45 | } 46 | 47 | @end 48 | 49 | 50 | @interface hackfoldr_iOSTests : XCTestCase 51 | @end 52 | 53 | @implementation hackfoldr_iOSTests 54 | 55 | - (void)setUp 56 | { 57 | [super setUp]; 58 | } 59 | 60 | - (void)tearDown 61 | { 62 | [super tearDown]; 63 | } 64 | 65 | - (void)testHackfoldrPage 66 | { 67 | HackfoldrPage *page = [[HackfoldrPage alloc] initWithFieldArray:@[@[@"abcdefg_is_key_length_must_bigger_than_40", @"A1"]]]; 68 | XCTAssertTrue(page.rediredKey!=nil); 69 | XCTAssertTrue(page.rediredKey.length > 0); 70 | 71 | NSString *comment = @"#A1: if not marked as comment (start with #), better less than 40 characters, or will be parsed as a google spreadsheer id"; 72 | HackfoldrPage *commentPage = [[HackfoldrPage alloc] initWithFieldArray:@[@[comment, @"A1 comment"]]]; 73 | XCTAssertNil(commentPage.rediredKey); 74 | } 75 | 76 | - (void)testHackfoldrField 77 | { 78 | NSString *nameURL = @"http://www.g0v.tw"; 79 | NSString *name = @"Name "; 80 | NSString *actions = @"{target}"; 81 | HackfoldrField *field = [[HackfoldrField alloc] initWithFieldArray:@[nameURL, name, actions]]; 82 | XCTAssertTrue([field.urlString isEqualToString:nameURL], @""); 83 | XCTAssertTrue([field.name isEqualToString:name], @""); 84 | XCTAssertTrue([field.actions isEqualToString:actions], @""); 85 | XCTAssertTrue(field.isSubItem, @""); 86 | 87 | NSString *subItemURL = @" http://www.g0v.tw"; 88 | HackfoldrField *subField = [[HackfoldrField alloc] initWithFieldArray:@[subItemURL, name, actions]]; 89 | NSString *subString = [subItemURL substringWithRange:NSMakeRange(1, subItemURL.length-1)]; 90 | XCTAssertTrue([subField.urlString isEqualToString:subString], @""); 91 | XCTAssertTrue([subField.name isEqualToString:name], @""); 92 | XCTAssertTrue([subField.actions isEqualToString:actions], @""); 93 | XCTAssertTrue(subField.isSubItem, @"isSubItem must be YES"); 94 | 95 | // Hacfoldr 2.0 rule 96 | NSString *topItemURL = @"< http://hackfoldr.org/"; 97 | HackfoldrField *topField = [[HackfoldrField alloc] initWithFieldArray:@[topItemURL, name, actions]]; 98 | NSString *subStringOfTopField = [topItemURL substringWithRange:NSMakeRange(2, topItemURL.length -2)]; 99 | XCTAssertFalse(topField.isSubItem); 100 | XCTAssertTrue([topField.urlString isEqualToString:subStringOfTopField]); 101 | 102 | NSString *commentURL = @"# yooo"; 103 | HackfoldrField *commentField = [[HackfoldrField alloc] initWithFieldArray:@[@"", commentURL, actions]]; 104 | XCTAssertTrue(commentField.isCommentLine); 105 | 106 | NSString *notCleanCommentURL = @"\"# not yoo\""; 107 | HackfoldrField *notCleanCommentField = [[HackfoldrField alloc] initWithFieldArray:@[@"", notCleanCommentURL, actions]]; 108 | XCTAssertTrue(notCleanCommentField.isCommentLine); 109 | 110 | NSString *lableString = @"blue I am red"; 111 | HackfoldrField *labelField = [[HackfoldrField alloc] initWithFieldArray:@[nameURL, @"label", actions, lableString]]; 112 | XCTAssertTrue([labelField.labelString isEqualToString:@"I am red"]); 113 | 114 | NSString *commentLabelString = @"lol:warning"; 115 | HackfoldrField *commentLabelField = [[HackfoldrField alloc] initWithFieldArray:@[nameURL, @"commentLabel", actions, commentLabelString]]; 116 | NSLog(@"%@",commentLabelField.labelString); 117 | XCTAssertTrue([commentLabelField.labelString isEqualToString:@"lol"]); 118 | } 119 | 120 | - (void)testHackfoldrClient 121 | { 122 | XCTestExpectation *openHackfoldrExpectation = [self expectationWithDescription:@"open Hackfoldr"]; 123 | 124 | id stub = [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) { 125 | return [request.URL.absoluteString rangeOfString:@"ethercalc.org"].location != NSNotFound; 126 | } withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) { 127 | NSLog(@"hook hackfoldr:%@", request); 128 | NSString *jsonCSVDataString = OHPathForFile(@"sample.csv.json", [self class]); 129 | XCTAssertNotNil(jsonCSVDataString); 130 | NSData *csvData = [NSData dataWithContentsOfFile:jsonCSVDataString]; 131 | 132 | return [OHHTTPStubsResponse responseWithData:csvData 133 | statusCode:200 134 | headers:@{@"Content-Type":@"text/json"}]; 135 | }]; 136 | 137 | HackfoldrClient *client = [[HackfoldrClient alloc] initWithBaseURLForUnitTest:[NSURL URLWithString:@"https://ethercalc.org/"]]; 138 | [[client taskCompletionWithKey:@"testHackFoldr"].task continueWithBlock:^id(BFTask *task) { 139 | XCTAssertNil(task.error); 140 | NSLog(@"task %@ %@", task.error, task.result); 141 | 142 | [openHackfoldrExpectation fulfill];; 143 | return nil; 144 | }]; 145 | 146 | [self waitForExpectationsWithTimeout:1000 handler:^(NSError * _Nullable error) { 147 | [OHHTTPStubs removeStub:stub]; 148 | }]; 149 | } 150 | 151 | - (void)testHackfoldrURL { 152 | XCTAssertTrue([NSURL canHandleHackfoldrURL:[NSURL URLWithString:@"hackfoldr://test"]]); 153 | XCTAssertTrue([NSURL canHandleHackfoldrURL:[NSURL URLWithString:@"http://hackfoldr.org"]]); 154 | XCTAssertTrue([NSURL canHandleHackfoldrURL:[NSURL URLWithString:@"http://beta.hackfoldr.org"]]); 155 | XCTAssertTrue([NSURL canHandleHackfoldrURL:[NSURL URLWithString:@"https://hackfoldr.org"]]); 156 | XCTAssertFalse([NSURL canHandleHackfoldrURL:[NSURL URLWithString:@"https://hackfoldr-iOS"]]); 157 | XCTAssertFalse([NSURL canHandleHackfoldrURL:[NSURL URLWithString:@"http://nobody.hackfoldr.org"]]); 158 | } 159 | 160 | - (void)testRealKey { 161 | NSString *realKeyOfHackfoldr = [NSURL realKeyOfHackfoldrWithURL:[NSURL URLWithString:@"hackfoldr://hackfoldr-iOS/"]]; 162 | XCTAssertNotNil(realKeyOfHackfoldr); 163 | XCTAssertEqualObjects(realKeyOfHackfoldr, @"hackfoldr-iOS"); 164 | 165 | NSString *realKeyOfHackfoldrOrg = [NSURL realKeyOfHackfoldrWithURL:[NSURL URLWithString:@"https://hackfoldr.org/hackfoldr-iOS/"]]; 166 | XCTAssertNotNil(realKeyOfHackfoldrOrg); 167 | XCTAssertEqualObjects(realKeyOfHackfoldrOrg, @"hackfoldr-iOS"); 168 | 169 | NSString *realKeyOfBeta = [NSURL realKeyOfHackfoldrWithURL:[NSURL URLWithString:@"http://beta.hackfoldr.org/hackfoldr-iOS/"]]; 170 | XCTAssertNotNil(realKeyOfBeta); 171 | XCTAssertEqualObjects(realKeyOfBeta, @"hackfoldr-iOS"); 172 | 173 | NSString *badRealKey = [NSURL realKeyOfHackfoldrWithURL:[NSURL URLWithString:@"https://hackfoldr-iOS"]]; 174 | XCTAssertNil(badRealKey); 175 | } 176 | 177 | @end 178 | -------------------------------------------------------------------------------- /hackfoldr-iOS/HackfoldrField.m: -------------------------------------------------------------------------------- 1 | // 2 | // HackfoldrField.m 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2014/6/22. 6 | // Copyright (c) 2014年 org.superbil. All rights reserved. 7 | // 8 | 9 | #import "HackfoldrField.h" 10 | 11 | //RGB color macro 12 | #define UIColorFromRGB(rgbValue) [UIColor \ 13 | colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 \ 14 | green:((float)((rgbValue & 0xFF00) >> 8))/255.0 \ 15 | blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0] 16 | 17 | typedef NS_ENUM(NSUInteger, FieldType) { 18 | FieldType_URL = 0, 19 | FieldType_Title, 20 | FieldType_Foldrexpand, 21 | FieldType_Label 22 | }; 23 | 24 | @interface HackfoldrField () { 25 | NSString *_urlString; 26 | NSString *_labelString; 27 | } 28 | @end 29 | 30 | @implementation HackfoldrField 31 | 32 | - (instancetype)init { 33 | if (self = [super init]) { 34 | _subFields = [NSMutableArray array]; 35 | } 36 | return self; 37 | } 38 | 39 | - (instancetype)initWithFieldArray:(NSArray *)fields 40 | { 41 | self = [self init]; 42 | if (!self) { 43 | return nil; 44 | } 45 | 46 | if (!fields) { 47 | return self; 48 | } 49 | 50 | [fields enumerateObjectsUsingBlock:^(NSString *field, NSUInteger idx, BOOL *stop) { 51 | switch (idx) { 52 | case FieldType_URL: 53 | self.urlString = field; 54 | break; 55 | case FieldType_Title: 56 | self.name = field; 57 | break; 58 | case FieldType_Foldrexpand: 59 | self.actions = field; 60 | break; 61 | case FieldType_Label: 62 | self.labelString = field; 63 | break; 64 | default: 65 | break; 66 | } 67 | }]; 68 | // hackfoldr 2.0 rule 69 | self.isCommentLine = [self isCommentLineWithFieldArray:fields]; 70 | 71 | return self; 72 | } 73 | 74 | - (BOOL)isCommentLineWithFieldArray:(NSArray *)fields 75 | { 76 | __block BOOL isCommentLine = NO; 77 | [fields enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 78 | if ([obj isKindOfClass:[NSString class]]) { 79 | NSString *field = [obj stringByReplacingOccurrencesOfString:@"\"" withString:@""];; 80 | // only read first one 81 | if ([field hasPrefix:@"#"]) { 82 | isCommentLine = YES; 83 | *stop = YES; 84 | } 85 | } 86 | }]; 87 | return isCommentLine; 88 | } 89 | 90 | #pragma mark - Setter and Getter 91 | 92 | - (BOOL)isEmpty 93 | { 94 | if (!self.urlString && !self.name && !self.actions) { 95 | return YES; 96 | } 97 | 98 | if (self.urlString.length == 0 && self.name.length == 0 && self.actions.length == 0) { 99 | return YES; 100 | } 101 | 102 | return NO; 103 | } 104 | 105 | - (void)setUrlString:(NSString *)aURLString 106 | { 107 | NSString *cleanString = [aURLString stringByReplacingOccurrencesOfString:@" " withString:@""]; 108 | cleanString = [cleanString stringByReplacingOccurrencesOfString:@"\"" withString:@""]; 109 | cleanString = [cleanString stringByReplacingOccurrencesOfString:@"<" withString:@""]; 110 | _urlString = cleanString; 111 | 112 | [self setIsSubItemWithURLString:aURLString]; 113 | } 114 | 115 | - (NSString *)urlString 116 | { 117 | return _urlString; 118 | } 119 | 120 | - (void)setIsSubItemWithURLString:(NSString *)aURLString 121 | { 122 | // |aURLString| must have '://', otherwise that is not subItem 123 | if (!aURLString || 124 | aURLString.length == 0 || 125 | [aURLString rangeOfString:@"://"].location == NSNotFound) { 126 | return; 127 | } 128 | 129 | // hackfoldr 2.0 rule, default is subItem 130 | self.isSubItem = YES; 131 | // While first string is space, this HackfoldrField is subItem 132 | if ([aURLString hasPrefix:@" "]) { 133 | self.isSubItem = YES; 134 | } 135 | if ([aURLString hasPrefix:@"<"]) { 136 | self.isSubItem = NO; 137 | } 138 | } 139 | 140 | - (void)setLabelString:(NSString *)labelString 141 | { 142 | if (labelString.length == 0) { 143 | _labelString = labelString; 144 | return; 145 | } 146 | 147 | __block NSString *colorName = nil; 148 | [[self colorTable] enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull name, UIColor * _Nonnull c, BOOL * _Nonnull stop) { 149 | if ([labelString rangeOfString:name].location != NSNotFound) { 150 | colorName = name; 151 | *stop = YES; 152 | } 153 | }]; 154 | if (colorName && [self updateLabelColorByString:colorName]) { 155 | NSString *cleanLabel = [labelString stringByReplacingOccurrencesOfString:colorName withString:@""]; 156 | cleanLabel = [cleanLabel stringByReplacingOccurrencesOfString:@"important" withString:@""]; 157 | cleanLabel = [cleanLabel stringByReplacingOccurrencesOfString:@"warning" withString:@""]; 158 | cleanLabel = [cleanLabel stringByReplacingOccurrencesOfString:@"issue" withString:@""]; 159 | cleanLabel = [cleanLabel stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@":"]]; 160 | cleanLabel = [cleanLabel stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 161 | _labelString = cleanLabel; 162 | return; 163 | } 164 | 165 | _labelString = labelString; 166 | } 167 | 168 | - (NSString *)labelString 169 | { 170 | return _labelString; 171 | } 172 | 173 | - (NSDictionary *)colorTable { 174 | UIColor *defaultColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.35f]; 175 | return @{@"black" : UIColorFromRGB(0x5C6166), 176 | @"blue" : UIColorFromRGB(0x6ECFF5), 177 | @"deep-blue" : UIColorFromRGB(0x006DCC), 178 | @"deep-green" : UIColorFromRGB(0x5BB75B), 179 | @"deep-purple" : UIColorFromRGB(0x564F8A), 180 | @"gray" : defaultColor, 181 | @"green" : UIColorFromRGB(0xA1CF64), 182 | @"important" : UIColorFromRGB(0xD95C5C), 183 | @"issue" : defaultColor, 184 | @"orange" : UIColorFromRGB(0xF0AD4E), 185 | @"pink" : UIColorFromRGB(0xF3A8AA), 186 | @"purple" : UIColorFromRGB(0x9B96F7), 187 | @"red" : UIColorFromRGB(0xD95C5C), 188 | @"teal" : UIColorFromRGB(0x00B5AD), 189 | @"warning" : UIColorFromRGB(0xF0AD4E), 190 | @"yellow" : UIColorFromRGB(0xF0AD4E), 191 | }; 192 | } 193 | 194 | - (BOOL)updateLabelColorByString:(NSString *)colorString 195 | { 196 | self.labelColor = [self colorTable][colorString]; 197 | if (self.labelColor) { 198 | return YES; 199 | } 200 | 201 | self.labelColor = [self colorTable][@"gray"]; 202 | return NO; 203 | } 204 | 205 | #pragma mark - DEBUG 206 | 207 | - (NSString *)description 208 | { 209 | NSMutableString *description = [NSMutableString string]; 210 | 211 | [description appendFormat:@"index:%ld ", (long)self.index]; 212 | if (self.name) { 213 | [description appendFormat:@"name: %@ ", self.name]; 214 | } 215 | if (self.urlString) { 216 | [description appendFormat:@"urlString: %@ ", self.urlString]; 217 | } 218 | if (self.actions) { 219 | [description appendFormat:@"actions: %@ ", self.actions]; 220 | } 221 | 222 | [description appendFormat:@"isSubItem: %@ ", self.isSubItem ? @"YES" : @"NO"]; 223 | [description appendFormat:@"isCommentLine: %@ ", self.isCommentLine ? @"YES" : @"NO"]; 224 | 225 | if (self.subFields.count > 0) { 226 | [description appendString:@"subFields: {\n"]; 227 | for (HackfoldrField *sf in self.subFields) { 228 | [description appendFormat:@"\t%@\n", sf.description]; 229 | } 230 | [description appendString:@"}\n"]; 231 | } 232 | 233 | if (self.labelString) { 234 | [description appendFormat:@"labelString: %@ ", self.labelString]; 235 | } 236 | 237 | if (self.labelColor) { 238 | [description appendFormat:@"labelColor: %@ ", self.labelColor]; 239 | } 240 | 241 | return description; 242 | } 243 | 244 | @end 245 | -------------------------------------------------------------------------------- /hackfoldr-iOS/SettingViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SettingViewController.m 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2017/11/2. 6 | // Copyright © 2017年 org.g0v. All rights reserved. 7 | // 8 | 9 | #import "SettingViewController.h" 10 | 11 | #import 12 | 13 | // Model & Client 14 | #import 15 | #import "HackfoldrClient.h" 16 | #import "HackfoldrHistory.h" 17 | #import "HackfoldrPage.h" 18 | 19 | // Cateogry 20 | #import "NSURL+Hackfoldr.h" 21 | #import "NSUserDefaults+DefaultHackfoldrPage.h" 22 | 23 | // View Controller 24 | #import "QRCodeViewController.h" 25 | 26 | @interface SettingViewController () 27 | 28 | @end 29 | 30 | @implementation SettingViewController 31 | 32 | - (instancetype)init { 33 | XLFormDescriptor *form = [self settingForm]; 34 | if (self = [super initWithForm:form]) { 35 | } 36 | return self; 37 | } 38 | 39 | - (void)viewDidLoad { 40 | [super viewDidLoad]; 41 | 42 | } 43 | 44 | - (void)viewDidAppear:(BOOL)animated { 45 | [super viewDidAppear:animated]; 46 | UIBarButtonItem *shareItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(shareAction)]; 47 | [self.navigationController.navigationBar.topItem setRightBarButtonItem:shareItem animated:animated]; 48 | } 49 | 50 | - (void)didReceiveMemoryWarning { 51 | [super didReceiveMemoryWarning]; 52 | // Dispose of any resources that can be recreated. 53 | } 54 | 55 | - (XLFormDescriptor *)settingForm { 56 | XLFormDescriptor *form = [XLFormDescriptor formDescriptorWithTitle:NSLocalizedStringFromTable(@"Setting Hackfoldr Page", @"Hackfoldr", @"Title of SettingView")]; 57 | 58 | XLFormSectionDescriptor *currentPageSection = [XLFormSectionDescriptor formSection]; 59 | currentPageSection.title = NSLocalizedStringFromTable(@"Current Hackfoldr", @"Hackfoldr", @"Section title of current hackfoldr"); 60 | NSString *key = [[NSUserDefaults standardUserDefaults] stringOfCurrentHackfoldrPage]; 61 | 62 | XLFormRowDescriptor *currentHackpageKey = [XLFormRowDescriptor formRowDescriptorWithTag:nil rowType:XLFormRowDescriptorTypeInfo]; 63 | currentHackpageKey.title = NSLocalizedStringFromTable(@"Current key", @"Hackfoldr", @"Current key title in SettingView."); 64 | currentHackpageKey.value = key; 65 | [currentPageSection addFormRow:currentHackpageKey]; 66 | 67 | XLFormRowDescriptor *currentHackpageDate = [XLFormRowDescriptor formRowDescriptorWithTag:nil rowType:XLFormRowDescriptorTypeDate]; 68 | HackfoldrHistory *currentHistory = [HackfoldrHistory MR_findFirstByAttribute:@"hackfoldrKey" withValue:key]; 69 | currentHackpageDate.title = NSLocalizedStringFromTable(@"Refresh At", @"Hackfoldr", @"Refresh date about current hackfoldr key in SettingView."); 70 | currentHackpageDate.value = currentHistory.refreshDate; 71 | currentHackpageDate.disabled = @YES; 72 | [currentPageSection addFormRow:currentHackpageDate]; 73 | 74 | [form addFormSection:currentPageSection]; 75 | 76 | XLFormSectionDescriptor *inputSection = [XLFormSectionDescriptor formSection]; 77 | XLFormRowDescriptor *addButton = [XLFormRowDescriptor formRowDescriptorWithTag:nil rowType:XLFormRowDescriptorTypeButton]; 78 | addButton.title = NSLocalizedStringFromTable(@"Add Hackfoldr Page", @"Hackfoldr", @"'Add Hackfoldr Page' button title"); 79 | __weak XLFormRowDescriptor *wab = addButton; 80 | addButton.action.formBlock = ^(XLFormRowDescriptor * _Nonnull sender) { 81 | void (^deselectCell)(void) = ^() { 82 | UITableViewCell *cell = [sender cellForFormController:self]; 83 | NSIndexPath *indexPathOfCell = [self.tableView indexPathForCell:cell]; 84 | [self.tableView deselectRowAtIndexPath:indexPathOfCell animated:YES]; 85 | }; 86 | 87 | __strong XLFormRowDescriptor *sab = wab; 88 | UIAlertController *ac = [UIAlertController alertControllerWithTitle:nil 89 | message:NSLocalizedStringFromTable(@"Select a way to input new key", @"Hackfoldr", @"Message for 'Add Hackfolr Page'") 90 | preferredStyle:UIAlertControllerStyleActionSheet]; 91 | [ac addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"Hackfoldr key or URL", @"Hackfoldr", @"Place holder string for input element in SettingView.") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 92 | // Show an alert with textField 93 | UIAlertController *inputAlert = [UIAlertController alertControllerWithTitle:sab.title 94 | message:nil 95 | preferredStyle:UIAlertControllerStyleAlert]; 96 | NSString *changeKey = NSLocalizedStringFromTable(@"Change Key", @"Hackfoldr", @"Change hackfoldr key button title in SettingView."); 97 | [inputAlert addAction:[UIAlertAction actionWithTitle:changeKey style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { 98 | if (inputAlert.textFields.count == 0) return; 99 | 100 | UITextField *textField = inputAlert.textFields.firstObject; 101 | BFTask *cleanKeyTask = [self validatorHackfoldrKeyForSettingViewWithHackfoldrKey:textField.text]; 102 | [cleanKeyTask continueWithBlock:^id(BFTask *task) { 103 | if (task.error) { 104 | self.updateHackfoldrPage(textField.text, task.error); 105 | return nil; 106 | } 107 | if (self.updateHackfoldrPage) { 108 | self.updateHackfoldrPage(task.result, nil); 109 | } 110 | return nil; 111 | }]; 112 | }]]; 113 | [inputAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) { 114 | textField.placeholder = action.title; 115 | }]; 116 | [self presentViewController:inputAlert animated:YES completion:deselectCell]; 117 | }]]; 118 | [ac addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"Scan QR Code", @"Hackfoldr", @"Scan QR code button title") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 119 | // Show scaner 120 | QRCodeViewController *qvc = [QRCodeViewController viewController]; 121 | qvc.foundedResult = ^(NSString *result) { 122 | [self.navigationController popViewControllerAnimated:YES]; 123 | 124 | BFTask *cleanKeyTask = [self validatorHackfoldrKeyForSettingViewWithHackfoldrKey:result]; 125 | [cleanKeyTask continueWithBlock:^id(BFTask *t) { 126 | if (t.error) { 127 | self.updateHackfoldrPage(t.result, t.error); 128 | return nil; 129 | } 130 | if (self.updateHackfoldrPage) { 131 | self.updateHackfoldrPage(t.result, nil); 132 | } 133 | return nil; 134 | }]; 135 | }; 136 | [self.navigationController pushViewController:qvc animated:YES]; 137 | deselectCell(); 138 | }]]; 139 | [ac addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"Cancel", @"Hackfoldr", @"Cancel button title of 'Add Hackfoldr Page'") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { 140 | deselectCell(); 141 | }]]; 142 | [self presentViewController:ac animated:YES completion:nil]; 143 | }; 144 | [inputSection addFormRow:addButton]; 145 | 146 | [form addFormSection:inputSection]; 147 | 148 | XLFormSectionDescriptor *historySection = [XLFormSectionDescriptor formSection]; 149 | historySection.title = NSLocalizedStringFromTable(@"History", @"Hackfoldr", @"History section title in SettingView"); 150 | historySection.footerTitle = NSLocalizedStringFromTable(@"You can select one cell to return to the past.", @"Hackfoldr", @"History section footer in SettingView"); 151 | NSArray *histories = [HackfoldrHistory MR_findAllSortedBy:@"refreshDate" ascending:NO]; 152 | [histories enumerateObjectsUsingBlock:^(HackfoldrHistory *history, NSUInteger idx, BOOL *stop) { 153 | XLFormRowDescriptor *historyButton = [XLFormRowDescriptor formRowDescriptorWithTag:nil rowType:XLFormRowDescriptorTypeButton]; 154 | historyButton.title = history.title; 155 | historyButton.action.formBlock = ^(XLFormRowDescriptor * _Nonnull sender) { 156 | if (self.updateHackfoldrPage) { 157 | self.updateHackfoldrPage(history.hackfoldrKey, nil); 158 | } 159 | }; 160 | [historySection addFormRow:historyButton]; 161 | }]; 162 | [form addFormSection:historySection]; 163 | 164 | XLFormSectionDescriptor *resetSection = [XLFormSectionDescriptor formSection]; 165 | resetSection.title = NSLocalizedStringFromTable(@"Reset Actions", @"Hackfoldr", @"Reset hackfoldr actions in SettingView"); 166 | resetSection.footerTitle = NSLocalizedStringFromTable(@"If you lost in app, just select it.", @"Hackfoldr", @"Reset section footer in SettingView"); 167 | XLFormRowDescriptor *restHackfoldrPageButton = [XLFormRowDescriptor formRowDescriptorWithTag:nil rowType:XLFormRowDescriptorTypeButton]; 168 | restHackfoldrPageButton.title = NSLocalizedStringFromTable(@"Hackfoldr Help", @"Hackfoldr", @"Reset hackfoldr page button title in SettingView"); 169 | restHackfoldrPageButton.action.formBlock = ^(XLFormRowDescriptor * _Nonnull sender) { 170 | // Set current HackfoldrPage to |DefaultHackfoldrPage| 171 | NSString *defaultHackfoldrKey = [[NSUserDefaults standardUserDefaults] stringOfDefaultHackfoldrPage]; 172 | if (self.updateHackfoldrPage) { 173 | self.updateHackfoldrPage(defaultHackfoldrKey, nil); 174 | } 175 | }; 176 | [resetSection addFormRow:restHackfoldrPageButton]; 177 | [form addFormSection:resetSection]; 178 | 179 | XLFormSectionDescriptor *infoSection = [XLFormSectionDescriptor formSection]; 180 | NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; 181 | NSString *build = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleVersionKey]; 182 | infoSection.footerTitle = [NSString stringWithFormat:@"App Version: %@ (%@)\nCreate by Superbil", version, build]; 183 | [form addFormSection:infoSection]; 184 | 185 | return form; 186 | } 187 | 188 | - (BFTask *)validatorHackfoldrKeyForSettingViewWithHackfoldrKey:(NSString *)newHackfoldrKey 189 | { 190 | BFTaskCompletionSource *completion = [BFTaskCompletionSource taskCompletionSource]; 191 | 192 | NSString *validatorKey = [NSURL validatorHackfoldrKey:newHackfoldrKey]; 193 | 194 | if (validatorKey && validatorKey.length > 0) { 195 | [completion setResult:validatorKey]; 196 | } else { 197 | [completion setError:[NSError errorWithDomain:@"SettingView" 198 | code:1 199 | userInfo:nil]]; 200 | } 201 | 202 | return completion.task; 203 | } 204 | 205 | - (void)shareAction { 206 | QRCodeViewController *qrvc = [QRCodeViewController viewController]; 207 | NSString *hackfoldrPageKey = [[NSUserDefaults standardUserDefaults] stringOfCurrentHackfoldrPage]; 208 | qrvc.qrCodeString = [@"https://hackfoldr.org" stringByAppendingPathComponent:hackfoldrPageKey]; 209 | [self.navigationController pushViewController:qrvc animated:YES]; 210 | } 211 | 212 | @end 213 | -------------------------------------------------------------------------------- /hackfoldr-iOS/ListFieldViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ListFieldViewController.m 3 | // hackfoldr-iOS 4 | // 5 | // Created by Superbil on 2014/8/31. 6 | // Copyright (c) 2014年 org.superbil. All rights reserved. 7 | // 8 | 9 | #import "ListFieldViewController.h" 10 | 11 | #import 12 | #import 13 | #import 14 | #import 15 | #import 16 | 17 | // Model & Client 18 | #import 19 | #import "HackfoldrClient.h" 20 | #import "HackfoldrHistory.h" 21 | #import "HackfoldrPage.h" 22 | // Category 23 | #import "HackfoldrClient+Store.h" 24 | #import "HackfoldrPage+CSSearchableItem.h" 25 | #import "NSURL+Hackfoldr.h" 26 | #import "NSUserDefaults+DefaultHackfoldrPage.h" 27 | #import "UIColor+Hackfoldr.h" 28 | #import "UIImage+TOWebViewControllerIcons.h" 29 | // ViewController 30 | #import "ListFieldViewController.h" 31 | #import "SettingViewController.h" 32 | #import "TOWebViewController+HackfoldrField.h" 33 | 34 | @interface ListFieldViewController () 35 | @property (nonatomic, strong) SettingViewController *settingsController; 36 | 37 | @property (nonatomic, strong) UIRefreshControl *refreshControl; 38 | @property (nonatomic, strong) UIView *refreshLoadingView; 39 | @property (nonatomic, strong) UIImage *hackfoldrImage; 40 | @property (nonatomic, strong) SVGImageView *g0vIcon; 41 | @property (assign) BOOL isRefreshAnimating; 42 | @property (assign) CGAffineTransform defaultIconTransform; 43 | @property (assign) CGFloat lastY; 44 | @end 45 | 46 | @implementation ListFieldViewController 47 | 48 | + (instancetype)viewController 49 | { 50 | return [[[self class] alloc] initWithNibName:nil bundle:nil]; 51 | } 52 | 53 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 54 | { 55 | if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { 56 | _hideBackButton = YES; 57 | } 58 | return self; 59 | } 60 | 61 | - (void)viewDidLoad 62 | { 63 | [super viewDidLoad]; 64 | 65 | self.treeView = [[RATreeView alloc] initWithFrame:CGRectZero style:RATreeViewStylePlain]; 66 | self.treeView.delegate = self; 67 | self.treeView.dataSource = self; 68 | self.treeView.backgroundColor = [UIColor whiteColor]; 69 | [self.view addSubview:self.treeView]; 70 | self.treeView.translatesAutoresizingMaskIntoConstraints = NO; 71 | NSLayoutConstraint *topC = [NSLayoutConstraint constraintWithItem:self.treeView 72 | attribute:NSLayoutAttributeTop 73 | relatedBy:NSLayoutRelationEqual 74 | toItem:self.view 75 | attribute:NSLayoutAttributeTop 76 | multiplier:1.0 77 | constant:0.0]; 78 | NSLayoutConstraint *leftC = [NSLayoutConstraint constraintWithItem:self.treeView 79 | attribute:NSLayoutAttributeLeft 80 | relatedBy:NSLayoutRelationEqual 81 | toItem:self.view 82 | attribute:NSLayoutAttributeLeft 83 | multiplier:1.0 84 | constant:0.0]; 85 | NSLayoutConstraint *rightC = [NSLayoutConstraint constraintWithItem:self.treeView 86 | attribute:NSLayoutAttributeRight 87 | relatedBy:NSLayoutRelationEqual 88 | toItem:self.view 89 | attribute:NSLayoutAttributeRight 90 | multiplier:1.0 91 | constant:0.0]; 92 | NSLayoutConstraint *bottomC = [NSLayoutConstraint constraintWithItem:self.treeView 93 | attribute:NSLayoutAttributeBottom 94 | relatedBy:NSLayoutRelationEqual 95 | toItem:self.view 96 | attribute:NSLayoutAttributeBottom 97 | multiplier:1.0 98 | constant:0.0]; 99 | [self.view addConstraints:@[topC, leftC, rightC, bottomC]]; 100 | 101 | self.settingButton = [UIButton buttonWithType:UIButtonTypeCustom]; 102 | [self.settingButton addTarget:self action:@selector(settingAction:) forControlEvents:UIControlEventTouchUpInside]; 103 | CGFloat iconSize = 24; 104 | [self.settingButton setImage: [[FAKFontAwesome cogIconWithSize:iconSize] imageWithSize:CGSizeMake(iconSize, iconSize)] forState:UIControlStateNormal]; 105 | self.settingButton.frame = CGRectMake(0, 0, iconSize, iconSize); 106 | self.settingButton.accessibilityLabel = @"Settings button"; 107 | 108 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.settingButton]; 109 | 110 | // Hide back button 111 | UIView *leftView = nil; 112 | if (self.hideBackButton) { 113 | leftView = [[UIView alloc] init]; 114 | } else { 115 | CGFloat iconSize = 24; 116 | UIImage *image = [[FAKFontAwesome chevronLeftIconWithSize:iconSize] imageWithSize:CGSizeMake(iconSize, iconSize)]; 117 | UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom]; 118 | [backButton addTarget:self action:@selector(backAction:) forControlEvents:UIControlEventTouchUpInside]; 119 | backButton.frame = CGRectMake(0, 0, iconSize, iconSize); 120 | backButton.accessibilityLabel = @"Back button"; 121 | [backButton setImage:image forState:UIControlStateNormal]; 122 | leftView = backButton; 123 | } 124 | self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:leftView]; 125 | 126 | if (@available(iOS 10.0, *)) { 127 | [self setupRefreshControl]; 128 | } 129 | } 130 | 131 | - (void)setupRefreshControl 132 | { 133 | self.refreshControl = [[UIRefreshControl alloc] init]; 134 | self.treeView.scrollView.refreshControl = self.refreshControl; 135 | 136 | self.refreshLoadingView = [[UIView alloc] initWithFrame:self.refreshControl.bounds]; 137 | self.refreshLoadingView.backgroundColor = [UIColor clearColor]; 138 | 139 | NSURL *g0vIconURL = [[NSBundle mainBundle] URLForResource:@"g0v" withExtension:@"svg"]; 140 | SVGImageView *icon = [[SVGImageView alloc] initWithContentsOfURL:g0vIconURL]; 141 | icon.frame = CGRectMake(0, 0, 24, 24); 142 | self.g0vIcon = icon; 143 | [self.refreshLoadingView addSubview:self.g0vIcon]; 144 | self.defaultIconTransform = self.g0vIcon.transform; 145 | 146 | // Clip so the graphics don't stick out 147 | self.refreshLoadingView.clipsToBounds = YES; 148 | 149 | self.refreshControl.tintColor = [UIColor clearColor]; 150 | 151 | [self.refreshControl addSubview:self.refreshLoadingView]; 152 | [self.refreshControl addTarget:self action:@selector(refreshAction:) forControlEvents:UIControlEventValueChanged]; 153 | 154 | self.treeView.scrollView.delegate = self; 155 | } 156 | 157 | - (void)viewWillAppear:(BOOL)animated 158 | { 159 | [super viewWillAppear:animated]; 160 | 161 | self.title = self.page.pageTitle; 162 | 163 | [self createSearchIndex]; 164 | } 165 | 166 | - (void)viewDidAppear:(BOOL)animated 167 | { 168 | [super viewDidAppear:animated]; 169 | 170 | if (self.page.key) { 171 | [[NSUserDefaults standardUserDefaults] setCurrentHackfoldrPage:self.page.key]; 172 | [[NSUserDefaults standardUserDefaults] synchronize]; 173 | } 174 | } 175 | 176 | - (void)showSettingViewController 177 | { 178 | // When Setting view is showed, don't show again 179 | for (SettingViewController *vc in self.navigationController.viewControllers) { 180 | if ([vc isKindOfClass:[SettingViewController class]]) { 181 | return; 182 | } 183 | } 184 | 185 | SettingViewController *dialogController = [[SettingViewController alloc] init]; 186 | self.settingsController = dialogController; 187 | dialogController.updateHackfoldrPage = ^void(NSString * _Nonnull pageKey, NSError * _Nullable error) { 188 | if (error) { 189 | [self.settingsController popoverPresentationController]; 190 | } else { 191 | [[self updateHackfoldrPageWithDialogController:self.settingsController key:pageKey] continueWithSuccessBlock:^id _Nullable(BFTask * _Nonnull t) { 192 | [self reloadPage]; 193 | 194 | [self.navigationController popViewControllerAnimated:YES]; 195 | return nil; 196 | }]; 197 | } 198 | }; 199 | 200 | if (self.navigationController) { 201 | [self.navigationController pushViewController:dialogController animated:YES]; 202 | } else { 203 | UINavigationController *controller = [[UINavigationController alloc] initWithRootViewController:dialogController]; 204 | [self presentViewController:controller animated:YES completion:nil]; 205 | } 206 | } 207 | 208 | - (BFTask *)updateHackfoldrPageWithDialogController:(SettingViewController *)dialogController key:(NSString *)hackfoldrKey 209 | { 210 | NSString *rediredKey = nil; 211 | // lookup |rediredKey| from core data 212 | HackfoldrHistory *history = [HackfoldrHistory MR_findFirstByAttribute:@"hackfoldrKey" withValue:hackfoldrKey]; 213 | if (history && history.rediredKey) { 214 | rediredKey = history.rediredKey; 215 | } 216 | 217 | HackfoldrTaskCompletionSource *completionSource = [[HackfoldrClient sharedClient] hackfoldrPageTaskWithKey:hackfoldrKey rediredKey:rediredKey]; 218 | 219 | NSString *title = NSLocalizedStringFromTable(@"Error", @"Hackfoldr", @"Error title"); 220 | NSString *dismissButtonTitle = NSLocalizedStringFromTable(@"Dismiss", @"Hackfoldr", @"Dismiss button title in SettingView"); 221 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleAlert]; 222 | 223 | [alert addAction:[UIAlertAction actionWithTitle:dismissButtonTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 224 | [alert dismissViewControllerAnimated:YES completion:nil]; 225 | }]]; 226 | 227 | [completionSource.task continueWithBlock:^id _Nullable(BFTask * _Nonnull t) { 228 | if (t.error) { 229 | [self presentViewController:alert animated:YES completion:nil]; 230 | } 231 | return nil; 232 | }]; 233 | 234 | [[completionSource.task continueWithBlock:^id(BFTask *task) { 235 | return task; 236 | }] continueWithSuccessBlock:^id(BFTask *task) { 237 | NSLog(@"change hackfoldr page to: %@", hackfoldrKey); 238 | // Just save |hackfoldrKey| to user defaults 239 | [[NSUserDefaults standardUserDefaults] setCurrentHackfoldrPage:hackfoldrKey]; 240 | [[NSUserDefaults standardUserDefaults] synchronize]; 241 | 242 | HackfoldrPage *page = task.result; 243 | self.page = page; 244 | return nil; 245 | }]; 246 | 247 | return completionSource.task; 248 | } 249 | 250 | - (void)settingAction:(id)sender 251 | { 252 | [self showSettingViewController]; 253 | } 254 | 255 | - (void)backAction:(id)sender 256 | { 257 | [self.navigationController popViewControllerAnimated:YES]; 258 | } 259 | 260 | - (void)refreshAction:(id)sender 261 | { 262 | if (!self.page.key) return; 263 | 264 | [[self updateHackfoldrPageWithDialogController:self.settingsController key:self.page.key] continueWithExecutor:[BFExecutor mainThreadExecutor] withBlock:^id _Nullable(BFTask * _Nonnull t) { 265 | [self.refreshControl endRefreshing]; 266 | 267 | if (t.error) { 268 | NSLog(@"refresh: %@", t.error); 269 | return nil; 270 | } 271 | 272 | HackfoldrPage *page = t.result; 273 | self.page = page; 274 | [self reloadPage]; 275 | return nil; 276 | }]; 277 | } 278 | 279 | - (void)updateHackfoldrPageWithKey:(NSString *)hackfoldrKey 280 | { 281 | [[self updateHackfoldrPageWithDialogController:self.settingsController key:hackfoldrKey] continueWithExecutor:[BFExecutor mainThreadExecutor] withBlock:^id _Nullable(BFTask * _Nonnull t) { 282 | [self reloadPage]; 283 | return nil; 284 | }]; 285 | } 286 | 287 | - (void)reloadPage { 288 | if (!self.page) return; 289 | 290 | [self.treeView reloadData]; 291 | 292 | // Just a little delay to workaround UI display 293 | [[BFTask taskWithDelay:100] continueWithExecutor:[BFExecutor mainThreadExecutor] withBlock:^id _Nullable(BFTask * _Nonnull t) { 294 | // Expand field cells 295 | for (HackfoldrField *f in self.page.cells) { 296 | if (f.actions.length > 0) { 297 | // Always expand when |f.actions| had expand 298 | if ([f.actions rangeOfString:@"expand"].location != NSNotFound) { 299 | [self.treeView expandRowForItem:f]; 300 | continue; 301 | } 302 | // Always collapse when |f.actions| had collapse 303 | else if ([f.actions rangeOfString:@"collapse"].location != NSNotFound) { 304 | // Do nothing 305 | continue; 306 | } 307 | } 308 | 309 | // Auto expand when |f.subFields| has less than 3 fields 310 | if (f.subFields.count > 0 && f.subFields.count <= 3) { 311 | [self.treeView expandRowForItem:f]; 312 | } 313 | } 314 | return nil; 315 | }]; 316 | } 317 | 318 | - (void)createSearchIndex { 319 | NSMutableArray *items = [NSMutableArray array]; 320 | NSArray *histories = [HackfoldrHistory MR_findAllSortedBy:@"refreshDate" ascending:NO]; 321 | [histories enumerateObjectsUsingBlock:^(HackfoldrHistory * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 322 | CSSearchableItem *item = [self.page searchableItemWithDomain:NSStringFromClass(self.class)]; 323 | [items addObject:item]; 324 | }]; 325 | 326 | [CSSearchableIndex.defaultSearchableIndex indexSearchableItems:items completionHandler: ^(NSError * __nullable error) { 327 | if (error) { 328 | NSLog(@"Indexed failed %@", error); 329 | return; 330 | } 331 | NSLog(@"create search indexed"); 332 | }]; 333 | self.userActivity.eligibleForSearch = YES; 334 | self.userActivity.eligibleForPublicIndexing = YES; 335 | self.userActivity.eligibleForHandoff = YES; 336 | 337 | [self updateUserActivityState:self.userActivity]; 338 | } 339 | 340 | - (UIImage *)folderImageWithField:(HackfoldrField *)field 341 | { 342 | if (field.actions && [field.actions rangeOfString:@"expand"].location != NSNotFound) { 343 | return [self folderImageWithisExpand:YES]; 344 | } 345 | return [self folderImageWithisExpand:NO]; 346 | } 347 | 348 | - (UIImage *)folderImageWithisExpand:(BOOL)isExpand 349 | { 350 | CGFloat iconSize = 24; 351 | UIImage *foldrImage; 352 | if (isExpand) { 353 | foldrImage = [[FAKFontAwesome folderOpenIconWithSize:iconSize] imageWithSize:CGSizeMake(iconSize, iconSize)]; 354 | } else { 355 | foldrImage = [[FAKFontAwesome folderIconWithSize:iconSize] imageWithSize:CGSizeMake(iconSize, iconSize)]; 356 | } 357 | return foldrImage; 358 | } 359 | 360 | - (UIImage *)hackfoldrImage { 361 | if (_hackfoldrImage) return _hackfoldrImage; 362 | 363 | _hackfoldrImage = [[UIImage imageNamed:@"hackfoldr-icon-small"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 364 | return _hackfoldrImage; 365 | } 366 | 367 | - (void)animateRefreshView 368 | { 369 | self.isRefreshAnimating = YES; 370 | 371 | [UIView animateWithDuration:0.3 372 | delay:0 373 | options:UIViewAnimationOptionCurveLinear 374 | animations:^{ 375 | // Rotate the spinner by M_PI_2 = PI/2 = 90 degrees 376 | [self.g0vIcon setTransform:CGAffineTransformRotate(self.g0vIcon.transform, M_PI_2)]; 377 | } 378 | completion:^(BOOL finished) { 379 | // If still refreshing, keep spinning, else reset 380 | if (self.refreshControl.isRefreshing) { 381 | [self animateRefreshView]; 382 | } else { 383 | self.isRefreshAnimating = NO; 384 | self.g0vIcon.transform = self.defaultIconTransform; 385 | } 386 | }]; 387 | } 388 | 389 | #pragma mark - UIScrollViewDelegate 390 | 391 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView 392 | { 393 | // Get the current size of the refresh controller 394 | CGRect refreshBounds = self.refreshControl.bounds; 395 | 396 | // Distance the table has been pulled >= 0 397 | CGFloat pullDistance = MAX(0.0, -self.refreshControl.frame.origin.y); 398 | 399 | // Half the width of the table 400 | CGFloat midX = self.treeView.frame.size.width / 2.0; 401 | 402 | // Calculate the width and height of our graphics 403 | CGFloat iconHeight = self.g0vIcon.bounds.size.height; 404 | CGFloat iconHeightHalf = iconHeight / 2.0; 405 | 406 | CGFloat iconWidth = self.g0vIcon.bounds.size.width; 407 | CGFloat iconWidthHalf = iconWidth / 2.0; 408 | 409 | CGFloat maxPullRange = 100.f; 410 | 411 | // Calculate the pull ratio, between 0.0-1.0 412 | CGFloat pullRatio = MIN( MAX(pullDistance, 0.0), maxPullRange) / maxPullRange; 413 | 414 | // Set the Y coord of the graphics, based on pull distance 415 | CGFloat iconY = pullDistance / 2.0 - iconHeightHalf; 416 | 417 | // Use center to move icon 418 | self.g0vIcon.center = CGPointMake(midX - iconWidthHalf, iconY); 419 | 420 | // Set the encompassing view's frames 421 | refreshBounds.size.height = pullDistance; 422 | 423 | self.refreshLoadingView.frame = refreshBounds; 424 | 425 | if (pullRatio > 0.f) { 426 | CGFloat angle = (-(CGRectGetMaxY(self.refreshControl.frame) - self.lastY) / maxPullRange * 11.25); 427 | self.g0vIcon.transform = CGAffineTransformRotate(self.g0vIcon.transform, angle); 428 | } 429 | self.lastY = CGRectGetMaxY(self.refreshControl.frame); 430 | 431 | // If we're refreshing and the animation is not playing, then play the animation 432 | if (self.refreshControl.isRefreshing && !self.isRefreshAnimating) { 433 | [self animateRefreshView]; 434 | } 435 | 436 | NSLog(@"y: %.2f, pullDistance: %.1f, pullRatio: %.3f, iconY: %.2f, isRefreshing: %@", 437 | self.refreshControl.frame.origin.y, 438 | pullDistance, 439 | pullRatio, 440 | iconY, 441 | self.refreshControl.isRefreshing ? @"YES" : @"NO"); 442 | } 443 | 444 | #pragma mark - RATreeViewDelegate 445 | 446 | - (void)treeView:(RATreeView *)treeView didSelectRowForItem:(id)item 447 | { 448 | HackfoldrField *rowOfField = item; 449 | if (!rowOfField.isSubItem) { 450 | return; 451 | } 452 | 453 | NSString *urlString = rowOfField.urlString; 454 | NSLog(@"url: %@", urlString); 455 | 456 | if (!urlString || urlString.length == 0) { 457 | // TODO: show nil message 458 | return; 459 | } 460 | 461 | NSURL *targetURL = [NSURL URLWithString:urlString]; 462 | 463 | if ([NSURL canHandleHackfoldrURL:targetURL]) { 464 | // Redirect to |urlString| 465 | NSRange range = [urlString rangeOfString:@"://"]; 466 | if (range.location != NSNotFound) { 467 | NSString *realKey = [NSURL realKeyOfHackfoldrWithURL:targetURL]; 468 | if (!realKey) return; 469 | 470 | [[[HackfoldrClient sharedClient] hackfoldrPageTaskWithKey:realKey rediredKey:nil].task continueWithSuccessBlock:^id _Nullable(BFTask * _Nonnull t) { 471 | HackfoldrPage *page = t.result; 472 | 473 | [[NSUserDefaults standardUserDefaults] setCurrentHackfoldrPage:realKey]; 474 | 475 | ListFieldViewController *lvc = [ListFieldViewController viewController]; 476 | lvc.hideBackButton = NO; 477 | lvc.page = page; 478 | [self.navigationController pushViewController:lvc animated:YES]; 479 | [lvc reloadPage]; 480 | return nil; 481 | }]; 482 | [treeView deselectRowForItem:item animated:YES]; 483 | return; 484 | } 485 | } 486 | 487 | if (NSClassFromString(@"SFSafariViewController")) { 488 | SFSafariViewController *svc = [[SFSafariViewController alloc] initWithURL:targetURL]; 489 | svc.title = rowOfField.name; 490 | [self presentViewController:svc animated:YES completion:nil]; 491 | } else { 492 | TOWebViewController *webViewController = [[TOWebViewController alloc] init]; 493 | webViewController.showPageTitles = YES; 494 | [webViewController loadWithField:rowOfField]; 495 | 496 | [self.navigationController pushViewController:webViewController animated:YES]; 497 | } 498 | [treeView deselectRowForItem:item animated:YES]; 499 | } 500 | 501 | - (BOOL)treeView:(RATreeView *)treeView shouldExpandRowForItem:(id)item 502 | { 503 | if (item) { 504 | NSInteger subIndex = [self.page.cells indexOfObject:item]; 505 | return (subIndex != NSNotFound); 506 | } 507 | return YES; 508 | } 509 | 510 | - (void)treeView:(RATreeView *)treeView willExpandRowForItem:(id)item 511 | { 512 | UITableViewCell *cell = [treeView cellForItem:item]; 513 | cell.imageView.image = [self folderImageWithisExpand:YES]; 514 | } 515 | 516 | - (void)treeView:(RATreeView *)treeView willCollapseRowForItem:(id)item 517 | { 518 | UITableViewCell *cell = [treeView cellForItem:item]; 519 | cell.imageView.image = [self folderImageWithisExpand:NO]; 520 | } 521 | 522 | #pragma mark - RATreeViewDataSource 523 | 524 | - (NSInteger)treeView:(RATreeView *)treeView numberOfChildrenOfItem:(nullable id)item; 525 | { 526 | if (item) { 527 | NSInteger subIndex = [self.page.cells indexOfObject:item]; 528 | HackfoldrField *subField = self.page.cells[subIndex]; 529 | return subField.subFields.count; 530 | } 531 | 532 | // Root items 533 | NSUInteger sum = 0; 534 | for (HackfoldrField *f in self.page.cells) { 535 | if (f.index == 0) { 536 | sum += f.subFields.count; 537 | } else if (!f.isSubItem) { 538 | sum += 1; 539 | } 540 | } 541 | return sum; 542 | } 543 | 544 | - (id)treeView:(RATreeView *)treeView child:(NSInteger)index ofItem:(nullable id)item 545 | { 546 | if (item) { 547 | HackfoldrField *field = item; 548 | return field.subFields[index]; 549 | } 550 | 551 | HackfoldrField *rootField = nil; 552 | for (HackfoldrField *f in self.page.cells) { 553 | if (f.index == 0) { 554 | rootField = f; 555 | } else { 556 | break; 557 | } 558 | } 559 | if (rootField) { 560 | if (index < rootField.subFields.count) { 561 | return rootField.subFields[index]; 562 | } 563 | NSInteger realIndex = index - (rootField.subFields.count - 1); 564 | if (rootField && rootField.subFields.count > 0 && realIndex == 0) { 565 | realIndex += 1; 566 | } 567 | return self.page.cells[realIndex]; 568 | } 569 | return self.page.cells[index]; 570 | } 571 | 572 | - (UITableViewCell *)treeView:(RATreeView *)treeView cellForItem:(nullable id)item; 573 | { 574 | HackfoldrField *field = item; 575 | NSString *cellName = field.isSubItem ? @"detail" : @"folder"; 576 | NSString *identifier = [cellName stringByAppendingString:@"Cell"]; 577 | UITableViewCell *cell = [treeView dequeueReusableCellWithIdentifier:identifier]; 578 | if (!cell) { 579 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifier]; 580 | } 581 | 582 | // Default color is white 583 | cell.backgroundColor = [UIColor whiteColor]; 584 | cell.detailTextLabel.backgroundColor = [UIColor whiteColor]; 585 | 586 | CGFloat fontSize = [UIFont systemFontSize]; 587 | 588 | if (field.isSubItem) { 589 | UIImage *iconImage = nil; 590 | CGFloat iconSize = [UIFont systemFontSize] + 2.f; 591 | 592 | if ([NSURL canHandleHackfoldrURL:[NSURL URLWithString:field.urlString]]) { 593 | iconImage = self.hackfoldrImage; 594 | } else if (field.actions.length > 0 && [field.actions rangeOfString:@"fa-"].location != NSNotFound) { 595 | // Custom icon 596 | NSString *iconName = nil; 597 | NSArray *actions = [field.actions componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@","]]; 598 | for (NSString *s in actions) { 599 | if ([s rangeOfString:@"fa-"].location != NSNotFound) { 600 | iconName = [s stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 601 | break; 602 | } 603 | } 604 | 605 | NSError *error = nil; 606 | FAKFontAwesome *fa = [FAKFontAwesome iconWithIdentifier:iconName size:iconSize error:&error]; 607 | if (!error) { 608 | iconImage = [fa imageWithSize:CGSizeMake(iconSize, iconSize)]; 609 | } else { 610 | NSLog(@"FontAwsome %@, %@", iconName, error); 611 | } 612 | } 613 | 614 | if (iconImage) { 615 | UIImageView *iconView = [[UIImageView alloc] initWithImage:iconImage]; 616 | iconView.frame = CGRectMake(0, 0, iconSize, iconSize); 617 | iconView.tintColor = [UIColor hackfoldrGreenColor]; 618 | cell.accessoryView = iconView; 619 | } else { 620 | cell.accessoryType = field.urlString.length > 0 ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone; 621 | cell.accessoryView = nil; 622 | } 623 | 624 | // Only setup when |field.labelString| have value 625 | NSString *cleanLabelString = [field.labelString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 626 | if (cleanLabelString.length > 0) { 627 | cell.detailTextLabel.text = [NSString stringWithFormat:@" %@ ", field.labelString]; 628 | cell.detailTextLabel.textColor = [UIColor whiteColor]; 629 | cell.detailTextLabel.backgroundColor = field.labelColor; 630 | [cell.detailTextLabel.layer setCornerRadius:3.f]; 631 | [cell.detailTextLabel.layer setMasksToBounds:YES]; 632 | } else { 633 | cell.detailTextLabel.text = nil; 634 | } 635 | } else { 636 | cell.detailTextLabel.text = nil; 637 | fontSize += 4.f; 638 | 639 | cell.imageView.image = [self folderImageWithField:field]; 640 | } 641 | 642 | cell.textLabel.text = field.name; 643 | cell.textLabel.font = [UIFont systemFontOfSize:fontSize]; 644 | 645 | NSLog(@"field:%@", field); 646 | 647 | return cell; 648 | } 649 | 650 | @end 651 | -------------------------------------------------------------------------------- /hackfoldr-iOS.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3AB4D0070023B1FA82E42FAE /* libPods-hackfoldr-iOSTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 893ECEAB2C1FEB82EE9E1ED6 /* libPods-hackfoldr-iOSTests.a */; }; 11 | 882E652A19B2D5920054E522 /* ListFieldViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 882E652919B2D5920054E522 /* ListFieldViewController.m */; }; 12 | 886F4A471E0536AA00AF14DE /* NSURL+Hackfoldr.m in Sources */ = {isa = PBXBuildFile; fileRef = 886F4A461E0536AA00AF14DE /* NSURL+Hackfoldr.m */; }; 13 | 88AC6BEA1F6BD13000E082E2 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 88AC6BE91F6BD13000E082E2 /* LaunchScreen.storyboard */; }; 14 | D7BD8128463F908AE0D361C0 /* libPods-hackfoldr-iOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E2BE931B1EC9D9EDF8673F5C /* libPods-hackfoldr-iOS.a */; }; 15 | E70F906D1A77F72200BF4B25 /* NSUserDefaults+DefaultHackfoldrPage.m in Sources */ = {isa = PBXBuildFile; fileRef = E70F906C1A77F72200BF4B25 /* NSUserDefaults+DefaultHackfoldrPage.m */; }; 16 | E71D192A1956CE5500B57207 /* HackfoldrClient.m in Sources */ = {isa = PBXBuildFile; fileRef = E71D19291956CE5500B57207 /* HackfoldrClient.m */; }; 17 | E71D192D1956CFB600B57207 /* HackfoldrField.m in Sources */ = {isa = PBXBuildFile; fileRef = E71D192C1956CFB600B57207 /* HackfoldrField.m */; }; 18 | E755907A1AEAB595007337B1 /* Hackfoldr.strings in Resources */ = {isa = PBXBuildFile; fileRef = E75590781AEAB595007337B1 /* Hackfoldr.strings */; }; 19 | E77B93D11FAB1F4800BD8130 /* HackfoldrPage+CSSearchableItem.m in Sources */ = {isa = PBXBuildFile; fileRef = E77B93D01FAB1F4800BD8130 /* HackfoldrPage+CSSearchableItem.m */; }; 20 | E77B93D41FAB29EC00BD8130 /* SettingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E77B93D31FAB29EC00BD8130 /* SettingViewController.m */; }; 21 | E79424AE1ACEEDCF00558AE9 /* HackoldrModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = E79424AC1ACEEDCF00558AE9 /* HackoldrModel.xcdatamodeld */; }; 22 | E79424B11ACEEF6400558AE9 /* HackfoldrHistory.m in Sources */ = {isa = PBXBuildFile; fileRef = E79424B01ACEEF6400558AE9 /* HackfoldrHistory.m */; }; 23 | E79424B31ACF015300558AE9 /* sample.csv.json in Resources */ = {isa = PBXBuildFile; fileRef = E79424B21ACF015300558AE9 /* sample.csv.json */; }; 24 | E79937391FC4C00C00DAC153 /* QRCodeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E79937381FC4C00C00DAC153 /* QRCodeViewController.m */; }; 25 | E799373B1FC4C71000DAC153 /* QRCode.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E799373A1FC4C71000DAC153 /* QRCode.storyboard */; }; 26 | E79B0E771E7AE43F005BE38C /* UIColor+Hackfoldr.m in Sources */ = {isa = PBXBuildFile; fileRef = E79B0E761E7AE43F005BE38C /* UIColor+Hackfoldr.m */; }; 27 | E79B0E7A1E7B1714005BE38C /* HackfoldrClient+Store.m in Sources */ = {isa = PBXBuildFile; fileRef = E79B0E791E7B1714005BE38C /* HackfoldrClient+Store.m */; }; 28 | E79B0E7E1E7C030E005BE38C /* g0v.svg in Resources */ = {isa = PBXBuildFile; fileRef = E79B0E7D1E7C030E005BE38C /* g0v.svg */; }; 29 | E7B1DD8A195AEE7D00768FFA /* TOWebViewController+HackfoldrField.m in Sources */ = {isa = PBXBuildFile; fileRef = E7B1DD89195AEE7D00768FFA /* TOWebViewController+HackfoldrField.m */; }; 30 | E7EB66BC19554E290069FD71 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7EB66BB19554E290069FD71 /* Foundation.framework */; }; 31 | E7EB66BE19554E290069FD71 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7EB66BD19554E290069FD71 /* CoreGraphics.framework */; }; 32 | E7EB66C019554E290069FD71 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7EB66BF19554E290069FD71 /* UIKit.framework */; }; 33 | E7EB66C619554E290069FD71 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E7EB66C419554E290069FD71 /* InfoPlist.strings */; }; 34 | E7EB66C819554E290069FD71 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E7EB66C719554E290069FD71 /* main.m */; }; 35 | E7EB66CC19554E290069FD71 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E7EB66CB19554E290069FD71 /* AppDelegate.m */; }; 36 | E7EB66D519554E290069FD71 /* MainViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E7EB66D419554E290069FD71 /* MainViewController.m */; }; 37 | E7EB66D719554E290069FD71 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E7EB66D619554E290069FD71 /* Images.xcassets */; }; 38 | E7EB66DE19554E290069FD71 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7EB66DD19554E290069FD71 /* XCTest.framework */; }; 39 | E7EB66DF19554E290069FD71 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7EB66BB19554E290069FD71 /* Foundation.framework */; }; 40 | E7EB66E019554E290069FD71 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7EB66BF19554E290069FD71 /* UIKit.framework */; }; 41 | E7EB66E819554E290069FD71 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E7EB66E619554E290069FD71 /* InfoPlist.strings */; }; 42 | E7EB66EA19554E290069FD71 /* hackfoldr_iOSTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E7EB66E919554E290069FD71 /* hackfoldr_iOSTests.m */; }; 43 | E7F7482A1955878C00262A34 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7F748291955878C00262A34 /* QuartzCore.framework */; }; 44 | E7F9558719570D4E0098661E /* HackfoldrPage.m in Sources */ = {isa = PBXBuildFile; fileRef = E7F9558619570D4E0098661E /* HackfoldrPage.m */; }; 45 | /* End PBXBuildFile section */ 46 | 47 | /* Begin PBXContainerItemProxy section */ 48 | E7EB66E119554E290069FD71 /* PBXContainerItemProxy */ = { 49 | isa = PBXContainerItemProxy; 50 | containerPortal = E7EB66B019554E290069FD71 /* Project object */; 51 | proxyType = 1; 52 | remoteGlobalIDString = E7EB66B719554E290069FD71; 53 | remoteInfo = "hackfoldr-iOS"; 54 | }; 55 | /* End PBXContainerItemProxy section */ 56 | 57 | /* Begin PBXFileReference section */ 58 | 0FFCE6D7B8522BC376B03A51 /* Pods-hackfoldr-iOSTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-hackfoldr-iOSTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-hackfoldr-iOSTests/Pods-hackfoldr-iOSTests.release.xcconfig"; sourceTree = ""; }; 59 | 70B8275F56498EFBD8F1410F /* Pods-hackfoldr-iOSTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-hackfoldr-iOSTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-hackfoldr-iOSTests/Pods-hackfoldr-iOSTests.debug.xcconfig"; sourceTree = ""; }; 60 | 882E652819B2D5920054E522 /* ListFieldViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ListFieldViewController.h; path = "hackfoldr-iOS/ListFieldViewController.h"; sourceTree = ""; }; 61 | 882E652919B2D5920054E522 /* ListFieldViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ListFieldViewController.m; path = "hackfoldr-iOS/ListFieldViewController.m"; sourceTree = ""; }; 62 | 886F4A451E0536AA00AF14DE /* NSURL+Hackfoldr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSURL+Hackfoldr.h"; path = "hackfoldr-iOS/NSURL+Hackfoldr.h"; sourceTree = ""; }; 63 | 886F4A461E0536AA00AF14DE /* NSURL+Hackfoldr.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSURL+Hackfoldr.m"; path = "hackfoldr-iOS/NSURL+Hackfoldr.m"; sourceTree = ""; }; 64 | 889DFBC61D28EE740066ECA2 /* hackfoldr-iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "hackfoldr-iOS.entitlements"; sourceTree = ""; }; 65 | 88AC6BE91F6BD13000E082E2 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 66 | 893ECEAB2C1FEB82EE9E1ED6 /* libPods-hackfoldr-iOSTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-hackfoldr-iOSTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 67 | D6F8B8E84166B0CC5D012432 /* Pods-hackfoldr-iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-hackfoldr-iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-hackfoldr-iOS/Pods-hackfoldr-iOS.debug.xcconfig"; sourceTree = ""; }; 68 | E2BE931B1EC9D9EDF8673F5C /* libPods-hackfoldr-iOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-hackfoldr-iOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 69 | E70F906B1A77F72200BF4B25 /* NSUserDefaults+DefaultHackfoldrPage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSUserDefaults+DefaultHackfoldrPage.h"; path = "hackfoldr-iOS/NSUserDefaults+DefaultHackfoldrPage.h"; sourceTree = ""; }; 70 | E70F906C1A77F72200BF4B25 /* NSUserDefaults+DefaultHackfoldrPage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSUserDefaults+DefaultHackfoldrPage.m"; path = "hackfoldr-iOS/NSUserDefaults+DefaultHackfoldrPage.m"; sourceTree = ""; }; 71 | E71D19281956CE5500B57207 /* HackfoldrClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HackfoldrClient.h; path = "hackfoldr-iOS/HackfoldrClient.h"; sourceTree = ""; }; 72 | E71D19291956CE5500B57207 /* HackfoldrClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HackfoldrClient.m; path = "hackfoldr-iOS/HackfoldrClient.m"; sourceTree = ""; }; 73 | E71D192B1956CFB600B57207 /* HackfoldrField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HackfoldrField.h; path = "hackfoldr-iOS/HackfoldrField.h"; sourceTree = ""; }; 74 | E71D192C1956CFB600B57207 /* HackfoldrField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HackfoldrField.m; path = "hackfoldr-iOS/HackfoldrField.m"; sourceTree = ""; }; 75 | E75590791AEAB595007337B1 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Hackfoldr.strings"; sourceTree = ""; }; 76 | E755907B1AEAB5A0007337B1 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Hackfoldr.strings; sourceTree = ""; }; 77 | E77B93CF1FAB1F4800BD8130 /* HackfoldrPage+CSSearchableItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "HackfoldrPage+CSSearchableItem.h"; path = "hackfoldr-iOS/HackfoldrPage+CSSearchableItem.h"; sourceTree = ""; }; 78 | E77B93D01FAB1F4800BD8130 /* HackfoldrPage+CSSearchableItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "HackfoldrPage+CSSearchableItem.m"; path = "hackfoldr-iOS/HackfoldrPage+CSSearchableItem.m"; sourceTree = ""; }; 79 | E77B93D21FAB29EC00BD8130 /* SettingViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SettingViewController.h; path = "hackfoldr-iOS/SettingViewController.h"; sourceTree = ""; }; 80 | E77B93D31FAB29EC00BD8130 /* SettingViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SettingViewController.m; path = "hackfoldr-iOS/SettingViewController.m"; sourceTree = ""; }; 81 | E79424AD1ACEEDCF00558AE9 /* HackoldrModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = HackoldrModel.xcdatamodel; sourceTree = ""; }; 82 | E79424AF1ACEEF6400558AE9 /* HackfoldrHistory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HackfoldrHistory.h; path = "hackfoldr-iOS/HackfoldrHistory.h"; sourceTree = ""; }; 83 | E79424B01ACEEF6400558AE9 /* HackfoldrHistory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HackfoldrHistory.m; path = "hackfoldr-iOS/HackfoldrHistory.m"; sourceTree = ""; }; 84 | E79424B21ACF015300558AE9 /* sample.csv.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = sample.csv.json; path = TestData/sample.csv.json; sourceTree = ""; }; 85 | E79937371FC4C00C00DAC153 /* QRCodeViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = QRCodeViewController.h; path = "hackfoldr-iOS/QRCodeViewController.h"; sourceTree = ""; }; 86 | E79937381FC4C00C00DAC153 /* QRCodeViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = QRCodeViewController.m; path = "hackfoldr-iOS/QRCodeViewController.m"; sourceTree = ""; }; 87 | E799373A1FC4C71000DAC153 /* QRCode.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = QRCode.storyboard; path = "hackfoldr-iOS/QRCode.storyboard"; sourceTree = ""; }; 88 | E79B0E751E7AE43F005BE38C /* UIColor+Hackfoldr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIColor+Hackfoldr.h"; path = "hackfoldr-iOS/UIColor+Hackfoldr.h"; sourceTree = ""; }; 89 | E79B0E761E7AE43F005BE38C /* UIColor+Hackfoldr.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIColor+Hackfoldr.m"; path = "hackfoldr-iOS/UIColor+Hackfoldr.m"; sourceTree = ""; }; 90 | E79B0E781E7B1714005BE38C /* HackfoldrClient+Store.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "HackfoldrClient+Store.h"; path = "hackfoldr-iOS/HackfoldrClient+Store.h"; sourceTree = ""; }; 91 | E79B0E791E7B1714005BE38C /* HackfoldrClient+Store.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "HackfoldrClient+Store.m"; path = "hackfoldr-iOS/HackfoldrClient+Store.m"; sourceTree = ""; }; 92 | E79B0E7D1E7C030E005BE38C /* g0v.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = g0v.svg; sourceTree = ""; }; 93 | E7A644D31FCEC12D00D28263 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/InfoPlist.strings"; sourceTree = ""; }; 94 | E7B1DD88195AEE7D00768FFA /* TOWebViewController+HackfoldrField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "TOWebViewController+HackfoldrField.h"; path = "hackfoldr-iOS/TOWebViewController+HackfoldrField.h"; sourceTree = ""; }; 95 | E7B1DD89195AEE7D00768FFA /* TOWebViewController+HackfoldrField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "TOWebViewController+HackfoldrField.m"; path = "hackfoldr-iOS/TOWebViewController+HackfoldrField.m"; sourceTree = ""; }; 96 | E7EB66B819554E290069FD71 /* hackfoldr-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "hackfoldr-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 97 | E7EB66BB19554E290069FD71 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 98 | E7EB66BD19554E290069FD71 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 99 | E7EB66BF19554E290069FD71 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 100 | E7EB66C319554E290069FD71 /* hackfoldr-iOS-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "hackfoldr-iOS-Info.plist"; sourceTree = ""; }; 101 | E7EB66C519554E290069FD71 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 102 | E7EB66C719554E290069FD71 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 103 | E7EB66C919554E290069FD71 /* hackfoldr-iOS-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "hackfoldr-iOS-Prefix.pch"; sourceTree = ""; }; 104 | E7EB66CA19554E290069FD71 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 105 | E7EB66CB19554E290069FD71 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 106 | E7EB66D319554E290069FD71 /* MainViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MainViewController.h; path = "hackfoldr-iOS/MainViewController.h"; sourceTree = ""; }; 107 | E7EB66D419554E290069FD71 /* MainViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MainViewController.m; path = "hackfoldr-iOS/MainViewController.m"; sourceTree = ""; }; 108 | E7EB66D619554E290069FD71 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 109 | E7EB66DC19554E290069FD71 /* hackfoldr-iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "hackfoldr-iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 110 | E7EB66DD19554E290069FD71 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 111 | E7EB66E519554E290069FD71 /* hackfoldr-iOSTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "hackfoldr-iOSTests-Info.plist"; sourceTree = ""; }; 112 | E7EB66E719554E290069FD71 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 113 | E7EB66E919554E290069FD71 /* hackfoldr_iOSTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = hackfoldr_iOSTests.m; sourceTree = ""; }; 114 | E7F748291955878C00262A34 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 115 | E7F9558519570D4E0098661E /* HackfoldrPage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HackfoldrPage.h; path = "hackfoldr-iOS/HackfoldrPage.h"; sourceTree = ""; }; 116 | E7F9558619570D4E0098661E /* HackfoldrPage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HackfoldrPage.m; path = "hackfoldr-iOS/HackfoldrPage.m"; sourceTree = ""; }; 117 | F15476DD2E850E7A93F2DF39 /* Pods-hackfoldr-iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-hackfoldr-iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-hackfoldr-iOS/Pods-hackfoldr-iOS.release.xcconfig"; sourceTree = ""; }; 118 | /* End PBXFileReference section */ 119 | 120 | /* Begin PBXFrameworksBuildPhase section */ 121 | E7EB66B519554E290069FD71 /* Frameworks */ = { 122 | isa = PBXFrameworksBuildPhase; 123 | buildActionMask = 2147483647; 124 | files = ( 125 | E7F7482A1955878C00262A34 /* QuartzCore.framework in Frameworks */, 126 | E7EB66BE19554E290069FD71 /* CoreGraphics.framework in Frameworks */, 127 | E7EB66C019554E290069FD71 /* UIKit.framework in Frameworks */, 128 | E7EB66BC19554E290069FD71 /* Foundation.framework in Frameworks */, 129 | D7BD8128463F908AE0D361C0 /* libPods-hackfoldr-iOS.a in Frameworks */, 130 | ); 131 | runOnlyForDeploymentPostprocessing = 0; 132 | }; 133 | E7EB66D919554E290069FD71 /* Frameworks */ = { 134 | isa = PBXFrameworksBuildPhase; 135 | buildActionMask = 2147483647; 136 | files = ( 137 | E7EB66DE19554E290069FD71 /* XCTest.framework in Frameworks */, 138 | E7EB66E019554E290069FD71 /* UIKit.framework in Frameworks */, 139 | E7EB66DF19554E290069FD71 /* Foundation.framework in Frameworks */, 140 | 3AB4D0070023B1FA82E42FAE /* libPods-hackfoldr-iOSTests.a in Frameworks */, 141 | ); 142 | runOnlyForDeploymentPostprocessing = 0; 143 | }; 144 | /* End PBXFrameworksBuildPhase section */ 145 | 146 | /* Begin PBXGroup section */ 147 | 53725B8A749867B92203B128 /* Pods */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | D6F8B8E84166B0CC5D012432 /* Pods-hackfoldr-iOS.debug.xcconfig */, 151 | F15476DD2E850E7A93F2DF39 /* Pods-hackfoldr-iOS.release.xcconfig */, 152 | 70B8275F56498EFBD8F1410F /* Pods-hackfoldr-iOSTests.debug.xcconfig */, 153 | 0FFCE6D7B8522BC376B03A51 /* Pods-hackfoldr-iOSTests.release.xcconfig */, 154 | ); 155 | name = Pods; 156 | sourceTree = ""; 157 | }; 158 | E70F906E1A77F7A300BF4B25 /* Extensions */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | E79B0E781E7B1714005BE38C /* HackfoldrClient+Store.h */, 162 | E79B0E791E7B1714005BE38C /* HackfoldrClient+Store.m */, 163 | E77B93CF1FAB1F4800BD8130 /* HackfoldrPage+CSSearchableItem.h */, 164 | E77B93D01FAB1F4800BD8130 /* HackfoldrPage+CSSearchableItem.m */, 165 | 886F4A451E0536AA00AF14DE /* NSURL+Hackfoldr.h */, 166 | 886F4A461E0536AA00AF14DE /* NSURL+Hackfoldr.m */, 167 | E70F906B1A77F72200BF4B25 /* NSUserDefaults+DefaultHackfoldrPage.h */, 168 | E70F906C1A77F72200BF4B25 /* NSUserDefaults+DefaultHackfoldrPage.m */, 169 | E79B0E751E7AE43F005BE38C /* UIColor+Hackfoldr.h */, 170 | E79B0E761E7AE43F005BE38C /* UIColor+Hackfoldr.m */, 171 | ); 172 | name = Extensions; 173 | sourceTree = ""; 174 | }; 175 | E71D192E1956DAF800B57207 /* Models */ = { 176 | isa = PBXGroup; 177 | children = ( 178 | E71D192B1956CFB600B57207 /* HackfoldrField.h */, 179 | E71D192C1956CFB600B57207 /* HackfoldrField.m */, 180 | E79424AF1ACEEF6400558AE9 /* HackfoldrHistory.h */, 181 | E79424B01ACEEF6400558AE9 /* HackfoldrHistory.m */, 182 | E7F9558519570D4E0098661E /* HackfoldrPage.h */, 183 | E7F9558619570D4E0098661E /* HackfoldrPage.m */, 184 | E79424AC1ACEEDCF00558AE9 /* HackoldrModel.xcdatamodeld */, 185 | ); 186 | name = Models; 187 | sourceTree = ""; 188 | }; 189 | E71D192F1956DB0300B57207 /* Client */ = { 190 | isa = PBXGroup; 191 | children = ( 192 | E71D19281956CE5500B57207 /* HackfoldrClient.h */, 193 | E71D19291956CE5500B57207 /* HackfoldrClient.m */, 194 | ); 195 | name = Client; 196 | sourceTree = ""; 197 | }; 198 | E7B1DD871959AFE700768FFA /* ViewController */ = { 199 | isa = PBXGroup; 200 | children = ( 201 | 882E652819B2D5920054E522 /* ListFieldViewController.h */, 202 | 882E652919B2D5920054E522 /* ListFieldViewController.m */, 203 | E7EB66D319554E290069FD71 /* MainViewController.h */, 204 | E7EB66D419554E290069FD71 /* MainViewController.m */, 205 | E799373A1FC4C71000DAC153 /* QRCode.storyboard */, 206 | E79937371FC4C00C00DAC153 /* QRCodeViewController.h */, 207 | E79937381FC4C00C00DAC153 /* QRCodeViewController.m */, 208 | E77B93D21FAB29EC00BD8130 /* SettingViewController.h */, 209 | E77B93D31FAB29EC00BD8130 /* SettingViewController.m */, 210 | E7B1DD88195AEE7D00768FFA /* TOWebViewController+HackfoldrField.h */, 211 | E7B1DD89195AEE7D00768FFA /* TOWebViewController+HackfoldrField.m */, 212 | ); 213 | name = ViewController; 214 | sourceTree = ""; 215 | }; 216 | E7EB66AF19554E290069FD71 = { 217 | isa = PBXGroup; 218 | children = ( 219 | E7B1DD871959AFE700768FFA /* ViewController */, 220 | E71D192F1956DB0300B57207 /* Client */, 221 | E70F906E1A77F7A300BF4B25 /* Extensions */, 222 | E71D192E1956DAF800B57207 /* Models */, 223 | E7EB66BA19554E290069FD71 /* Frameworks */, 224 | E7EB66C119554E290069FD71 /* hackfoldr-iOS */, 225 | E7EB66E319554E290069FD71 /* hackfoldr-iOSTests */, 226 | E7EB66B919554E290069FD71 /* Products */, 227 | 53725B8A749867B92203B128 /* Pods */, 228 | ); 229 | sourceTree = ""; 230 | }; 231 | E7EB66B919554E290069FD71 /* Products */ = { 232 | isa = PBXGroup; 233 | children = ( 234 | E7EB66B819554E290069FD71 /* hackfoldr-iOS.app */, 235 | E7EB66DC19554E290069FD71 /* hackfoldr-iOSTests.xctest */, 236 | ); 237 | name = Products; 238 | sourceTree = ""; 239 | }; 240 | E7EB66BA19554E290069FD71 /* Frameworks */ = { 241 | isa = PBXGroup; 242 | children = ( 243 | E7F748291955878C00262A34 /* QuartzCore.framework */, 244 | E7EB66BB19554E290069FD71 /* Foundation.framework */, 245 | E7EB66BD19554E290069FD71 /* CoreGraphics.framework */, 246 | E7EB66BF19554E290069FD71 /* UIKit.framework */, 247 | E7EB66DD19554E290069FD71 /* XCTest.framework */, 248 | E2BE931B1EC9D9EDF8673F5C /* libPods-hackfoldr-iOS.a */, 249 | 893ECEAB2C1FEB82EE9E1ED6 /* libPods-hackfoldr-iOSTests.a */, 250 | ); 251 | name = Frameworks; 252 | sourceTree = ""; 253 | }; 254 | E7EB66C119554E290069FD71 /* hackfoldr-iOS */ = { 255 | isa = PBXGroup; 256 | children = ( 257 | 889DFBC61D28EE740066ECA2 /* hackfoldr-iOS.entitlements */, 258 | E7EB66CA19554E290069FD71 /* AppDelegate.h */, 259 | E7EB66CB19554E290069FD71 /* AppDelegate.m */, 260 | E7EB66D619554E290069FD71 /* Images.xcassets */, 261 | E79B0E7D1E7C030E005BE38C /* g0v.svg */, 262 | 88AC6BE91F6BD13000E082E2 /* LaunchScreen.storyboard */, 263 | E7EB66C219554E290069FD71 /* Supporting Files */, 264 | ); 265 | path = "hackfoldr-iOS"; 266 | sourceTree = ""; 267 | }; 268 | E7EB66C219554E290069FD71 /* Supporting Files */ = { 269 | isa = PBXGroup; 270 | children = ( 271 | E7EB66C319554E290069FD71 /* hackfoldr-iOS-Info.plist */, 272 | E7EB66C919554E290069FD71 /* hackfoldr-iOS-Prefix.pch */, 273 | E75590781AEAB595007337B1 /* Hackfoldr.strings */, 274 | E7EB66C419554E290069FD71 /* InfoPlist.strings */, 275 | E7EB66C719554E290069FD71 /* main.m */, 276 | ); 277 | name = "Supporting Files"; 278 | sourceTree = ""; 279 | }; 280 | E7EB66E319554E290069FD71 /* hackfoldr-iOSTests */ = { 281 | isa = PBXGroup; 282 | children = ( 283 | E79424B21ACF015300558AE9 /* sample.csv.json */, 284 | E7EB66E919554E290069FD71 /* hackfoldr_iOSTests.m */, 285 | E7EB66E419554E290069FD71 /* Supporting Files */, 286 | ); 287 | path = "hackfoldr-iOSTests"; 288 | sourceTree = ""; 289 | }; 290 | E7EB66E419554E290069FD71 /* Supporting Files */ = { 291 | isa = PBXGroup; 292 | children = ( 293 | E7EB66E519554E290069FD71 /* hackfoldr-iOSTests-Info.plist */, 294 | E7EB66E619554E290069FD71 /* InfoPlist.strings */, 295 | ); 296 | name = "Supporting Files"; 297 | sourceTree = ""; 298 | }; 299 | /* End PBXGroup section */ 300 | 301 | /* Begin PBXNativeTarget section */ 302 | E7EB66B719554E290069FD71 /* hackfoldr-iOS */ = { 303 | isa = PBXNativeTarget; 304 | buildConfigurationList = E7EB66ED19554E290069FD71 /* Build configuration list for PBXNativeTarget "hackfoldr-iOS" */; 305 | buildPhases = ( 306 | 052512BDB754F2B582BE1463 /* [CP] Check Pods Manifest.lock */, 307 | E7EB66B419554E290069FD71 /* Sources */, 308 | E7EB66B519554E290069FD71 /* Frameworks */, 309 | E7EB66B619554E290069FD71 /* Resources */, 310 | E7118A521AD4FFB7007608C0 /* Update app bundle version */, 311 | 69F766257D99B6A5FCB74096 /* [CP] Copy Pods Resources */, 312 | ); 313 | buildRules = ( 314 | ); 315 | dependencies = ( 316 | ); 317 | name = "hackfoldr-iOS"; 318 | productName = "hackfoldr-iOS"; 319 | productReference = E7EB66B819554E290069FD71 /* hackfoldr-iOS.app */; 320 | productType = "com.apple.product-type.application"; 321 | }; 322 | E7EB66DB19554E290069FD71 /* hackfoldr-iOSTests */ = { 323 | isa = PBXNativeTarget; 324 | buildConfigurationList = E7EB66F019554E290069FD71 /* Build configuration list for PBXNativeTarget "hackfoldr-iOSTests" */; 325 | buildPhases = ( 326 | 77FF7C2AC3EDECEA679C69E0 /* [CP] Check Pods Manifest.lock */, 327 | E7EB66D819554E290069FD71 /* Sources */, 328 | E7EB66D919554E290069FD71 /* Frameworks */, 329 | E7EB66DA19554E290069FD71 /* Resources */, 330 | ); 331 | buildRules = ( 332 | ); 333 | dependencies = ( 334 | E7EB66E219554E290069FD71 /* PBXTargetDependency */, 335 | ); 336 | name = "hackfoldr-iOSTests"; 337 | productName = "hackfoldr-iOSTests"; 338 | productReference = E7EB66DC19554E290069FD71 /* hackfoldr-iOSTests.xctest */; 339 | productType = "com.apple.product-type.bundle.unit-test"; 340 | }; 341 | /* End PBXNativeTarget section */ 342 | 343 | /* Begin PBXProject section */ 344 | E7EB66B019554E290069FD71 /* Project object */ = { 345 | isa = PBXProject; 346 | attributes = { 347 | LastUpgradeCheck = 1010; 348 | ORGANIZATIONNAME = org.g0v; 349 | TargetAttributes = { 350 | E7EB66B719554E290069FD71 = { 351 | DevelopmentTeam = 67Y6DS58YP; 352 | SystemCapabilities = { 353 | com.apple.SafariKeychain = { 354 | enabled = 1; 355 | }; 356 | }; 357 | }; 358 | E7EB66DB19554E290069FD71 = { 359 | TestTargetID = E7EB66B719554E290069FD71; 360 | }; 361 | }; 362 | }; 363 | buildConfigurationList = E7EB66B319554E290069FD71 /* Build configuration list for PBXProject "hackfoldr-iOS" */; 364 | compatibilityVersion = "Xcode 3.2"; 365 | developmentRegion = English; 366 | hasScannedForEncodings = 0; 367 | knownRegions = ( 368 | English, 369 | en, 370 | Base, 371 | "zh-Hant", 372 | ); 373 | mainGroup = E7EB66AF19554E290069FD71; 374 | productRefGroup = E7EB66B919554E290069FD71 /* Products */; 375 | projectDirPath = ""; 376 | projectRoot = ""; 377 | targets = ( 378 | E7EB66B719554E290069FD71 /* hackfoldr-iOS */, 379 | E7EB66DB19554E290069FD71 /* hackfoldr-iOSTests */, 380 | ); 381 | }; 382 | /* End PBXProject section */ 383 | 384 | /* Begin PBXResourcesBuildPhase section */ 385 | E7EB66B619554E290069FD71 /* Resources */ = { 386 | isa = PBXResourcesBuildPhase; 387 | buildActionMask = 2147483647; 388 | files = ( 389 | E7EB66D719554E290069FD71 /* Images.xcassets in Resources */, 390 | 88AC6BEA1F6BD13000E082E2 /* LaunchScreen.storyboard in Resources */, 391 | E7EB66C619554E290069FD71 /* InfoPlist.strings in Resources */, 392 | E79B0E7E1E7C030E005BE38C /* g0v.svg in Resources */, 393 | E799373B1FC4C71000DAC153 /* QRCode.storyboard in Resources */, 394 | E755907A1AEAB595007337B1 /* Hackfoldr.strings in Resources */, 395 | ); 396 | runOnlyForDeploymentPostprocessing = 0; 397 | }; 398 | E7EB66DA19554E290069FD71 /* Resources */ = { 399 | isa = PBXResourcesBuildPhase; 400 | buildActionMask = 2147483647; 401 | files = ( 402 | E7EB66E819554E290069FD71 /* InfoPlist.strings in Resources */, 403 | E79424B31ACF015300558AE9 /* sample.csv.json in Resources */, 404 | ); 405 | runOnlyForDeploymentPostprocessing = 0; 406 | }; 407 | /* End PBXResourcesBuildPhase section */ 408 | 409 | /* Begin PBXShellScriptBuildPhase section */ 410 | 052512BDB754F2B582BE1463 /* [CP] Check Pods Manifest.lock */ = { 411 | isa = PBXShellScriptBuildPhase; 412 | buildActionMask = 2147483647; 413 | files = ( 414 | ); 415 | inputPaths = ( 416 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 417 | "${PODS_ROOT}/Manifest.lock", 418 | ); 419 | name = "[CP] Check Pods Manifest.lock"; 420 | outputPaths = ( 421 | "$(DERIVED_FILE_DIR)/Pods-hackfoldr-iOS-checkManifestLockResult.txt", 422 | ); 423 | runOnlyForDeploymentPostprocessing = 0; 424 | shellPath = /bin/sh; 425 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 426 | showEnvVarsInLog = 0; 427 | }; 428 | 69F766257D99B6A5FCB74096 /* [CP] Copy Pods Resources */ = { 429 | isa = PBXShellScriptBuildPhase; 430 | buildActionMask = 2147483647; 431 | files = ( 432 | ); 433 | inputPaths = ( 434 | "${SRCROOT}/Pods/Target Support Files/Pods-hackfoldr-iOS/Pods-hackfoldr-iOS-resources.sh", 435 | "${PODS_ROOT}/FontAwesomeKit/FontAwesomeKit/FontAwesome.otf", 436 | "${PODS_ROOT}/FontAwesomeKit/FontAwesomeKit/foundation-icons.ttf", 437 | "${PODS_ROOT}/FontAwesomeKit/FontAwesomeKit/ionicons.ttf", 438 | "${PODS_ROOT}/FontAwesomeKit/FontAwesomeKit/Material-Design-Iconic-Font.ttf", 439 | "${PODS_ROOT}/FontAwesomeKit/FontAwesomeKit/octicons.ttf", 440 | "${PODS_ROOT}/FontAwesomeKit/FontAwesomeKit/zocial-regular-webfont.ttf", 441 | "${PODS_CONFIGURATION_BUILD_DIR}/TOWebViewController/TOWebViewControllerLocalizable.bundle", 442 | "${PODS_ROOT}/XLForm/XLForm/XLForm.bundle", 443 | ); 444 | name = "[CP] Copy Pods Resources"; 445 | outputPaths = ( 446 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.otf", 447 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/foundation-icons.ttf", 448 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ionicons.ttf", 449 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Material-Design-Iconic-Font.ttf", 450 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/octicons.ttf", 451 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/zocial-regular-webfont.ttf", 452 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOWebViewControllerLocalizable.bundle", 453 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/XLForm.bundle", 454 | ); 455 | runOnlyForDeploymentPostprocessing = 0; 456 | shellPath = /bin/sh; 457 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-hackfoldr-iOS/Pods-hackfoldr-iOS-resources.sh\"\n"; 458 | showEnvVarsInLog = 0; 459 | }; 460 | 77FF7C2AC3EDECEA679C69E0 /* [CP] Check Pods Manifest.lock */ = { 461 | isa = PBXShellScriptBuildPhase; 462 | buildActionMask = 2147483647; 463 | files = ( 464 | ); 465 | inputPaths = ( 466 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 467 | "${PODS_ROOT}/Manifest.lock", 468 | ); 469 | name = "[CP] Check Pods Manifest.lock"; 470 | outputPaths = ( 471 | "$(DERIVED_FILE_DIR)/Pods-hackfoldr-iOSTests-checkManifestLockResult.txt", 472 | ); 473 | runOnlyForDeploymentPostprocessing = 0; 474 | shellPath = /bin/sh; 475 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 476 | showEnvVarsInLog = 0; 477 | }; 478 | E7118A521AD4FFB7007608C0 /* Update app bundle version */ = { 479 | isa = PBXShellScriptBuildPhase; 480 | buildActionMask = 2147483647; 481 | files = ( 482 | ); 483 | inputPaths = ( 484 | ); 485 | name = "Update app bundle version"; 486 | outputPaths = ( 487 | ); 488 | runOnlyForDeploymentPostprocessing = 0; 489 | shellPath = /bin/sh; 490 | shellScript = "exec ./script/xcode-git-version.sh"; 491 | }; 492 | /* End PBXShellScriptBuildPhase section */ 493 | 494 | /* Begin PBXSourcesBuildPhase section */ 495 | E7EB66B419554E290069FD71 /* Sources */ = { 496 | isa = PBXSourcesBuildPhase; 497 | buildActionMask = 2147483647; 498 | files = ( 499 | E79424B11ACEEF6400558AE9 /* HackfoldrHistory.m in Sources */, 500 | E79B0E7A1E7B1714005BE38C /* HackfoldrClient+Store.m in Sources */, 501 | 886F4A471E0536AA00AF14DE /* NSURL+Hackfoldr.m in Sources */, 502 | E7EB66D519554E290069FD71 /* MainViewController.m in Sources */, 503 | 882E652A19B2D5920054E522 /* ListFieldViewController.m in Sources */, 504 | E77B93D11FAB1F4800BD8130 /* HackfoldrPage+CSSearchableItem.m in Sources */, 505 | E79B0E771E7AE43F005BE38C /* UIColor+Hackfoldr.m in Sources */, 506 | E7B1DD8A195AEE7D00768FFA /* TOWebViewController+HackfoldrField.m in Sources */, 507 | E7EB66CC19554E290069FD71 /* AppDelegate.m in Sources */, 508 | E71D192A1956CE5500B57207 /* HackfoldrClient.m in Sources */, 509 | E79424AE1ACEEDCF00558AE9 /* HackoldrModel.xcdatamodeld in Sources */, 510 | E71D192D1956CFB600B57207 /* HackfoldrField.m in Sources */, 511 | E7F9558719570D4E0098661E /* HackfoldrPage.m in Sources */, 512 | E70F906D1A77F72200BF4B25 /* NSUserDefaults+DefaultHackfoldrPage.m in Sources */, 513 | E7EB66C819554E290069FD71 /* main.m in Sources */, 514 | E79937391FC4C00C00DAC153 /* QRCodeViewController.m in Sources */, 515 | E77B93D41FAB29EC00BD8130 /* SettingViewController.m in Sources */, 516 | ); 517 | runOnlyForDeploymentPostprocessing = 0; 518 | }; 519 | E7EB66D819554E290069FD71 /* Sources */ = { 520 | isa = PBXSourcesBuildPhase; 521 | buildActionMask = 2147483647; 522 | files = ( 523 | E7EB66EA19554E290069FD71 /* hackfoldr_iOSTests.m in Sources */, 524 | ); 525 | runOnlyForDeploymentPostprocessing = 0; 526 | }; 527 | /* End PBXSourcesBuildPhase section */ 528 | 529 | /* Begin PBXTargetDependency section */ 530 | E7EB66E219554E290069FD71 /* PBXTargetDependency */ = { 531 | isa = PBXTargetDependency; 532 | target = E7EB66B719554E290069FD71 /* hackfoldr-iOS */; 533 | targetProxy = E7EB66E119554E290069FD71 /* PBXContainerItemProxy */; 534 | }; 535 | /* End PBXTargetDependency section */ 536 | 537 | /* Begin PBXVariantGroup section */ 538 | E75590781AEAB595007337B1 /* Hackfoldr.strings */ = { 539 | isa = PBXVariantGroup; 540 | children = ( 541 | E75590791AEAB595007337B1 /* zh-Hant */, 542 | E755907B1AEAB5A0007337B1 /* en */, 543 | ); 544 | name = Hackfoldr.strings; 545 | sourceTree = ""; 546 | }; 547 | E7EB66C419554E290069FD71 /* InfoPlist.strings */ = { 548 | isa = PBXVariantGroup; 549 | children = ( 550 | E7EB66C519554E290069FD71 /* en */, 551 | E7A644D31FCEC12D00D28263 /* zh-Hant */, 552 | ); 553 | name = InfoPlist.strings; 554 | sourceTree = ""; 555 | }; 556 | E7EB66E619554E290069FD71 /* InfoPlist.strings */ = { 557 | isa = PBXVariantGroup; 558 | children = ( 559 | E7EB66E719554E290069FD71 /* en */, 560 | ); 561 | name = InfoPlist.strings; 562 | sourceTree = ""; 563 | }; 564 | /* End PBXVariantGroup section */ 565 | 566 | /* Begin XCBuildConfiguration section */ 567 | E7EB66EB19554E290069FD71 /* Debug */ = { 568 | isa = XCBuildConfiguration; 569 | buildSettings = { 570 | ALWAYS_SEARCH_USER_PATHS = NO; 571 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 572 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 573 | CLANG_CXX_LIBRARY = "libc++"; 574 | CLANG_ENABLE_MODULES = YES; 575 | CLANG_ENABLE_OBJC_ARC = YES; 576 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 577 | CLANG_WARN_BOOL_CONVERSION = YES; 578 | CLANG_WARN_COMMA = YES; 579 | CLANG_WARN_CONSTANT_CONVERSION = YES; 580 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 581 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 582 | CLANG_WARN_EMPTY_BODY = YES; 583 | CLANG_WARN_ENUM_CONVERSION = YES; 584 | CLANG_WARN_INFINITE_RECURSION = YES; 585 | CLANG_WARN_INT_CONVERSION = YES; 586 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 587 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 588 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 589 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 590 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 591 | CLANG_WARN_STRICT_PROTOTYPES = YES; 592 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 593 | CLANG_WARN_UNREACHABLE_CODE = YES; 594 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 595 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 596 | COPY_PHASE_STRIP = NO; 597 | ENABLE_STRICT_OBJC_MSGSEND = YES; 598 | ENABLE_TESTABILITY = YES; 599 | GCC_C_LANGUAGE_STANDARD = gnu99; 600 | GCC_DYNAMIC_NO_PIC = NO; 601 | GCC_NO_COMMON_BLOCKS = YES; 602 | GCC_OPTIMIZATION_LEVEL = 0; 603 | GCC_PREPROCESSOR_DEFINITIONS = ( 604 | "DEBUG=1", 605 | "$(inherited)", 606 | ); 607 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 608 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 609 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 610 | GCC_WARN_UNDECLARED_SELECTOR = YES; 611 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 612 | GCC_WARN_UNUSED_FUNCTION = YES; 613 | GCC_WARN_UNUSED_VARIABLE = YES; 614 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 615 | ONLY_ACTIVE_ARCH = YES; 616 | SDKROOT = iphoneos; 617 | TARGETED_DEVICE_FAMILY = "1,2"; 618 | }; 619 | name = Debug; 620 | }; 621 | E7EB66EC19554E290069FD71 /* Release */ = { 622 | isa = XCBuildConfiguration; 623 | buildSettings = { 624 | ALWAYS_SEARCH_USER_PATHS = NO; 625 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 626 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 627 | CLANG_CXX_LIBRARY = "libc++"; 628 | CLANG_ENABLE_MODULES = YES; 629 | CLANG_ENABLE_OBJC_ARC = YES; 630 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 631 | CLANG_WARN_BOOL_CONVERSION = YES; 632 | CLANG_WARN_COMMA = YES; 633 | CLANG_WARN_CONSTANT_CONVERSION = YES; 634 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 635 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 636 | CLANG_WARN_EMPTY_BODY = YES; 637 | CLANG_WARN_ENUM_CONVERSION = YES; 638 | CLANG_WARN_INFINITE_RECURSION = YES; 639 | CLANG_WARN_INT_CONVERSION = YES; 640 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 641 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 642 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 643 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 644 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 645 | CLANG_WARN_STRICT_PROTOTYPES = YES; 646 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 647 | CLANG_WARN_UNREACHABLE_CODE = YES; 648 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 649 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 650 | COPY_PHASE_STRIP = YES; 651 | ENABLE_NS_ASSERTIONS = NO; 652 | ENABLE_STRICT_OBJC_MSGSEND = YES; 653 | GCC_C_LANGUAGE_STANDARD = gnu99; 654 | GCC_NO_COMMON_BLOCKS = YES; 655 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 656 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 657 | GCC_WARN_UNDECLARED_SELECTOR = YES; 658 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 659 | GCC_WARN_UNUSED_FUNCTION = YES; 660 | GCC_WARN_UNUSED_VARIABLE = YES; 661 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 662 | SDKROOT = iphoneos; 663 | TARGETED_DEVICE_FAMILY = "1,2"; 664 | VALIDATE_PRODUCT = YES; 665 | }; 666 | name = Release; 667 | }; 668 | E7EB66EE19554E290069FD71 /* Debug */ = { 669 | isa = XCBuildConfiguration; 670 | baseConfigurationReference = D6F8B8E84166B0CC5D012432 /* Pods-hackfoldr-iOS.debug.xcconfig */; 671 | buildSettings = { 672 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 673 | CODE_SIGN_ENTITLEMENTS = "hackfoldr-iOS/hackfoldr-iOS.entitlements"; 674 | DEVELOPMENT_TEAM = 67Y6DS58YP; 675 | FRAMEWORK_SEARCH_PATHS = ( 676 | "$(inherited)", 677 | "$(PROJECT_DIR)", 678 | ); 679 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 680 | GCC_PREFIX_HEADER = "hackfoldr-iOS/hackfoldr-iOS-Prefix.pch"; 681 | INFOPLIST_FILE = "hackfoldr-iOS/hackfoldr-iOS-Info.plist"; 682 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 683 | PRODUCT_BUNDLE_IDENTIFIER = org.g0v.hackfoldr; 684 | PRODUCT_NAME = "$(TARGET_NAME)"; 685 | PROVISIONING_PROFILE = ""; 686 | TARGETED_DEVICE_FAMILY = 1; 687 | WRAPPER_EXTENSION = app; 688 | }; 689 | name = Debug; 690 | }; 691 | E7EB66EF19554E290069FD71 /* Release */ = { 692 | isa = XCBuildConfiguration; 693 | baseConfigurationReference = F15476DD2E850E7A93F2DF39 /* Pods-hackfoldr-iOS.release.xcconfig */; 694 | buildSettings = { 695 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 696 | CODE_SIGN_ENTITLEMENTS = "hackfoldr-iOS/hackfoldr-iOS.entitlements"; 697 | DEVELOPMENT_TEAM = 67Y6DS58YP; 698 | FRAMEWORK_SEARCH_PATHS = ( 699 | "$(inherited)", 700 | "$(PROJECT_DIR)", 701 | ); 702 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 703 | GCC_PREFIX_HEADER = "hackfoldr-iOS/hackfoldr-iOS-Prefix.pch"; 704 | INFOPLIST_FILE = "hackfoldr-iOS/hackfoldr-iOS-Info.plist"; 705 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 706 | PRODUCT_BUNDLE_IDENTIFIER = org.g0v.hackfoldr; 707 | PRODUCT_NAME = "$(TARGET_NAME)"; 708 | PROVISIONING_PROFILE = ""; 709 | TARGETED_DEVICE_FAMILY = 1; 710 | WRAPPER_EXTENSION = app; 711 | }; 712 | name = Release; 713 | }; 714 | E7EB66F119554E290069FD71 /* Debug */ = { 715 | isa = XCBuildConfiguration; 716 | baseConfigurationReference = 70B8275F56498EFBD8F1410F /* Pods-hackfoldr-iOSTests.debug.xcconfig */; 717 | buildSettings = { 718 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/hackfoldr-iOS.app/hackfoldr-iOS"; 719 | FRAMEWORK_SEARCH_PATHS = ( 720 | "$(SDKROOT)/Developer/Library/Frameworks", 721 | "$(inherited)", 722 | "$(DEVELOPER_FRAMEWORKS_DIR)", 723 | ); 724 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 725 | GCC_PREFIX_HEADER = "hackfoldr-iOS/hackfoldr-iOS-Prefix.pch"; 726 | GCC_PREPROCESSOR_DEFINITIONS = ( 727 | "DEBUG=1", 728 | "$(inherited)", 729 | ); 730 | INFOPLIST_FILE = "hackfoldr-iOSTests/hackfoldr-iOSTests-Info.plist"; 731 | PRODUCT_BUNDLE_IDENTIFIER = org.g0v.hackfoldr; 732 | PRODUCT_NAME = "$(TARGET_NAME)"; 733 | TEST_HOST = "$(BUNDLE_LOADER)"; 734 | WRAPPER_EXTENSION = xctest; 735 | }; 736 | name = Debug; 737 | }; 738 | E7EB66F219554E290069FD71 /* Release */ = { 739 | isa = XCBuildConfiguration; 740 | baseConfigurationReference = 0FFCE6D7B8522BC376B03A51 /* Pods-hackfoldr-iOSTests.release.xcconfig */; 741 | buildSettings = { 742 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/hackfoldr-iOS.app/hackfoldr-iOS"; 743 | FRAMEWORK_SEARCH_PATHS = ( 744 | "$(SDKROOT)/Developer/Library/Frameworks", 745 | "$(inherited)", 746 | "$(DEVELOPER_FRAMEWORKS_DIR)", 747 | ); 748 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 749 | GCC_PREFIX_HEADER = "hackfoldr-iOS/hackfoldr-iOS-Prefix.pch"; 750 | INFOPLIST_FILE = "hackfoldr-iOSTests/hackfoldr-iOSTests-Info.plist"; 751 | PRODUCT_BUNDLE_IDENTIFIER = org.g0v.hackfoldr; 752 | PRODUCT_NAME = "$(TARGET_NAME)"; 753 | TEST_HOST = "$(BUNDLE_LOADER)"; 754 | WRAPPER_EXTENSION = xctest; 755 | }; 756 | name = Release; 757 | }; 758 | /* End XCBuildConfiguration section */ 759 | 760 | /* Begin XCConfigurationList section */ 761 | E7EB66B319554E290069FD71 /* Build configuration list for PBXProject "hackfoldr-iOS" */ = { 762 | isa = XCConfigurationList; 763 | buildConfigurations = ( 764 | E7EB66EB19554E290069FD71 /* Debug */, 765 | E7EB66EC19554E290069FD71 /* Release */, 766 | ); 767 | defaultConfigurationIsVisible = 0; 768 | defaultConfigurationName = Release; 769 | }; 770 | E7EB66ED19554E290069FD71 /* Build configuration list for PBXNativeTarget "hackfoldr-iOS" */ = { 771 | isa = XCConfigurationList; 772 | buildConfigurations = ( 773 | E7EB66EE19554E290069FD71 /* Debug */, 774 | E7EB66EF19554E290069FD71 /* Release */, 775 | ); 776 | defaultConfigurationIsVisible = 0; 777 | defaultConfigurationName = Release; 778 | }; 779 | E7EB66F019554E290069FD71 /* Build configuration list for PBXNativeTarget "hackfoldr-iOSTests" */ = { 780 | isa = XCConfigurationList; 781 | buildConfigurations = ( 782 | E7EB66F119554E290069FD71 /* Debug */, 783 | E7EB66F219554E290069FD71 /* Release */, 784 | ); 785 | defaultConfigurationIsVisible = 0; 786 | defaultConfigurationName = Release; 787 | }; 788 | /* End XCConfigurationList section */ 789 | 790 | /* Begin XCVersionGroup section */ 791 | E79424AC1ACEEDCF00558AE9 /* HackoldrModel.xcdatamodeld */ = { 792 | isa = XCVersionGroup; 793 | children = ( 794 | E79424AD1ACEEDCF00558AE9 /* HackoldrModel.xcdatamodel */, 795 | ); 796 | currentVersion = E79424AD1ACEEDCF00558AE9 /* HackoldrModel.xcdatamodel */; 797 | name = HackoldrModel.xcdatamodeld; 798 | path = "hackfoldr-iOS/HackoldrModel.xcdatamodeld"; 799 | sourceTree = ""; 800 | versionGroupType = wrapper.xcdatamodel; 801 | }; 802 | /* End XCVersionGroup section */ 803 | }; 804 | rootObject = E7EB66B019554E290069FD71 /* Project object */; 805 | } 806 | --------------------------------------------------------------------------------