├── LeanCloudFeedback ├── resources │ ├── feedback_btn@2x.png │ ├── feedback_back@2x.png │ ├── feedback_bg_1@2x.png │ ├── feedback_bg_2@2x.png │ ├── feedback_btn_new@2x.png │ ├── feedback_warning@2x.png │ ├── feedback_add_image@2x.png │ └── LeanCloudFeedback.strings ├── LeanCloudFeedback.h ├── LCUserFeedbackImageViewController.h ├── LCUserFeedbackReply_Internal.h ├── LCUserFeedbackThread_Internal.h ├── LCUserFeedbackReplyCell.h ├── LCHttpClient.h ├── Info.plist ├── LCUserFeedbackImageViewController.m ├── LCUserFeedbackViewController.h ├── LCUserFeedbackReply.h ├── LCUserFeedbackThread.h ├── LCUserFeedbackAgent.h ├── LCUtils.h ├── LCUserFeedbackReply.m ├── LCUtils.m ├── LCUserFeedbackAgent.m ├── LCHttpClient.m ├── LCUserFeedbackThread.m ├── LCUserFeedbackReplyCell.m └── LCUserFeedbackViewController.m ├── LeanCloudFeedbackDemo ├── LeanCloudFeedback.framework │ └── Info.plist ├── LeanCloudFeedbackDemo │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── icon_29@2x.png │ │ │ ├── icon_29@3x.png │ │ │ ├── icon_40@2x.png │ │ │ ├── icon_40@3x.png │ │ │ ├── icon_60@2x.png │ │ │ ├── icon_60@3x.png │ │ │ └── Contents.json │ ├── ViewController.h │ ├── AppDelegate.h │ ├── main.m │ ├── Info.plist │ ├── ViewController.m │ ├── AppDelegate.m │ └── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard └── LeanCloudFeedbackDemo.xcodeproj │ ├── project.xcworkspace │ └── contents.xcworkspacedata │ └── project.pbxproj ├── README.md ├── LeanCloudFeedback.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── project.pbxproj ├── LeanCloudFeedback.xcworkspace ├── xcshareddata │ └── IDEWorkspaceChecks.plist └── contents.xcworkspacedata ├── Podfile.lock ├── Podfile ├── LeanCloudFeedback.podspec └── .gitignore /LeanCloudFeedback/resources/feedback_btn@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/leancloud-feedback-ios/HEAD/LeanCloudFeedback/resources/feedback_btn@2x.png -------------------------------------------------------------------------------- /LeanCloudFeedback/resources/feedback_back@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/leancloud-feedback-ios/HEAD/LeanCloudFeedback/resources/feedback_back@2x.png -------------------------------------------------------------------------------- /LeanCloudFeedback/resources/feedback_bg_1@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/leancloud-feedback-ios/HEAD/LeanCloudFeedback/resources/feedback_bg_1@2x.png -------------------------------------------------------------------------------- /LeanCloudFeedback/resources/feedback_bg_2@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/leancloud-feedback-ios/HEAD/LeanCloudFeedback/resources/feedback_bg_2@2x.png -------------------------------------------------------------------------------- /LeanCloudFeedback/resources/feedback_btn_new@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/leancloud-feedback-ios/HEAD/LeanCloudFeedback/resources/feedback_btn_new@2x.png -------------------------------------------------------------------------------- /LeanCloudFeedback/resources/feedback_warning@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/leancloud-feedback-ios/HEAD/LeanCloudFeedback/resources/feedback_warning@2x.png -------------------------------------------------------------------------------- /LeanCloudFeedback/resources/feedback_add_image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/leancloud-feedback-ios/HEAD/LeanCloudFeedback/resources/feedback_add_image@2x.png -------------------------------------------------------------------------------- /LeanCloudFeedbackDemo/LeanCloudFeedback.framework/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/leancloud-feedback-ios/HEAD/LeanCloudFeedbackDemo/LeanCloudFeedback.framework/Info.plist -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The user feedback API is deprecated. 2 | We recommend you [use LeanStorage][doc] to implement the feedback feature instead. 3 | 4 | [doc]: https://leancloud.cn/docs/rest_api.html#hash1473482921 -------------------------------------------------------------------------------- /LeanCloudFeedbackDemo/LeanCloudFeedbackDemo/Images.xcassets/AppIcon.appiconset/icon_29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/leancloud-feedback-ios/HEAD/LeanCloudFeedbackDemo/LeanCloudFeedbackDemo/Images.xcassets/AppIcon.appiconset/icon_29@2x.png -------------------------------------------------------------------------------- /LeanCloudFeedbackDemo/LeanCloudFeedbackDemo/Images.xcassets/AppIcon.appiconset/icon_29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/leancloud-feedback-ios/HEAD/LeanCloudFeedbackDemo/LeanCloudFeedbackDemo/Images.xcassets/AppIcon.appiconset/icon_29@3x.png -------------------------------------------------------------------------------- /LeanCloudFeedbackDemo/LeanCloudFeedbackDemo/Images.xcassets/AppIcon.appiconset/icon_40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/leancloud-feedback-ios/HEAD/LeanCloudFeedbackDemo/LeanCloudFeedbackDemo/Images.xcassets/AppIcon.appiconset/icon_40@2x.png -------------------------------------------------------------------------------- /LeanCloudFeedbackDemo/LeanCloudFeedbackDemo/Images.xcassets/AppIcon.appiconset/icon_40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/leancloud-feedback-ios/HEAD/LeanCloudFeedbackDemo/LeanCloudFeedbackDemo/Images.xcassets/AppIcon.appiconset/icon_40@3x.png -------------------------------------------------------------------------------- /LeanCloudFeedbackDemo/LeanCloudFeedbackDemo/Images.xcassets/AppIcon.appiconset/icon_60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/leancloud-feedback-ios/HEAD/LeanCloudFeedbackDemo/LeanCloudFeedbackDemo/Images.xcassets/AppIcon.appiconset/icon_60@2x.png -------------------------------------------------------------------------------- /LeanCloudFeedbackDemo/LeanCloudFeedbackDemo/Images.xcassets/AppIcon.appiconset/icon_60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/leancloud-feedback-ios/HEAD/LeanCloudFeedbackDemo/LeanCloudFeedbackDemo/Images.xcassets/AppIcon.appiconset/icon_60@3x.png -------------------------------------------------------------------------------- /LeanCloudFeedback.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LeanCloudFeedbackDemo/LeanCloudFeedbackDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LeanCloudFeedback.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LeanCloudFeedbackDemo/LeanCloudFeedbackDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // LeanCloudFeedbackDemo 4 | // 5 | // Created by Feng Junwen on 5/22/15. 6 | // Copyright (c) 2015 LeanCloud. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /LeanCloudFeedback/resources/LeanCloudFeedback.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | LeanCloudFeedback 4 | 5 | Created by lzw on 15/11/6. 6 | Copyright © 2015年 LeanCloud. All rights reserved. 7 | */ 8 | 9 | 10 | "Detail"="详情"; 11 | "Email Or QQ"="Email 或 QQ 号"; 12 | "Please write your feedback"="请填写反馈"; 13 | "Send"="发送"; 14 | "User Feedback"="意见反馈"; 15 | -------------------------------------------------------------------------------- /LeanCloudFeedback/LeanCloudFeedback.h: -------------------------------------------------------------------------------- 1 | // 2 | // LeanCloudFeedback.h 3 | // LeanCloudFeedback 4 | // 5 | // Created by Feng Junwen on 5/18/15. 6 | // Copyright (c) 2015 LeanCloud. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "LCUserFeedbackReply.h" 12 | #import "LCUserFeedbackAgent.h" 13 | #import "LCUserFeedbackThread.h" 14 | #import "LCUserFeedbackViewController.h" 15 | -------------------------------------------------------------------------------- /LeanCloudFeedback/LCUserFeedbackImageViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // LCUserFeedbackImageViewController.h 3 | // LeanCloudFeedback 4 | // 5 | // Created by lzw on 15/7/21. 6 | // Copyright (c) 2015年 LeanCloud. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface LCUserFeedbackImageViewController : UIViewController 12 | 13 | @property (nonatomic, strong) UIImage *image; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /LeanCloudFeedbackDemo/LeanCloudFeedbackDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // LeanCloudFeedbackDemo 4 | // 5 | // Created by Feng Junwen on 5/22/15. 6 | // Copyright (c) 2015 LeanCloud. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /LeanCloudFeedback.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /LeanCloudFeedback/LCUserFeedbackReply_Internal.h: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // LCUserFeedbackReply_Internal.h 4 | // LeanCloudFeedback 5 | // 6 | // Created by lzw on 15/7/21. 7 | // Copyright (c) 2015年 LeanCloud. All rights reserved. 8 | // 9 | 10 | #import "LCUserFeedbackReply.h" 11 | 12 | @interface LCUserFeedbackReply() 13 | 14 | @property(nonatomic, strong, readwrite) UIImage *attachmentImage; 15 | @property(nonatomic, copy, readwrite) NSString *createAt; 16 | 17 | @end -------------------------------------------------------------------------------- /LeanCloudFeedbackDemo/LeanCloudFeedbackDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // LeanCloudFeedbackDemo 4 | // 5 | // Created by Feng Junwen on 5/22/15. 6 | // Copyright (c) 2015 LeanCloud. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /LeanCloudFeedback/LCUserFeedbackThread_Internal.h: -------------------------------------------------------------------------------- 1 | // 2 | // LCUserFeedbackThread_Internal.h 3 | // Feedback 4 | // 5 | // Created by Feng Junwen on 5/18/15. 6 | // Copyright (c) 2015 LeanCloud. All rights reserved. 7 | // 8 | 9 | #import "LCUserFeedbackThread.h" 10 | 11 | @interface LCUserFeedbackThread () 12 | 13 | +(void)feedbackWithContent:(NSString *)content 14 | contact:(NSString *)contact 15 | create:(BOOL)create 16 | withBlock:(AVIdResultBlock)block; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - AVOSCloud (12.0.4) 3 | - LeanCloudFeedback (0.1.0): 4 | - AVOSCloud 5 | 6 | DEPENDENCIES: 7 | - AVOSCloud 8 | - LeanCloudFeedback (from `.`) 9 | 10 | SPEC REPOS: 11 | trunk: 12 | - AVOSCloud 13 | 14 | EXTERNAL SOURCES: 15 | LeanCloudFeedback: 16 | :path: "." 17 | 18 | SPEC CHECKSUMS: 19 | AVOSCloud: 6a3dd184e94a61caf3c8504e90172340a04798dd 20 | LeanCloudFeedback: 475a2cf99385a23cc7818116b81fb7c781b79ce0 21 | 22 | PODFILE CHECKSUM: b0677fc747183471f9d653b377e73db854e607f5 23 | 24 | COCOAPODS: 1.8.4 25 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '8.0' 3 | # Uncomment this line if you're using Swift 4 | # use_frameworks! 5 | 6 | platform :ios, '8.0' 7 | 8 | workspace 'LeanCloudFeedback.xcworkspace' 9 | 10 | target 'LeanCloudFeedback' do 11 | project 'LeanCloudFeedback.xcodeproj' 12 | pod 'AVOSCloud' 13 | end 14 | 15 | target 'LeanCloudFeedbackDemo' do 16 | project 'LeanCloudFeedbackDemo/LeanCloudFeedbackDemo.xcodeproj' 17 | pod 'AVOSCloud' 18 | pod 'LeanCloudFeedback', :path => '.' 19 | end 20 | -------------------------------------------------------------------------------- /LeanCloudFeedback.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "LeanCloudFeedback" 4 | s.version = "0.1.4" 5 | s.platform = :ios, "8.0" 6 | s.summary = "LeanCloud iOS SDK for mobile backend." 7 | s.homepage = "https://leancloud.cn" 8 | s.documentation_url = "https://leancloud.cn/docs/feedback.html" 9 | s.license = { 10 | :type => "Commercial", 11 | :text => "Copyright 2015 LeanCloud, Inc. See https://leancloud.cn/terms.html" 12 | } 13 | s.author = { "LeanCloud" => "support@leancloud.cn" } 14 | s.source = { :git => "https://github.com/leancloud/leancloud-feedback-ios.git", :tag => s.version.to_s } 15 | 16 | s.source_files = "LeanCloudFeedback/**/*.{h,m}" 17 | s.public_header_files = "LeanCloudFeedback/**/*.h" 18 | s.resources = "LeanCloudFeedback/resources/*.{png,strings}" 19 | 20 | s.dependency 'AVOSCloud' 21 | 22 | end 23 | -------------------------------------------------------------------------------- /LeanCloudFeedback/LCUserFeedbackReplyCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // LCUserFeedbackCell.h 3 | // 4 | // Created by yang chaozhong on 4/24/14. 5 | // Copyright (c) 2014 LeanCloud. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "LCUserFeedbackReply.h" 10 | 11 | @protocol LCUserFeedbackReplyCellDelegate 12 | 13 | - (void)didSelectImageViewOnFeedbackReply:(LCUserFeedbackReply *)feedbackReply; 14 | 15 | @end 16 | 17 | @interface LCUserFeedbackReplyCell : UITableViewCell 18 | 19 | @property (nonatomic, strong) UIFont *cellFont UI_APPEARANCE_SELECTOR; 20 | @property (nonatomic, strong) id delegate; 21 | 22 | + (CGFloat)heightForFeedbackReply:(LCUserFeedbackReply *)reply; 23 | 24 | - (id)initWithFeedbackReply:(LCUserFeedbackReply *)reply reuseIdentifier:(NSString *)reuseIdentifier; 25 | - (void)configuareCellWithFeedbackReply:(LCUserFeedbackReply *)reply; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /LeanCloudFeedback/LCHttpClient.h: -------------------------------------------------------------------------------- 1 | // 2 | // LCHttpClient.h 3 | // Feedback 4 | // 5 | // Created by Feng Junwen on 5/15/15. 6 | // Copyright (c) 2015 LeanCloud. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface LCHttpClient : NSObject 13 | 14 | +(LCHttpClient *)sharedInstance; 15 | 16 | -(void)postObject:(NSString *)path 17 | withParameters:(NSDictionary *)parameters 18 | block:(AVIdResultBlock)block; 19 | 20 | -(void)getObject:(NSString *)path 21 | withParameters:(NSDictionary *)parameters 22 | block:(AVIdResultBlock)block; 23 | 24 | -(void)putObject:(NSString *)path 25 | withParameters:(NSDictionary *)parameters 26 | block:(AVIdResultBlock)block; 27 | 28 | -(void)deleteObject:(NSString *)path 29 | withParameters:(NSDictionary *)parameters 30 | block:(AVIdResultBlock)block; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /LeanCloudFeedback/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 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /LeanCloudFeedback/LCUserFeedbackImageViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // LCUserFeedbackImageViewController.m 3 | // LeanCloudFeedback 4 | // 5 | // Created by lzw on 15/7/21. 6 | // Copyright (c) 2015年 LeanCloud. All rights reserved. 7 | // 8 | 9 | #import "LCUserFeedbackImageViewController.h" 10 | #import "LCUtils.h" 11 | 12 | @interface LCUserFeedbackImageViewController () 13 | 14 | @property (nonatomic, strong) UIImageView *imageView; 15 | 16 | @end 17 | 18 | @implementation LCUserFeedbackImageViewController 19 | 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | self.title = LCLocalizedString(@"Detail"); 23 | [self.view addSubview:self.imageView]; 24 | } 25 | 26 | - (void)didReceiveMemoryWarning { 27 | [super didReceiveMemoryWarning]; 28 | } 29 | 30 | - (UIImageView *)imageView { 31 | if (_imageView == nil) { 32 | _imageView = [[UIImageView alloc] initWithFrame:self.view.frame]; 33 | _imageView.contentMode = UIViewContentModeScaleAspectFill; 34 | _imageView.clipsToBounds = YES; 35 | _imageView.image = self.image; 36 | } 37 | return _imageView; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /LeanCloudFeedback/LCUserFeedbackViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // LCUserFeedbackViewController.h 3 | // 4 | // Created by yang chaozhong on 4/24/14. 5 | // Copyright (c) 2014 LeanCloud. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | typedef enum : NSUInteger { 11 | LCUserFeedbackNavigationBarStyleBlue = 0, 12 | LCUserFeedbackNavigationBarStyleNone, 13 | } LCUserFeedbackNavigationBarStyle; 14 | 15 | @interface LCUserFeedbackViewController : UIViewController 16 | 17 | /** 18 | * 导航栏主题,默认是蓝色主题 19 | */ 20 | @property(nonatomic, assign) LCUserFeedbackNavigationBarStyle navigationBarStyle; 21 | 22 | /** 23 | * 是否隐藏联系方式表头, 默认不隐藏。假如不需要用户提供联系方式则可以隐藏。 24 | */ 25 | @property(nonatomic, assign) BOOL contactHeaderHidden; 26 | 27 | /** 28 | * 设置字体。默认是大小为 16 的系统字体。 29 | */ 30 | @property(nonatomic, strong) UIFont *feedbackCellFont; 31 | 32 | /** 33 | * 这个是网站看到的用户反馈标题。默认采用第一条反馈作为标题。 34 | */ 35 | @property(nonatomic, copy) NSString *feedbackTitle; 36 | 37 | /** 38 | * 联系人。nil 需要用户自己填写。如果不需要用户填写联系方式,请设置 contactHeaderHidden 39 | */ 40 | @property(nonatomic, copy) NSString *contact; 41 | 42 | /** 43 | * 是否使用 present 方式弹出。默认为YES,决定返回按钮样式和返回方式 44 | */ 45 | @property(nonatomic, assign) BOOL presented; 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /LeanCloudFeedback/LCUserFeedbackReply.h: -------------------------------------------------------------------------------- 1 | // 2 | // LCUserFeedbackReply.h 3 | // 4 | // Created by yang chaozhong on 4/21/14. 5 | // Copyright (c) 2014 LeanCloud. All rights reserved. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | typedef enum : NSInteger { 12 | LCContentTypeText = 0 , 13 | LCContentTypeImage 14 | }LCContentType; 15 | 16 | typedef enum : NSInteger { 17 | LCReplyTypeUser = 0, 18 | LCReplyTypeDev, 19 | }LCReplyType; 20 | 21 | @class LCUserFeedbackThread; 22 | 23 | @interface LCUserFeedbackReply : NSObject 24 | 25 | @property(nonatomic, retain) LCUserFeedbackThread *feedback; 26 | 27 | @property(nonatomic, copy, readonly) NSString *content; 28 | @property(nonatomic, copy, readonly) NSString *attachment; 29 | @property(nonatomic, strong, readonly) UIImage *attachmentImage; 30 | @property(nonatomic, assign, readonly) LCReplyType type; 31 | @property(nonatomic, copy, readonly) NSString *createAt; 32 | 33 | + (instancetype)feedbackReplyWithContent:(NSString *)content type:(LCReplyType)type; 34 | + (instancetype)feedbackReplyWithAttachment:(NSString *)attachment type:(LCReplyType)type; 35 | 36 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary; 37 | 38 | - (NSDictionary *)dictionary; 39 | 40 | - (LCContentType)contentType; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /LeanCloudFeedbackDemo/LeanCloudFeedbackDemo/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_29@2x.png", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "icon_29@3x.png", 23 | "scale" : "3x" 24 | }, 25 | { 26 | "size" : "40x40", 27 | "idiom" : "iphone", 28 | "filename" : "icon_40@2x.png", 29 | "scale" : "2x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "icon_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" : "ios-marketing", 51 | "size" : "1024x1024", 52 | "scale" : "1x" 53 | } 54 | ], 55 | "info" : { 56 | "version" : 1, 57 | "author" : "xcode" 58 | } 59 | } -------------------------------------------------------------------------------- /LeanCloudFeedbackDemo/LeanCloudFeedbackDemo/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 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /LeanCloudFeedback/LCUserFeedbackThread.h: -------------------------------------------------------------------------------- 1 | // 2 | // LCUserFeedbackThread.h 3 | // Feedback 4 | // 5 | // Created by yang chaozhong on 4/21/14. 6 | // Copyright (c) 2014 LeanCloud. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @class LCUserFeedbackThread; 13 | @class LCUserFeedbackReply; 14 | 15 | typedef void (^LCUserFeedbackBlock)(LCUserFeedbackThread *feedback, NSError *error); 16 | 17 | 18 | @interface LCUserFeedbackThread : NSObject 19 | 20 | @property(nonatomic, copy) NSString *objectId; 21 | @property(nonatomic, copy) NSString *content; 22 | @property(nonatomic, copy) NSString *contact; 23 | 24 | +(void)fetchFeedbackWithBlock:(LCUserFeedbackBlock)block; 25 | +(void)fetchFeedbackWithContact:(NSString*)contact withBlock:(AVIdResultBlock)block AV_DEPRECATED("Use + fetchFeedbackWithBlock: instead"); 26 | +(void)feedbackWithContent:(NSString *)content contact:(NSString *)contact withBlock:(AVIdResultBlock)block AV_DEPRECATED("Use + fetchFeedbackWithBlock: instead"); 27 | 28 | +(void)updateFeedback:(LCUserFeedbackThread *)feedback withBlock:(AVIdResultBlock)block; 29 | 30 | +(void)deleteFeedback:(LCUserFeedbackThread *)feedback withBlock:(AVIdResultBlock)block; 31 | 32 | -(instancetype)initWithDictionary:(NSDictionary*)dict; 33 | 34 | -(void)saveFeedbackReplyInBackground:(LCUserFeedbackReply *)feedbackReply withBlock:(AVIdResultBlock)block; 35 | 36 | -(void)fetchFeedbackRepliesInBackgroundWithBlock:(AVArrayResultBlock)block; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | 37 | Pods/ 38 | 39 | # Add this line if you want to avoid checking in source code from the Xcode workspace 40 | # *.xcworkspace 41 | 42 | # Carthage 43 | # 44 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 45 | # Carthage/Checkouts 46 | 47 | Carthage/Build 48 | 49 | # fastlane 50 | # 51 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 52 | # screenshots whenever they are needed. 53 | # For more information about the recommended setup visit: 54 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 55 | 56 | fastlane/report.xml 57 | fastlane/Preview.html 58 | fastlane/screenshots/**/*.png 59 | fastlane/test_output 60 | 61 | # Code Injection 62 | # 63 | # After new code Injection tools there's a generated folder /iOSInjectionProject 64 | # https://github.com/johnno1962/injectionforxcode 65 | 66 | iOSInjectionProject/ 67 | -------------------------------------------------------------------------------- /LeanCloudFeedbackDemo/LeanCloudFeedbackDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // LeanCloudFeedbackDemo 4 | // 5 | // Created by Feng Junwen on 5/22/15. 6 | // Copyright (c) 2015 LeanCloud. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import 11 | 12 | @interface ViewController () 13 | 14 | @property (weak, nonatomic) IBOutlet UILabel *unreadTipLabel; 15 | 16 | @end 17 | 18 | @implementation ViewController 19 | 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | // Do any additional setup after loading the view, typically from a nib. 23 | self.unreadTipLabel.text = nil; 24 | [[LCUserFeedbackAgent sharedInstance] countUnreadFeedbackThreadsWithBlock:^(NSInteger number, NSError *error) { 25 | if (!error) { 26 | [self setUnreads:number]; 27 | } else { 28 | [self setUnreads:0]; 29 | } 30 | }]; 31 | } 32 | 33 | - (void)setUnreads:(NSInteger)unreads { 34 | self.unreadTipLabel.text = [NSString stringWithFormat:@"您有 %ld 条未读反馈", unreads]; 35 | } 36 | 37 | - (void)didReceiveMemoryWarning { 38 | [super didReceiveMemoryWarning]; 39 | // Dispose of any resources that can be recreated. 40 | } 41 | 42 | - (IBAction)presentFeedbackView:(id)sender { 43 | LCUserFeedbackAgent *agent = [LCUserFeedbackAgent sharedInstance]; 44 | // [agent showConversations:self title:nil contact:@"goodman@leancloud.cn"]; 45 | [agent showConversations:self title:nil contact:nil]; 46 | 47 | [self setUnreads:0]; 48 | } 49 | 50 | - (IBAction)pushFeedbackView:(id)sender { 51 | LCUserFeedbackViewController *feedbackViewController = [[LCUserFeedbackViewController alloc] init]; 52 | feedbackViewController.feedbackTitle = nil; 53 | feedbackViewController.contact = nil; 54 | 55 | // 隐藏联系人表头 56 | feedbackViewController.contactHeaderHidden = YES; 57 | 58 | // 决定返回按钮和样式 59 | feedbackViewController.presented = NO; 60 | 61 | // 不设置导航栏样式 62 | feedbackViewController.navigationBarStyle = LCUserFeedbackNavigationBarStyleNone; 63 | 64 | [self.navigationController pushViewController:feedbackViewController animated:YES]; 65 | 66 | [self setUnreads:0]; 67 | } 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /LeanCloudFeedback/LCUserFeedbackAgent.h: -------------------------------------------------------------------------------- 1 | // 2 | // LCUserFeedbackAgent.h 3 | // Feedback 4 | // 5 | // Created by yang chaozhong on 4/22/14. 6 | // Copyright (c) 2014 LeanCloud. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface LCUserFeedbackAgent : NSObject 13 | 14 | #pragma mark - methods 15 | 16 | /** 17 | * AVUserFeedbackAgent 实例 18 | */ 19 | +(instancetype)sharedInstance; 20 | 21 | /** 22 | * 打开默认用户反馈界面 23 | * @param viewController 默认的用户反馈界面将会展示在 viewController 之上,可以设置为当前的 viewController,比如 self。 24 | * @param title 反馈标题,如果传 nil,默认将 thread 中的第一条反馈消息作为标题。 25 | * @param contact 联系方式,邮箱或qq。 26 | */ 27 | - (void)showConversations:(UIViewController *)viewController title:(NSString *)title contact:(NSString *)contact; 28 | 29 | /** 30 | * 统计未读反馈数目 31 | * 32 | * @param block 结果回调。 33 | * @discussion 可用来设置小红点,提醒用户查看反馈 34 | */ 35 | - (void)countUnreadFeedbackThreadsWithBlock:(AVIntegerResultBlock)block; 36 | 37 | /** 38 | * 从服务端同步反馈回复,按照 contact 同步。 39 | * @param title 该字段已废弃,应该传 nil。 40 | * @param contact 联系方式,邮箱或qq。 41 | * @param block 结果回调。 42 | * @discussion 可以在 block 中处理反馈数据 (AVUserFeedbackThread 数组),然后将其传入自定义用户反馈界面。 43 | */ 44 | - (void)syncFeedbackThreadsWithBlock:(NSString *)title contact:(NSString *)contact block:(AVArrayResultBlock)block AV_DEPRECATED("Use -syncFeedbackThreadsWithContact:block: instead"); 45 | 46 | /** 47 | * 从服务端同步反馈回复,按照 contact 同步。 48 | * @param contact 联系方式,邮箱或qq。 49 | * @param block 结果回调。 50 | * @discussion 可以在 block 中处理反馈数据 (AVUserFeedbackThread 数组),然后将其传入自定义用户反馈界面。 51 | */ 52 | - (void)syncFeedbackThreadsWithContact:(NSString *)contact block:(AVArrayResultBlock)block AV_DEPRECATED("Because now feedback module is an open source project, you should tweak the UI in LCUserFeedbackViewController, do not make new UI and sync feedback threads by your own."); 53 | 54 | /** 55 | * 发送用户反馈 56 | * @param content 同上,用户反馈内容。 57 | * @param block 结果回调。 58 | */ 59 | - (void)postFeedbackThread:(NSString *)content block:(AVIdResultBlock)block AV_DEPRECATED("Use -postFeedbackThread:contact:block: instead"); 60 | 61 | /** 62 | * 发送用户反馈 63 | * @param content 同上,用户反馈内容。 64 | * @param contact 联系方式,邮箱或qq。 65 | * @param block 结果回调。 66 | */ 67 | - (void)postFeedbackThread:(NSString *)content contact:(NSString *)contact block:(AVIdResultBlock)block AV_DEPRECATED("Because now feedback module is an open source project, you should tweak the UI in LCUserFeedbackViewController, do not make new UI and post feedback by your own."); 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /LeanCloudFeedback/LCUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // LCUtils.h 3 | // Feedback 4 | // 5 | // Created by Feng Junwen on 5/15/15. 6 | // Copyright (c) 2015 LeanCloud. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #ifdef DEBUG 13 | # define FLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) 14 | #else 15 | # define FLog(fmt, ...) 16 | #endif 17 | 18 | #define LCLocalizedString(name) NSLocalizedStringFromTableInBundle(name, @"LeanCloudFeedback", [NSBundle bundleForClass:[self class]], nil) 19 | 20 | #define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending) 21 | 22 | @interface LCUtils : NSObject 23 | 24 | #pragma mark - Block 25 | + (void)callBooleanResultBlock:(AVBooleanResultBlock)block 26 | error:(NSError *)error; 27 | 28 | + (void)callIntegerResultBlock:(AVIntegerResultBlock)block 29 | number:(NSInteger)number 30 | error:(NSError *)error; 31 | 32 | + (void)callArrayResultBlock:(AVArrayResultBlock)block 33 | array:(NSArray *)array 34 | error:(NSError *)error; 35 | 36 | + (void)callObjectResultBlock:(AVObjectResultBlock)block 37 | object:(AVObject *)object 38 | error:(NSError *)error; 39 | 40 | + (void)callUserResultBlock:(AVUserResultBlock)block 41 | user:(AVUser *)user 42 | error:(NSError *)error; 43 | 44 | + (void)callIdResultBlock:(AVIdResultBlock)block 45 | object:(id)object 46 | error:(NSError *)error; 47 | 48 | + (void)callProgressBlock:(AVProgressBlock)block 49 | percent:(NSInteger)percentDone; 50 | 51 | 52 | + (void)callImageResultBlock:(AVImageResultBlock)block 53 | image:(UIImage *)image 54 | error:(NSError *)error; 55 | 56 | + (void)callFileResultBlock:(AVFileResultBlock)block 57 | AVFile:(AVFile *)file 58 | error:(NSError *)error; 59 | 60 | +(void)callSetResultBlock:(AVSetResultBlock)block 61 | set:(NSSet *)set 62 | error:(NSError *)error; 63 | +(void)callCloudQueryResultBlock:(AVCloudQueryCallback)block 64 | result:(AVCloudQueryResult *)result 65 | error:error; 66 | 67 | + (NSString*)calMD5:(NSString*)input; 68 | 69 | + (NSError *)errorWithText:(NSString *)format,... NS_FORMAT_FUNCTION(1, 2); 70 | 71 | @end 72 | -------------------------------------------------------------------------------- /LeanCloudFeedback/LCUserFeedbackReply.m: -------------------------------------------------------------------------------- 1 | // 2 | // LCUserFeedbackReply.m 3 | // 4 | // Created by yang chaozhong on 4/21/14. 5 | // Copyright (c) 2014 LeanCloud. All rights reserved. 6 | // 7 | 8 | #import "LCUserFeedbackReply.h" 9 | #import "LCUserFeedbackReply_Internal.h" 10 | #import "LCUserFeedbackThread.h" 11 | #import "LCHttpClient.h" 12 | #import "LCUtils.h" 13 | 14 | @interface LCUserFeedbackReply () 15 | 16 | @property(nonatomic, assign, readwrite) LCReplyType type; 17 | @property(nonatomic, copy, readwrite) NSString *attachment; 18 | @property(nonatomic, copy, readwrite) NSString *content; 19 | 20 | @end 21 | 22 | @implementation LCUserFeedbackReply 23 | 24 | - (NSDictionary *)dictionary { 25 | NSMutableDictionary * data = [[NSMutableDictionary alloc] init]; 26 | 27 | if (self.type == LCReplyTypeDev) { 28 | [data setObject:@"dev" forKey:@"type"]; 29 | } else { 30 | [data setObject:@"user" forKey:@"type"]; 31 | } 32 | if (self.content) { 33 | [data setObject:self.content forKey:@"content"]; 34 | } 35 | if (self.attachment) { 36 | [data setObject:self.attachment forKey:@"attachment"]; 37 | } 38 | return [data copy]; 39 | } 40 | 41 | + (instancetype)feedbackReplyWithContent:(NSString *)content type:(LCReplyType)type { 42 | LCUserFeedbackReply *feedbackReply = [[LCUserFeedbackReply alloc] init]; 43 | feedbackReply.content = content; 44 | feedbackReply.type = type; 45 | return feedbackReply; 46 | } 47 | 48 | + (instancetype)feedbackReplyWithAttachment:(NSString *)attachment type:(LCReplyType)type { 49 | LCUserFeedbackReply *feedbackReply = [[LCUserFeedbackReply alloc] init]; 50 | feedbackReply.attachment = attachment; 51 | feedbackReply.type = type; 52 | return feedbackReply; 53 | } 54 | 55 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary { 56 | LCUserFeedbackReply *feedbackReply = [[LCUserFeedbackReply alloc] init]; 57 | feedbackReply.content = [dictionary objectForKey:@"content"]; 58 | feedbackReply.createAt = [dictionary objectForKey:@"createdAt"]; 59 | NSString *type = [dictionary objectForKey:@"type"]; 60 | if ([type isEqualToString:@"user"]) { 61 | feedbackReply.type = LCReplyTypeUser; 62 | } else { 63 | feedbackReply.type = LCReplyTypeDev; 64 | } 65 | NSString *attachment = [dictionary objectForKey:@"attachment"]; 66 | feedbackReply.attachment = attachment; 67 | return feedbackReply; 68 | } 69 | 70 | - (LCContentType)contentType { 71 | if (self.attachment) { 72 | return LCContentTypeImage; 73 | } else { 74 | return LCContentTypeText; 75 | } 76 | } 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /LeanCloudFeedbackDemo/LeanCloudFeedbackDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // LeanCloudFeedbackDemo 4 | // 5 | // Created by Feng Junwen on 5/22/15. 6 | // Copyright (c) 2015 LeanCloud. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import 11 | 12 | @interface AppDelegate () 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | // Override point for customization after application launch. 21 | #warning 用公共账号登录 https://leancloud.cn ,账号/密码:leancloud@163.com/Public123 即可查看用户反馈 22 | NSString *appId = @"sl9sxfb9d9x0sc5g8c5wsv00f4nvztrgo5qcx4i4sjk1myn3"; 23 | NSString *appKey = @"ysz2l1zttl802m3qq8lvvqnmw8tnp4wiiuwe5xtydcjxrwtg"; 24 | [AVOSCloud setApplicationId:appId clientKey:appKey serverURLString:@"https://sl9sxfb9.lc-cn-n1-shared.com"]; 25 | 26 | [AVOSCloud setAllLogsEnabled:YES]; 27 | 28 | return YES; 29 | } 30 | 31 | - (void)applicationWillResignActive:(UIApplication *)application { 32 | // 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. 33 | // 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. 34 | } 35 | 36 | - (void)applicationDidEnterBackground:(UIApplication *)application { 37 | // 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. 38 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 39 | } 40 | 41 | - (void)applicationWillEnterForeground:(UIApplication *)application { 42 | // 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. 43 | } 44 | 45 | - (void)applicationDidBecomeActive:(UIApplication *)application { 46 | // 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. 47 | } 48 | 49 | - (void)applicationWillTerminate:(UIApplication *)application { 50 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /LeanCloudFeedbackDemo/LeanCloudFeedbackDemo/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /LeanCloudFeedback/LCUtils.m: -------------------------------------------------------------------------------- 1 | // 2 | // LCUtils.m 3 | // Feedback 4 | // 5 | // Created by Feng Junwen on 5/15/15. 6 | // Copyright (c) 2015 LeanCloud. All rights reserved. 7 | // 8 | 9 | #import "LCUtils.h" 10 | #import 11 | 12 | @implementation LCUtils 13 | 14 | #pragma mark - Safe way to call block 15 | 16 | #define safeBlock(first_param) \ 17 | if (block) { \ 18 | if ([NSThread isMainThread]) { \ 19 | block(first_param, error); \ 20 | }else {\ 21 | dispatch_async(dispatch_get_main_queue(), ^{ \ 22 | block(first_param, error); \ 23 | }); \ 24 | } \ 25 | } 26 | 27 | + (void)callBooleanResultBlock:(AVBooleanResultBlock)block 28 | error:(NSError *)error 29 | { 30 | safeBlock(error == nil); 31 | } 32 | 33 | + (void)callIntegerResultBlock:(AVIntegerResultBlock)block 34 | number:(NSInteger)number 35 | error:(NSError *)error { 36 | safeBlock(number); 37 | } 38 | 39 | + (void)callArrayResultBlock:(AVArrayResultBlock)block 40 | array:(NSArray *)array 41 | error:(NSError *)error { 42 | safeBlock(array); 43 | } 44 | 45 | + (void)callObjectResultBlock:(AVObjectResultBlock)block 46 | object:(AVObject *)object 47 | error:(NSError *)error { 48 | safeBlock(object); 49 | } 50 | 51 | + (void)callUserResultBlock:(AVUserResultBlock)block 52 | user:(AVUser *)user 53 | error:(NSError *)error { 54 | safeBlock(user); 55 | } 56 | 57 | + (void)callIdResultBlock:(AVIdResultBlock)block 58 | object:(id)object 59 | error:(NSError *)error { 60 | safeBlock(object); 61 | } 62 | 63 | + (void)callImageResultBlock:(AVImageResultBlock)block 64 | image:(UIImage *)image 65 | error:(NSError *)error 66 | { 67 | safeBlock(image); 68 | } 69 | 70 | + (void)callFileResultBlock:(AVFileResultBlock)block 71 | AVFile:(AVFile *)file 72 | error:(NSError *)error 73 | { 74 | safeBlock(file); 75 | } 76 | 77 | + (void)callProgressBlock:(AVProgressBlock)block 78 | percent:(NSInteger)percentDone { 79 | if (block) { 80 | if ([NSThread isMainThread]) { 81 | block(percentDone); 82 | }else { 83 | dispatch_async(dispatch_get_main_queue(), ^{ 84 | block(percentDone); 85 | }); 86 | } 87 | } 88 | } 89 | 90 | +(void)callSetResultBlock:(AVSetResultBlock)block 91 | set:(NSSet *)set 92 | error:(NSError *)error 93 | { 94 | if (block) { 95 | if ([NSThread isMainThread]) { 96 | block(set, error); 97 | }else { 98 | dispatch_async(dispatch_get_main_queue(), ^{ 99 | block(set, error); 100 | }); 101 | } 102 | } 103 | } 104 | 105 | +(void)callCloudQueryResultBlock:(AVCloudQueryCallback)block 106 | result:(AVCloudQueryResult *)result 107 | error:error { 108 | safeBlock(result); 109 | } 110 | 111 | + (NSString*)calMD5:(NSString *)input { 112 | const char *cstr = [input UTF8String]; 113 | unsigned char result[16]; 114 | CC_MD5(cstr, (CC_LONG)strlen(cstr), result); 115 | 116 | return [[NSString stringWithFormat: 117 | @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", 118 | result[0], result[1], result[2], result[3], 119 | result[4], result[5], result[6], result[7], 120 | result[8], result[9], result[10], result[11], 121 | result[12], result[13], result[14], result[15] 122 | ] lowercaseString]; 123 | } 124 | 125 | + (NSError *)errorWithText:(NSString *)format, ... NS_FORMAT_FUNCTION(1, 2) { 126 | va_list ap; 127 | va_start(ap, format); 128 | NSDictionary *errorInfo = @{NSLocalizedDescriptionKey : [[NSString alloc] initWithFormat:format arguments:ap]}; 129 | va_end(ap); 130 | NSError *error = [NSError errorWithDomain:@"LeanCloudFeedback Domain" code:0 userInfo:errorInfo]; 131 | return error; 132 | } 133 | 134 | @end 135 | 136 | -------------------------------------------------------------------------------- /LeanCloudFeedback/LCUserFeedbackAgent.m: -------------------------------------------------------------------------------- 1 | // 2 | // LCUserFeedbackAgent.m 3 | // Feedback 4 | // 5 | // Created by yang chaozhong on 4/22/14. 6 | // Copyright (c) 2014 LeanCloud. All rights reserved. 7 | // 8 | 9 | #import "LCUserFeedbackAgent.h" 10 | #import "LCUserFeedbackThread.h" 11 | #import "LCUserFeedbackReply.h" 12 | #import "LCUserFeedbackViewController.h" 13 | #import "LCUtils.h" 14 | 15 | @interface LCUserFeedbackAgent() 16 | 17 | @property(nonatomic, retain) LCUserFeedbackThread *userFeedback; 18 | 19 | @end 20 | 21 | @implementation LCUserFeedbackAgent 22 | 23 | + (instancetype)sharedInstance { 24 | static dispatch_once_t once; 25 | static LCUserFeedbackAgent * sharedInstance; 26 | dispatch_once(&once, ^{ 27 | sharedInstance = [[self alloc] init]; 28 | }); 29 | return sharedInstance; 30 | } 31 | 32 | - (void)showConversations:(UIViewController *)viewController title:(NSString *)title contact:(NSString *)contact{ 33 | LCUserFeedbackViewController *feedbackViewController = [[LCUserFeedbackViewController alloc] init]; 34 | feedbackViewController.feedbackTitle = title; 35 | feedbackViewController.contact = contact; 36 | 37 | UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:feedbackViewController]; 38 | [viewController presentViewController:navigationController animated:YES completion:^{ 39 | }]; 40 | } 41 | 42 | - (void)syncFeedbackThreadsWithBlock:(NSString *)title contact:(NSString *)contact block:(AVArrayResultBlock)block { 43 | [self syncFeedbackThreadsWithContact:contact block:block]; 44 | } 45 | 46 | - (void)syncFeedbackThreadsWithContact:(NSString *)contact block:(AVArrayResultBlock)block { 47 | [LCUserFeedbackThread fetchFeedbackWithContact:contact withBlock:^(id object, NSError *error) { 48 | if (!error) { 49 | if (object) { 50 | NSDictionary *dict = [[object objectForKey:@"results"] firstObject]; 51 | self.userFeedback = [[LCUserFeedbackThread alloc] initWithDictionary:dict]; 52 | 53 | [self.userFeedback fetchFeedbackRepliesInBackgroundWithBlock:^(NSArray *objects, NSError *error) { 54 | [LCUtils callArrayResultBlock:block array:objects error:error]; 55 | }]; 56 | } else { 57 | [LCUtils callArrayResultBlock:block array:[NSArray array] error:nil]; 58 | } 59 | } else { 60 | [LCUtils callIdResultBlock:block object:object error:error]; 61 | } 62 | }]; 63 | } 64 | 65 | - (void)postFeedbackThread:(NSString *)content block:(AVIdResultBlock)block { 66 | [self postFeedbackThread:content contact:nil block:block]; 67 | } 68 | 69 | - (void)postFeedbackThread:(NSString *)content contact:(NSString *)contact block:(AVIdResultBlock)block { 70 | if ([_userFeedback objectId]) { 71 | LCUserFeedbackReply *feedbackReply = [LCUserFeedbackReply feedbackReplyWithContent:content type:LCReplyTypeUser]; 72 | [self.userFeedback saveFeedbackReplyInBackground:feedbackReply withBlock:^(id object, NSError *error) { 73 | [LCUtils callIdResultBlock:block object:object error:error]; 74 | }]; 75 | } else { 76 | [LCUserFeedbackThread feedbackWithContent:content contact:contact withBlock:^(id object, NSError *error) { 77 | if (object && !error) { 78 | self.userFeedback = (LCUserFeedbackThread *)object; 79 | [self postFeedbackThread:content contact:contact block:block]; 80 | } else { 81 | [LCUtils callIdResultBlock:block object:nil error:error]; 82 | } 83 | }]; 84 | } 85 | } 86 | 87 | - (void)countUnreadFeedbackThreadsWithBlock:(AVIntegerResultBlock)block { 88 | [LCUserFeedbackThread fetchFeedbackWithBlock:^(LCUserFeedbackThread *feedback, NSError *error) { 89 | if (error) { 90 | block(0, error); 91 | } else { 92 | if (feedback == nil) { 93 | block(0, nil); 94 | } else { 95 | NSString *localKey = [NSString stringWithFormat:@"feedback_%@", feedback.objectId]; 96 | [feedback fetchFeedbackRepliesInBackgroundWithBlock:^(NSArray *objects, NSError *error) { 97 | if (error) { 98 | block(0 ,error); 99 | } else { 100 | NSUInteger lastThreadsCounts = [[[NSUserDefaults standardUserDefaults] objectForKey:localKey] intValue]; 101 | NSUInteger unreadCount = objects.count - lastThreadsCounts; 102 | block(unreadCount, nil); 103 | } 104 | }]; 105 | } 106 | } 107 | }]; 108 | } 109 | 110 | @end 111 | -------------------------------------------------------------------------------- /LeanCloudFeedback/LCHttpClient.m: -------------------------------------------------------------------------------- 1 | // 2 | // LCHttpClient.m 3 | // Feedback 4 | // 5 | // Created by Feng Junwen on 5/15/15. 6 | // Copyright (c) 2015 LeanCloud. All rights reserved. 7 | // 8 | 9 | #import "LCHttpClient.h" 10 | #import "LCUtils.h" 11 | #import "LCRouter.h" 12 | 13 | @interface LCHttpClient () 14 | 15 | @property (nonatomic, strong) NSURL *baseURL; 16 | @property (nonatomic, strong) NSOperationQueue *operationQueue; 17 | 18 | @end 19 | 20 | @implementation LCHttpClient 21 | 22 | + (LCHttpClient*)sharedInstance { 23 | static dispatch_once_t once; 24 | static LCHttpClient * sharedInstance; 25 | dispatch_once(&once, ^{ 26 | sharedInstance = [[self alloc] init]; 27 | sharedInstance.baseURL = [NSURL URLWithString:@"https://api.leancloud.cn/1.1/"]; 28 | sharedInstance.operationQueue = [[NSOperationQueue alloc] init]; 29 | }); 30 | return sharedInstance; 31 | } 32 | 33 | - (NSString *)queryStringFromParameters:(NSDictionary *)parameters { 34 | NSMutableString *queries = [[NSMutableString alloc] init]; 35 | NSArray *keys = [parameters allKeys]; 36 | for (int i = 0; i < keys.count; i++) { 37 | if (i != 0) { 38 | [queries appendString:@"&"]; 39 | } 40 | NSString *value = [parameters valueForKey:keys[i]]; 41 | [queries appendFormat:@"%@=%@", keys[i], value]; 42 | } 43 | return [queries stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 44 | } 45 | 46 | - (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters { 47 | 48 | NSURL *url = [NSURL URLWithString:path]; 49 | 50 | if (!url.scheme.length) { 51 | #pragma clang diagnostic push 52 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 53 | NSString *URLString = [[LCRouter sharedInstance] URLStringForPath:path]; 54 | #pragma clang diagnostic pop 55 | url = [NSURL URLWithString:URLString]; 56 | } 57 | 58 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; 59 | [request setValue:[AVOSCloud getApplicationId] forHTTPHeaderField:@"X-LC-Id"]; 60 | 61 | NSString *timestamp=[NSString stringWithFormat:@"%.0f",1000*[[NSDate date] timeIntervalSince1970]]; 62 | NSString *sign=[LCUtils calMD5:[NSString stringWithFormat:@"%@%@",timestamp,[AVOSCloud getClientKey]]]; 63 | NSString *headerValue=[NSString stringWithFormat:@"%@,%@",sign,timestamp]; 64 | [request setValue:headerValue forHTTPHeaderField:@"X-LC-Sign"]; 65 | [request setValue:@"application/json" forHTTPHeaderField:@"Accept"]; 66 | 67 | if ([AVUser currentUser].sessionToken) { 68 | [request setValue:[AVUser currentUser].sessionToken forHTTPHeaderField:@"X-LC-Session"]; 69 | } 70 | 71 | [request setHTTPMethod:method]; 72 | if ([method isEqualToString:@"GET"] || [method isEqualToString:@"DELETE"]) { 73 | url = [NSURL URLWithString:[[url absoluteString] stringByAppendingFormat:@"?%@", [self queryStringFromParameters:parameters]]]; 74 | [request setURL:url]; 75 | } else { 76 | [request setValue:[NSString stringWithFormat:@"application/json"] forHTTPHeaderField:@"Content-Type"]; 77 | NSError *error; 78 | [request setHTTPBody:[NSJSONSerialization dataWithJSONObject:parameters options:0 error:&error]]; 79 | if (error) { 80 | FLog(@"%@ error : %@", [self class], error); 81 | } 82 | } 83 | return request; 84 | } 85 | 86 | - (void)goRequest:(NSURLRequest *)request block:(AVIdResultBlock)block { 87 | FLog(@"request url : %@", request.URL); 88 | FLog(@"request headers : %@", [request allHTTPHeaderFields]); 89 | FLog(@"request body %@", [[NSString alloc] initWithData:[request HTTPBody] encoding:NSUTF8StringEncoding]); 90 | [NSURLConnection sendAsynchronousRequest:request queue:self.operationQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { 91 | if (connectionError) { 92 | block(nil, connectionError); 93 | } else { 94 | if (response && data) { 95 | NSError *error; 96 | NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; 97 | if (error) { 98 | NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 99 | FLog(@"reponse : %@", responseString); 100 | block(nil, [LCUtils errorWithText:@"Http request failed, reponse string : %@", responseString]); 101 | } else { 102 | block(dictionary, nil); 103 | } 104 | } 105 | } 106 | }]; 107 | } 108 | 109 | - (NSDictionary *)whereDictionaryFromConditions:(NSDictionary *)conditions { 110 | NSDictionary *where = [NSDictionary dictionary]; 111 | if (conditions.count>0) { 112 | NSData *data = [NSJSONSerialization dataWithJSONObject:conditions options:0 error:nil]; 113 | NSString *conditionString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 114 | where = @{@"where":conditionString}; 115 | } 116 | return where; 117 | } 118 | 119 | -(void)getObject:(NSString *)path 120 | withParameters:(NSDictionary *)parameters 121 | block:(AVIdResultBlock)block { 122 | NSMutableURLRequest *request = [self requestWithMethod:@"GET" path:path parameters:[self whereDictionaryFromConditions:parameters]]; 123 | [self goRequest:request block:block]; 124 | } 125 | 126 | -(void)postObject:(NSString *)path 127 | withParameters:(NSDictionary *)parameters 128 | block:(AVIdResultBlock)block 129 | { 130 | NSMutableURLRequest *request= [self requestWithMethod:@"POST" path:path parameters:parameters]; 131 | [self goRequest:request block:block]; 132 | } 133 | 134 | -(void)putObject:(NSString *)path 135 | withParameters:(NSDictionary *)parameters 136 | block:(AVIdResultBlock)block { 137 | NSMutableURLRequest *request = [self requestWithMethod:@"PUT" path:path parameters:parameters]; 138 | [self goRequest:request block:block]; 139 | } 140 | 141 | -(void)deleteObject:(NSString *)path 142 | withParameters:(NSDictionary *)parameters 143 | block:(AVIdResultBlock)block { 144 | NSMutableURLRequest *request = [self requestWithMethod:@"DELETE" path:path parameters:parameters]; 145 | [self goRequest:request block:block]; 146 | } 147 | 148 | @end 149 | -------------------------------------------------------------------------------- /LeanCloudFeedbackDemo/LeanCloudFeedbackDemo/Base.lproj/Main.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 | 45 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /LeanCloudFeedback/LCUserFeedbackThread.m: -------------------------------------------------------------------------------- 1 | // 2 | // LCUserFeedbackThread.m 3 | // Feedback 4 | // 5 | // Created by yang chaozhong on 4/21/14. 6 | // Copyright (c) 2014 LeanCloud. All rights reserved. 7 | // 8 | 9 | #import "LCUserFeedbackThread.h" 10 | #import "LCUserFeedbackThread_Internal.h" 11 | #import "LCUserFeedbackReply.h" 12 | #import "LCUserFeedbackReply_Internal.h" 13 | #import "LCHttpClient.h" 14 | #import "LCUtils.h" 15 | 16 | static NSString *const kLCUserFeedbackObjectId = @"LCUserFeedbackObjectId"; 17 | 18 | @interface LCUserFeedbackThread() 19 | 20 | @property(nonatomic, copy) NSString *appSign; 21 | @property(nonatomic, copy) NSString *status; 22 | @property(nonatomic, copy) NSString *remarks; 23 | @property(nonatomic, copy) NSString *iid; 24 | 25 | @end 26 | 27 | #define LC_FEEDBACK_BASE_PATH @"feedback" 28 | 29 | @implementation LCUserFeedbackThread 30 | 31 | + (NSString *)objectPath { 32 | return LC_FEEDBACK_BASE_PATH; 33 | } 34 | 35 | - (NSString *)threadsPath { 36 | return [NSString stringWithFormat:@"%@/%@/threads", LC_FEEDBACK_BASE_PATH, self.objectId]; 37 | } 38 | 39 | -(NSMutableDictionary *)postData { 40 | NSMutableDictionary *data = [[NSMutableDictionary alloc] init]; 41 | 42 | if (_content) { 43 | [data setObject:self.content forKey:@"content"]; 44 | } 45 | 46 | if (_contact) { 47 | [data setObject:self.contact forKey:@"contact"]; 48 | } 49 | 50 | if (_status) { 51 | [data setObject:self.status forKey:@"status"]; 52 | } 53 | 54 | if (_remarks) { 55 | [data setObject:self.remarks forKey:@"remarks"]; 56 | } 57 | _iid = [AVInstallation defaultInstallation].objectId; 58 | if (_iid) { 59 | [data setObject:self.iid forKey:@"iid"]; 60 | } 61 | 62 | return data; 63 | } 64 | 65 | -(instancetype)initWithDictionary:(NSDictionary*)dict { 66 | self = [super init]; 67 | if (self && dict) { 68 | self.contact = [dict objectForKey:@"contact"]; 69 | self.content = [dict objectForKey:@"content"]; 70 | self.status = [dict objectForKey:@"status"]; 71 | self.iid = [dict objectForKey:@"iid"]; 72 | self.remarks = [dict objectForKey:@"remarks"]; 73 | self.objectId = [dict objectForKey:@"objectId"]; 74 | } 75 | return self; 76 | } 77 | 78 | +(void)fetchFeedbackWithBlock:(LCUserFeedbackBlock)block { 79 | NSString *feedbackObjectId = [[NSUserDefaults standardUserDefaults] objectForKey:kLCUserFeedbackObjectId]; 80 | if (feedbackObjectId == nil) { 81 | // do not create empty feedback 82 | block(nil, nil); 83 | } else { 84 | LCHttpClient *client = [LCHttpClient sharedInstance]; 85 | NSString *path = [[LCUserFeedbackThread objectPath] stringByAppendingPathComponent:feedbackObjectId]; 86 | [client getObject:path withParameters:nil block:^(id object, NSError *error) { 87 | if (error) { 88 | [LCUtils callIdResultBlock:block object:nil error:error]; 89 | } else { 90 | if (object) { 91 | LCUserFeedbackThread *feedback = [[LCUserFeedbackThread alloc] initWithDictionary:object]; 92 | [LCUtils callIdResultBlock:block object:feedback error:nil]; 93 | } else { 94 | [LCUtils callIdResultBlock:block object:nil error:nil]; 95 | } 96 | } 97 | }]; 98 | } 99 | } 100 | 101 | +(void)fetchFeedbackWithContact:(NSString*)contact withBlock:(AVIdResultBlock)block { 102 | LCHttpClient *client = [LCHttpClient sharedInstance]; 103 | [client getObject:[LCUserFeedbackThread objectPath] withParameters:[NSDictionary dictionaryWithObject:contact forKey:@"contact"] block:^(id object, NSError *error) { 104 | if (error) { 105 | [LCUtils callIdResultBlock:block object:nil error:error]; 106 | } else { 107 | [LCUtils callIdResultBlock:block object:object error:nil]; 108 | } 109 | }]; 110 | } 111 | 112 | +(void)feedbackWithContent:(NSString *)content 113 | contact:(NSString *)contact 114 | create:(BOOL)create 115 | withBlock:(AVIdResultBlock)block { 116 | if (create) { 117 | LCUserFeedbackThread *feedback = [[LCUserFeedbackThread alloc] init]; 118 | feedback.content = content; 119 | feedback.contact = contact; 120 | 121 | LCHttpClient *client = [LCHttpClient sharedInstance]; 122 | [client postObject:[LCUserFeedbackThread objectPath] withParameters:[feedback postData] block:^(id object, NSError *error) { 123 | if (!error) { 124 | feedback.objectId = [(NSDictionary *)object objectForKey:@"objectId"]; 125 | [[NSUserDefaults standardUserDefaults] setObject:feedback.objectId forKey:kLCUserFeedbackObjectId]; 126 | [LCUtils callIdResultBlock:block object:feedback error:error]; 127 | } else { 128 | [LCUtils callIdResultBlock:block object:nil error:error]; 129 | } 130 | }]; 131 | } else { 132 | [LCUtils callIdResultBlock:block object:nil error:[NSError new]]; 133 | } 134 | } 135 | 136 | +(void)feedbackWithContent:(NSString *)content 137 | contact:(NSString *)contact 138 | withBlock:(AVIdResultBlock)block { 139 | [self feedbackWithContent:content contact:contact create:YES withBlock:block]; 140 | } 141 | 142 | 143 | +(void)updateFeedback:(LCUserFeedbackThread *)feedback withBlock:(AVIdResultBlock)block { 144 | [[LCHttpClient sharedInstance] putObject:[NSString stringWithFormat:@"%@/%@", [LCUserFeedbackThread objectPath], feedback.objectId] 145 | withParameters:[feedback postData] 146 | block:^(id object, NSError *error) { 147 | [LCUtils callIdResultBlock:block object:object error:error]; 148 | }]; 149 | } 150 | 151 | +(void)deleteFeedback:(LCUserFeedbackThread *)feedback withBlock:(AVIdResultBlock)block { 152 | [[LCHttpClient sharedInstance] deleteObject:[NSString stringWithFormat:@"%@/%@", [LCUserFeedbackThread objectPath], feedback.objectId] 153 | withParameters:nil 154 | block:^(id object, NSError *error) { 155 | [LCUtils callIdResultBlock:block object:object error:error]; 156 | }]; 157 | } 158 | 159 | - (NSDictionary *)parametersWithFeedbackReply:(LCUserFeedbackReply *)feedbackReply { 160 | NSMutableDictionary *parameters = [[feedbackReply dictionary] mutableCopy]; 161 | 162 | parameters[@"feedback"] = self.objectId; 163 | 164 | return [parameters copy]; 165 | } 166 | 167 | - (void)saveFeedbackReplyInBackground:(LCUserFeedbackReply *)feedbackReply withBlock:(AVIdResultBlock)block { 168 | if (!self.objectId) return; 169 | [[LCHttpClient sharedInstance] postObject:[self threadsPath] withParameters:[self parametersWithFeedbackReply:feedbackReply] block:^(id object, NSError *error){ 170 | if (!error) { 171 | feedbackReply.createAt = [object objectForKey:@"createdAt"]; 172 | } 173 | [LCUtils callIdResultBlock:block object:object error:error]; 174 | }]; 175 | } 176 | 177 | - (void)fetchFeedbackRepliesInBackgroundWithBlock:(AVArrayResultBlock)block { 178 | [[LCHttpClient sharedInstance] getObject:[self threadsPath] 179 | withParameters:nil 180 | block:^(id object, NSError *error) 181 | { 182 | NSArray *results = [object objectForKey:@"results"]; 183 | NSMutableArray *replies = [NSMutableArray arrayWithCapacity:[results count]]; 184 | 185 | for (NSDictionary *dictionary in results) { 186 | [replies addObject:[[LCUserFeedbackReply alloc] initWithDictionary:dictionary]]; 187 | } 188 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 189 | for (LCUserFeedbackReply *reply in replies) { 190 | if (reply.attachment) { 191 | NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:reply.attachment]]; 192 | if (data) { 193 | reply.attachmentImage = [UIImage imageWithData:data]; 194 | } else { 195 | FLog(@"attachment getData error"); 196 | } 197 | } 198 | } 199 | dispatch_async(dispatch_get_main_queue(), ^{ 200 | [LCUtils callArrayResultBlock:block array:replies error:error]; 201 | }); 202 | }); 203 | }]; 204 | } 205 | 206 | @end 207 | -------------------------------------------------------------------------------- /LeanCloudFeedback/LCUserFeedbackReplyCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // LCUserFeedbackCell.m 3 | // 4 | // Created by yang chaozhong on 4/24/14. 5 | // Copyright (c) 2014 LeanCloud. All rights reserved. 6 | // 7 | 8 | #import "LCUserFeedbackReplyCell.h" 9 | #import "LCUserFeedbackReply.h" 10 | 11 | static CGFloat const kLabelVerticalPadding = 10; 12 | static CGFloat const kTimestampLabelHeight = 18; 13 | static CGFloat const kMaxiumCellWidth = 226; 14 | static CGFloat const kBubbleHorizontalPadding = 10; 15 | static CGFloat const kContentHorizontalPadding = 10; 16 | static CGFloat const kContentVerticalPadding = 10; 17 | 18 | static CGFloat const kBubbleArrowHeight = 14; /**< 气泡尖嘴的高度 */ 19 | 20 | @interface LCUserFeedbackReplyCell() 21 | 22 | @property (nonatomic, strong) UILabel *timestampLabel; 23 | 24 | @property (nonatomic, strong) UIImageView *bubbleImageView; 25 | 26 | @property (nonatomic, strong) LCUserFeedbackReply *feedbackReply; 27 | 28 | @end 29 | 30 | @implementation LCUserFeedbackReplyCell 31 | 32 | #pragma mark - life cycle 33 | 34 | - (id)initWithFeedbackReply:(LCUserFeedbackReply *)reply reuseIdentifier:(NSString *)reuseIdentifier { 35 | self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier]; 36 | if (self) { 37 | [self setupWithFeedbackReply:reply]; 38 | } 39 | return self; 40 | } 41 | 42 | - (void)setupWithFeedbackReply:(LCUserFeedbackReply *)reply { 43 | self.backgroundColor = [UIColor clearColor]; 44 | [self setupTextLabel]; 45 | [self setupImageView]; 46 | [self.contentView addSubview:self.timestampLabel]; 47 | switch (reply.contentType) { 48 | case LCContentTypeText: 49 | [self.contentView insertSubview:self.bubbleImageView belowSubview:self.textLabel]; 50 | break; 51 | case LCContentTypeImage: 52 | [self.contentView insertSubview:self.bubbleImageView belowSubview:self.imageView]; 53 | break; 54 | } 55 | self.selectionStyle = UITableViewCellSelectionStyleNone; 56 | } 57 | 58 | #pragma mark - property 59 | 60 | - (void)setupTextLabel { 61 | if ([[UIDevice currentDevice].systemVersion floatValue] < 7.0f) { 62 | self.textLabel.backgroundColor = [UIColor whiteColor]; 63 | } 64 | self.textLabel.font = [[LCUserFeedbackReplyCell appearance] cellFont]; 65 | self.textLabel.lineBreakMode = NSLineBreakByWordWrapping; 66 | self.textLabel.numberOfLines = 0; 67 | self.textLabel.textAlignment = NSTextAlignmentLeft; 68 | self.textLabel.textColor = [UIColor blackColor]; 69 | } 70 | 71 | - (void)setupImageView { 72 | self.imageView.contentMode = UIViewContentModeScaleAspectFill; 73 | self.imageView.clipsToBounds = YES; 74 | self.imageView.userInteractionEnabled = YES; 75 | } 76 | 77 | - (UILabel *)timestampLabel { 78 | if (_timestampLabel == nil) { 79 | _timestampLabel = [[UILabel alloc] init]; 80 | _timestampLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; 81 | _timestampLabel.textAlignment = NSTextAlignmentCenter; 82 | _timestampLabel.backgroundColor = [UIColor clearColor]; 83 | _timestampLabel.font = [UIFont systemFontOfSize:9.0f]; 84 | _timestampLabel.textColor = [UIColor colorWithRed:0.4 green:0.4 blue:0.4 alpha:1.0]; 85 | _timestampLabel.frame = CGRectMake(0.0f, kLabelVerticalPadding, self.bounds.size.width, kTimestampLabelHeight); 86 | } 87 | return _timestampLabel; 88 | } 89 | 90 | - (UIImageView *)bubbleImageView { 91 | if (_bubbleImageView == nil) { 92 | _bubbleImageView = [[UIImageView alloc] initWithFrame:CGRectZero]; 93 | } 94 | return _bubbleImageView; 95 | } 96 | 97 | #pragma mark - size or frame 98 | 99 | + (CGSize)getBubbleSizeWithReply:(LCUserFeedbackReply *)reply { 100 | CGSize bubbleSize; 101 | switch (reply.contentType) { 102 | case LCContentTypeText: { 103 | bubbleSize = [self caculateLabelSizeForText:reply.content]; 104 | break; 105 | } 106 | case LCContentTypeImage: 107 | bubbleSize = CGSizeMake(120, 120); 108 | break; 109 | } 110 | bubbleSize = CGSizeMake(bubbleSize.width + kContentHorizontalPadding * 2, bubbleSize.height + kContentVerticalPadding * 2); 111 | bubbleSize.height += kBubbleArrowHeight; 112 | return bubbleSize; 113 | } 114 | 115 | + (CGFloat)heightForFeedbackReply:(LCUserFeedbackReply *)reply { 116 | CGFloat height = kTimestampLabelHeight + 2 * kLabelVerticalPadding; 117 | CGSize bubbleSize = [self getBubbleSizeWithReply:reply]; 118 | height += bubbleSize.height; 119 | return height; 120 | } 121 | 122 | - (CGRect)bubbleFrame { 123 | CGSize bubbleSize = [[self class] getBubbleSizeWithReply:self.feedbackReply]; 124 | 125 | CGFloat paddingX = kBubbleHorizontalPadding; 126 | if (self.feedbackReply.type == LCReplyTypeUser) { 127 | paddingX = CGRectGetWidth(self.bounds) - bubbleSize.width - kBubbleHorizontalPadding; 128 | } 129 | CGFloat originY = CGRectGetMaxY(self.timestampLabel.frame) + kLabelVerticalPadding; 130 | return CGRectMake(paddingX, originY, bubbleSize.width, bubbleSize.height); 131 | } 132 | /** 133 | * 气泡去除掉尖嘴的 frame 134 | */ 135 | - (CGRect)bubbleContentFrame { 136 | CGRect contentFrame = [self bubbleFrame]; 137 | switch (self.feedbackReply.type) { 138 | case LCReplyTypeDev: { 139 | // 尖嘴在上方 140 | contentFrame.origin.y += kBubbleArrowHeight; 141 | contentFrame.size.height -= kBubbleArrowHeight; 142 | } 143 | break; 144 | case LCReplyTypeUser: 145 | // 尖嘴在下方 146 | contentFrame.size.height -= kBubbleArrowHeight; 147 | break; 148 | } 149 | return contentFrame; 150 | } 151 | 152 | #pragma mark - configuare 153 | 154 | - (void)configuareCellWithFeedbackReply:(LCUserFeedbackReply *)reply { 155 | for (UIGestureRecognizer *recognizer in self.contentView.gestureRecognizers) { 156 | [self.imageView removeGestureRecognizer:recognizer]; 157 | } 158 | 159 | self.feedbackReply = reply; 160 | self.bubbleImageView.image = [self backgroundImageForFeedbackReply:reply]; 161 | self.timestampLabel.text = [self formatDateString:reply.createAt]; 162 | switch (reply.contentType) { 163 | case LCContentTypeText: 164 | self.textLabel.text = reply.content; 165 | break; 166 | case LCContentTypeImage: 167 | self.imageView.image = reply.attachmentImage; 168 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGestureRecognizer:)]; 169 | tap.delegate = self; 170 | tap.cancelsTouchesInView = NO; 171 | [self.imageView addGestureRecognizer:tap]; 172 | break; 173 | } 174 | } 175 | 176 | - (void)layoutSubviews { 177 | [super layoutSubviews]; 178 | 179 | CGRect bubbleFrame = [self bubbleFrame]; 180 | self.bubbleImageView.frame = bubbleFrame; 181 | CGRect contentFrame = [self bubbleContentFrame]; 182 | CGRect innerContentFrame = CGRectMake(CGRectGetMinX(contentFrame) + kContentHorizontalPadding, CGRectGetMinY(contentFrame) + kContentVerticalPadding, CGRectGetWidth(contentFrame) - kContentHorizontalPadding * 2, CGRectGetHeight(contentFrame) - kContentVerticalPadding * 2); 183 | switch (self.feedbackReply.contentType) { 184 | case LCContentTypeText: { 185 | self.textLabel.frame = innerContentFrame; 186 | break; 187 | } 188 | case LCContentTypeImage: { 189 | self.imageView.frame = innerContentFrame; 190 | break; 191 | } 192 | } 193 | } 194 | 195 | - (void)prepareForReuse { 196 | [super prepareForReuse]; 197 | self.timestampLabel.text = nil; 198 | self.textLabel.text = nil; 199 | self.imageView.image = nil; 200 | self.bubbleImageView.image = nil; 201 | } 202 | 203 | #pragma mark - action 204 | 205 | - (void)handleTapGestureRecognizer:(UITapGestureRecognizer *)tap { 206 | if (tap.state == UIGestureRecognizerStateEnded) { 207 | if ([self.delegate respondsToSelector:@selector(didSelectImageViewOnFeedbackReply:)]) { 208 | [self.delegate didSelectImageViewOnFeedbackReply:self.feedbackReply]; 209 | } 210 | } 211 | } 212 | 213 | #pragma mark - utils 214 | 215 | + (CGSize)caculateLabelSizeForText:(NSString *)text { 216 | UILabel *gettingSizeLabel = [[UILabel alloc] init]; 217 | gettingSizeLabel.font = [[LCUserFeedbackReplyCell appearance] cellFont]; 218 | gettingSizeLabel.lineBreakMode = NSLineBreakByWordWrapping; 219 | gettingSizeLabel.numberOfLines = 0; 220 | gettingSizeLabel.text = text; 221 | CGSize maxiumLabelSize = CGSizeMake(kMaxiumCellWidth, MAXFLOAT); 222 | return [gettingSizeLabel sizeThatFits:maxiumLabelSize]; 223 | } 224 | 225 | - (UIImage *)backgroundImageForFeedbackReply:(LCUserFeedbackReply *)reply { 226 | if (reply.type == LCReplyTypeDev) { 227 | return [[UIImage imageNamed:@"feedback_bg_1"] stretchableImageWithLeftCapWidth:20 topCapHeight:16]; 228 | } else { 229 | return [[UIImage imageNamed:@"feedback_bg_2"] stretchableImageWithLeftCapWidth:1 topCapHeight:16];; 230 | } 231 | } 232 | 233 | - (NSString *)formatDateString:(NSString *)dateString { 234 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 235 | [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; 236 | [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZ"]; 237 | NSDate *date = [dateFormatter dateFromString:dateString]; 238 | 239 | dateFormatter = [[NSDateFormatter alloc] init]; 240 | [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm"]; 241 | return [dateFormatter stringFromDate:date]; 242 | } 243 | 244 | #pragma mark - 245 | 246 | - (void)awakeFromNib 247 | { 248 | [super awakeFromNib]; 249 | // Initialization code 250 | } 251 | 252 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated 253 | { 254 | [super setSelected:selected animated:animated]; 255 | 256 | // Configure the view for the selected state 257 | } 258 | 259 | 260 | @end 261 | -------------------------------------------------------------------------------- /LeanCloudFeedbackDemo/LeanCloudFeedbackDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5CC504E71B0F01D3004D0CB1 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CC504E61B0F01D3004D0CB1 /* main.m */; }; 11 | 5CC504EA1B0F01D3004D0CB1 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CC504E91B0F01D3004D0CB1 /* AppDelegate.m */; }; 12 | 5CC504ED1B0F01D3004D0CB1 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CC504EC1B0F01D3004D0CB1 /* ViewController.m */; }; 13 | 5CC504F01B0F01D3004D0CB1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5CC504EE1B0F01D3004D0CB1 /* Main.storyboard */; }; 14 | 5CC504F21B0F01D3004D0CB1 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5CC504F11B0F01D3004D0CB1 /* Images.xcassets */; }; 15 | 840DDC8E9FF039EA6EE94011 /* libPods-LeanCloudFeedbackDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C033AA2C00DA993AA57FB14 /* libPods-LeanCloudFeedbackDemo.a */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 3C033AA2C00DA993AA57FB14 /* libPods-LeanCloudFeedbackDemo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-LeanCloudFeedbackDemo.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | 5CC504E11B0F01D3004D0CB1 /* LeanCloudFeedbackDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LeanCloudFeedbackDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | 5CC504E51B0F01D3004D0CB1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 22 | 5CC504E61B0F01D3004D0CB1 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 23 | 5CC504E81B0F01D3004D0CB1 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 24 | 5CC504E91B0F01D3004D0CB1 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 25 | 5CC504EB1B0F01D3004D0CB1 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 26 | 5CC504EC1B0F01D3004D0CB1 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 27 | 5CC504EF1B0F01D3004D0CB1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 28 | 5CC504F11B0F01D3004D0CB1 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 29 | 5CC504F41B0F01D3004D0CB1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 30 | 5CC504FF1B0F01D3004D0CB1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 31 | 5CC505001B0F01D3004D0CB1 /* LeanCloudFeedbackDemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LeanCloudFeedbackDemoTests.m; sourceTree = ""; }; 32 | 7465E688A3D8D6072420F996 /* Pods-LeanCloudFeedbackDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LeanCloudFeedbackDemo.release.xcconfig"; path = "../Pods/Target Support Files/Pods-LeanCloudFeedbackDemo/Pods-LeanCloudFeedbackDemo.release.xcconfig"; sourceTree = ""; }; 33 | C28BF2C6C8C5354952538019 /* Pods-LeanCloudFeedbackDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LeanCloudFeedbackDemo.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-LeanCloudFeedbackDemo/Pods-LeanCloudFeedbackDemo.debug.xcconfig"; sourceTree = ""; }; 34 | /* End PBXFileReference section */ 35 | 36 | /* Begin PBXFrameworksBuildPhase section */ 37 | 5CC504DE1B0F01D3004D0CB1 /* Frameworks */ = { 38 | isa = PBXFrameworksBuildPhase; 39 | buildActionMask = 2147483647; 40 | files = ( 41 | 840DDC8E9FF039EA6EE94011 /* libPods-LeanCloudFeedbackDemo.a in Frameworks */, 42 | ); 43 | runOnlyForDeploymentPostprocessing = 0; 44 | }; 45 | /* End PBXFrameworksBuildPhase section */ 46 | 47 | /* Begin PBXGroup section */ 48 | 009A4221AB1D8BBE1A8638BD /* Pods */ = { 49 | isa = PBXGroup; 50 | children = ( 51 | C28BF2C6C8C5354952538019 /* Pods-LeanCloudFeedbackDemo.debug.xcconfig */, 52 | 7465E688A3D8D6072420F996 /* Pods-LeanCloudFeedbackDemo.release.xcconfig */, 53 | ); 54 | name = Pods; 55 | sourceTree = ""; 56 | }; 57 | 5CC504D81B0F01D3004D0CB1 = { 58 | isa = PBXGroup; 59 | children = ( 60 | 5CC504E31B0F01D3004D0CB1 /* LeanCloudFeedbackDemo */, 61 | 5CC504FD1B0F01D3004D0CB1 /* LeanCloudFeedbackDemoTests */, 62 | 5CC504E21B0F01D3004D0CB1 /* Products */, 63 | 009A4221AB1D8BBE1A8638BD /* Pods */, 64 | 82A4E34BE877AC5780E8CC96 /* Frameworks */, 65 | ); 66 | sourceTree = ""; 67 | }; 68 | 5CC504E21B0F01D3004D0CB1 /* Products */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | 5CC504E11B0F01D3004D0CB1 /* LeanCloudFeedbackDemo.app */, 72 | ); 73 | name = Products; 74 | sourceTree = ""; 75 | }; 76 | 5CC504E31B0F01D3004D0CB1 /* LeanCloudFeedbackDemo */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 5CC504E81B0F01D3004D0CB1 /* AppDelegate.h */, 80 | 5CC504E91B0F01D3004D0CB1 /* AppDelegate.m */, 81 | 5CC504EB1B0F01D3004D0CB1 /* ViewController.h */, 82 | 5CC504EC1B0F01D3004D0CB1 /* ViewController.m */, 83 | 5CC504EE1B0F01D3004D0CB1 /* Main.storyboard */, 84 | 5CC504F11B0F01D3004D0CB1 /* Images.xcassets */, 85 | 5CC504F31B0F01D3004D0CB1 /* LaunchScreen.xib */, 86 | 5CC504E41B0F01D3004D0CB1 /* Supporting Files */, 87 | ); 88 | path = LeanCloudFeedbackDemo; 89 | sourceTree = ""; 90 | }; 91 | 5CC504E41B0F01D3004D0CB1 /* Supporting Files */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 5CC504E51B0F01D3004D0CB1 /* Info.plist */, 95 | 5CC504E61B0F01D3004D0CB1 /* main.m */, 96 | ); 97 | name = "Supporting Files"; 98 | sourceTree = ""; 99 | }; 100 | 5CC504FD1B0F01D3004D0CB1 /* LeanCloudFeedbackDemoTests */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 5CC505001B0F01D3004D0CB1 /* LeanCloudFeedbackDemoTests.m */, 104 | 5CC504FE1B0F01D3004D0CB1 /* Supporting Files */, 105 | ); 106 | path = LeanCloudFeedbackDemoTests; 107 | sourceTree = ""; 108 | }; 109 | 5CC504FE1B0F01D3004D0CB1 /* Supporting Files */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 5CC504FF1B0F01D3004D0CB1 /* Info.plist */, 113 | ); 114 | name = "Supporting Files"; 115 | sourceTree = ""; 116 | }; 117 | 82A4E34BE877AC5780E8CC96 /* Frameworks */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 3C033AA2C00DA993AA57FB14 /* libPods-LeanCloudFeedbackDemo.a */, 121 | ); 122 | name = Frameworks; 123 | sourceTree = ""; 124 | }; 125 | /* End PBXGroup section */ 126 | 127 | /* Begin PBXNativeTarget section */ 128 | 5CC504E01B0F01D3004D0CB1 /* LeanCloudFeedbackDemo */ = { 129 | isa = PBXNativeTarget; 130 | buildConfigurationList = 5CC505041B0F01D3004D0CB1 /* Build configuration list for PBXNativeTarget "LeanCloudFeedbackDemo" */; 131 | buildPhases = ( 132 | 4769E60FE17A8611CF49B3CF /* [CP] Check Pods Manifest.lock */, 133 | 5CC504DD1B0F01D3004D0CB1 /* Sources */, 134 | 5CC504DE1B0F01D3004D0CB1 /* Frameworks */, 135 | 5CC504DF1B0F01D3004D0CB1 /* Resources */, 136 | 3BC15B31DE1EE218A416CB6C /* [CP] Copy Pods Resources */, 137 | ); 138 | buildRules = ( 139 | ); 140 | dependencies = ( 141 | ); 142 | name = LeanCloudFeedbackDemo; 143 | productName = LeanCloudFeedbackDemo; 144 | productReference = 5CC504E11B0F01D3004D0CB1 /* LeanCloudFeedbackDemo.app */; 145 | productType = "com.apple.product-type.application"; 146 | }; 147 | /* End PBXNativeTarget section */ 148 | 149 | /* Begin PBXProject section */ 150 | 5CC504D91B0F01D3004D0CB1 /* Project object */ = { 151 | isa = PBXProject; 152 | attributes = { 153 | LastUpgradeCheck = 1120; 154 | ORGANIZATIONNAME = LeanCloud; 155 | TargetAttributes = { 156 | 5CC504E01B0F01D3004D0CB1 = { 157 | CreatedOnToolsVersion = 6.3.2; 158 | DevelopmentTeam = 7J5XFNL99Q; 159 | }; 160 | }; 161 | }; 162 | buildConfigurationList = 5CC504DC1B0F01D3004D0CB1 /* Build configuration list for PBXProject "LeanCloudFeedbackDemo" */; 163 | compatibilityVersion = "Xcode 3.2"; 164 | developmentRegion = English; 165 | hasScannedForEncodings = 0; 166 | knownRegions = ( 167 | English, 168 | en, 169 | Base, 170 | ); 171 | mainGroup = 5CC504D81B0F01D3004D0CB1; 172 | productRefGroup = 5CC504E21B0F01D3004D0CB1 /* Products */; 173 | projectDirPath = ""; 174 | projectRoot = ""; 175 | targets = ( 176 | 5CC504E01B0F01D3004D0CB1 /* LeanCloudFeedbackDemo */, 177 | ); 178 | }; 179 | /* End PBXProject section */ 180 | 181 | /* Begin PBXResourcesBuildPhase section */ 182 | 5CC504DF1B0F01D3004D0CB1 /* Resources */ = { 183 | isa = PBXResourcesBuildPhase; 184 | buildActionMask = 2147483647; 185 | files = ( 186 | 5CC504F01B0F01D3004D0CB1 /* Main.storyboard in Resources */, 187 | 5CC504F21B0F01D3004D0CB1 /* Images.xcassets in Resources */, 188 | ); 189 | runOnlyForDeploymentPostprocessing = 0; 190 | }; 191 | /* End PBXResourcesBuildPhase section */ 192 | 193 | /* Begin PBXShellScriptBuildPhase section */ 194 | 3BC15B31DE1EE218A416CB6C /* [CP] Copy Pods Resources */ = { 195 | isa = PBXShellScriptBuildPhase; 196 | buildActionMask = 2147483647; 197 | files = ( 198 | ); 199 | inputPaths = ( 200 | "${PODS_ROOT}/Target Support Files/Pods-LeanCloudFeedbackDemo/Pods-LeanCloudFeedbackDemo-resources.sh", 201 | "${PODS_ROOT}/AVOSCloud/AVOS/AVOSCloud/AVOSCloud_Art.inc", 202 | "${PODS_ROOT}/../LeanCloudFeedback/resources/feedback_add_image@2x.png", 203 | "${PODS_ROOT}/../LeanCloudFeedback/resources/feedback_back@2x.png", 204 | "${PODS_ROOT}/../LeanCloudFeedback/resources/feedback_bg_1@2x.png", 205 | "${PODS_ROOT}/../LeanCloudFeedback/resources/feedback_bg_2@2x.png", 206 | "${PODS_ROOT}/../LeanCloudFeedback/resources/feedback_btn@2x.png", 207 | "${PODS_ROOT}/../LeanCloudFeedback/resources/feedback_btn_new@2x.png", 208 | "${PODS_ROOT}/../LeanCloudFeedback/resources/feedback_warning@2x.png", 209 | "${PODS_ROOT}/../LeanCloudFeedback/resources/LeanCloudFeedback.strings", 210 | ); 211 | name = "[CP] Copy Pods Resources"; 212 | outputPaths = ( 213 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AVOSCloud_Art.inc", 214 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/feedback_add_image@2x.png", 215 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/feedback_back@2x.png", 216 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/feedback_bg_1@2x.png", 217 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/feedback_bg_2@2x.png", 218 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/feedback_btn@2x.png", 219 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/feedback_btn_new@2x.png", 220 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/feedback_warning@2x.png", 221 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/LeanCloudFeedback.strings", 222 | ); 223 | runOnlyForDeploymentPostprocessing = 0; 224 | shellPath = /bin/sh; 225 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-LeanCloudFeedbackDemo/Pods-LeanCloudFeedbackDemo-resources.sh\"\n"; 226 | showEnvVarsInLog = 0; 227 | }; 228 | 4769E60FE17A8611CF49B3CF /* [CP] Check Pods Manifest.lock */ = { 229 | isa = PBXShellScriptBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | ); 233 | inputPaths = ( 234 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 235 | "${PODS_ROOT}/Manifest.lock", 236 | ); 237 | name = "[CP] Check Pods Manifest.lock"; 238 | outputPaths = ( 239 | "$(DERIVED_FILE_DIR)/Pods-LeanCloudFeedbackDemo-checkManifestLockResult.txt", 240 | ); 241 | runOnlyForDeploymentPostprocessing = 0; 242 | shellPath = /bin/sh; 243 | 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"; 244 | showEnvVarsInLog = 0; 245 | }; 246 | /* End PBXShellScriptBuildPhase section */ 247 | 248 | /* Begin PBXSourcesBuildPhase section */ 249 | 5CC504DD1B0F01D3004D0CB1 /* Sources */ = { 250 | isa = PBXSourcesBuildPhase; 251 | buildActionMask = 2147483647; 252 | files = ( 253 | 5CC504ED1B0F01D3004D0CB1 /* ViewController.m in Sources */, 254 | 5CC504EA1B0F01D3004D0CB1 /* AppDelegate.m in Sources */, 255 | 5CC504E71B0F01D3004D0CB1 /* main.m in Sources */, 256 | ); 257 | runOnlyForDeploymentPostprocessing = 0; 258 | }; 259 | /* End PBXSourcesBuildPhase section */ 260 | 261 | /* Begin PBXVariantGroup section */ 262 | 5CC504EE1B0F01D3004D0CB1 /* Main.storyboard */ = { 263 | isa = PBXVariantGroup; 264 | children = ( 265 | 5CC504EF1B0F01D3004D0CB1 /* Base */, 266 | ); 267 | name = Main.storyboard; 268 | sourceTree = ""; 269 | }; 270 | 5CC504F31B0F01D3004D0CB1 /* LaunchScreen.xib */ = { 271 | isa = PBXVariantGroup; 272 | children = ( 273 | 5CC504F41B0F01D3004D0CB1 /* Base */, 274 | ); 275 | name = LaunchScreen.xib; 276 | sourceTree = ""; 277 | }; 278 | /* End PBXVariantGroup section */ 279 | 280 | /* Begin XCBuildConfiguration section */ 281 | 5CC505021B0F01D3004D0CB1 /* Debug */ = { 282 | isa = XCBuildConfiguration; 283 | buildSettings = { 284 | ALWAYS_SEARCH_USER_PATHS = NO; 285 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 286 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 287 | CLANG_CXX_LIBRARY = "libc++"; 288 | CLANG_ENABLE_MODULES = YES; 289 | CLANG_ENABLE_OBJC_ARC = YES; 290 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 291 | CLANG_WARN_BOOL_CONVERSION = YES; 292 | CLANG_WARN_COMMA = YES; 293 | CLANG_WARN_CONSTANT_CONVERSION = YES; 294 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 295 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 296 | CLANG_WARN_EMPTY_BODY = YES; 297 | CLANG_WARN_ENUM_CONVERSION = YES; 298 | CLANG_WARN_INFINITE_RECURSION = YES; 299 | CLANG_WARN_INT_CONVERSION = YES; 300 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 301 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 302 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 303 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 304 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 305 | CLANG_WARN_STRICT_PROTOTYPES = YES; 306 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 307 | CLANG_WARN_UNREACHABLE_CODE = YES; 308 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 309 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 310 | COPY_PHASE_STRIP = NO; 311 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 312 | ENABLE_STRICT_OBJC_MSGSEND = YES; 313 | ENABLE_TESTABILITY = YES; 314 | GCC_C_LANGUAGE_STANDARD = gnu99; 315 | GCC_DYNAMIC_NO_PIC = NO; 316 | GCC_NO_COMMON_BLOCKS = YES; 317 | GCC_OPTIMIZATION_LEVEL = 0; 318 | GCC_PREPROCESSOR_DEFINITIONS = ( 319 | "DEBUG=1", 320 | "$(inherited)", 321 | ); 322 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 323 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 324 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 325 | GCC_WARN_UNDECLARED_SELECTOR = YES; 326 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 327 | GCC_WARN_UNUSED_FUNCTION = YES; 328 | GCC_WARN_UNUSED_VARIABLE = YES; 329 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 330 | MTL_ENABLE_DEBUG_INFO = YES; 331 | ONLY_ACTIVE_ARCH = YES; 332 | SDKROOT = iphoneos; 333 | }; 334 | name = Debug; 335 | }; 336 | 5CC505031B0F01D3004D0CB1 /* Release */ = { 337 | isa = XCBuildConfiguration; 338 | buildSettings = { 339 | ALWAYS_SEARCH_USER_PATHS = NO; 340 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 341 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 342 | CLANG_CXX_LIBRARY = "libc++"; 343 | CLANG_ENABLE_MODULES = YES; 344 | CLANG_ENABLE_OBJC_ARC = YES; 345 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 346 | CLANG_WARN_BOOL_CONVERSION = YES; 347 | CLANG_WARN_COMMA = YES; 348 | CLANG_WARN_CONSTANT_CONVERSION = YES; 349 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 350 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 351 | CLANG_WARN_EMPTY_BODY = YES; 352 | CLANG_WARN_ENUM_CONVERSION = YES; 353 | CLANG_WARN_INFINITE_RECURSION = YES; 354 | CLANG_WARN_INT_CONVERSION = YES; 355 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 356 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 357 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 358 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 359 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 360 | CLANG_WARN_STRICT_PROTOTYPES = YES; 361 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 362 | CLANG_WARN_UNREACHABLE_CODE = YES; 363 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 364 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 365 | COPY_PHASE_STRIP = NO; 366 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 367 | ENABLE_NS_ASSERTIONS = NO; 368 | ENABLE_STRICT_OBJC_MSGSEND = YES; 369 | GCC_C_LANGUAGE_STANDARD = gnu99; 370 | GCC_NO_COMMON_BLOCKS = YES; 371 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 372 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 373 | GCC_WARN_UNDECLARED_SELECTOR = YES; 374 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 375 | GCC_WARN_UNUSED_FUNCTION = YES; 376 | GCC_WARN_UNUSED_VARIABLE = YES; 377 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 378 | MTL_ENABLE_DEBUG_INFO = NO; 379 | SDKROOT = iphoneos; 380 | VALIDATE_PRODUCT = YES; 381 | }; 382 | name = Release; 383 | }; 384 | 5CC505051B0F01D3004D0CB1 /* Debug */ = { 385 | isa = XCBuildConfiguration; 386 | baseConfigurationReference = C28BF2C6C8C5354952538019 /* Pods-LeanCloudFeedbackDemo.debug.xcconfig */; 387 | buildSettings = { 388 | ALWAYS_SEARCH_USER_PATHS = NO; 389 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 390 | CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; 391 | DEVELOPMENT_TEAM = 7J5XFNL99Q; 392 | ENABLE_BITCODE = NO; 393 | FRAMEWORK_SEARCH_PATHS = ( 394 | "$(inherited)", 395 | "$(PROJECT_DIR)", 396 | .., 397 | ); 398 | HEADER_SEARCH_PATHS = ( 399 | "\"$(OBJROOT)/UninstalledProducts/include\"", 400 | "$(inherited)", 401 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 402 | ); 403 | INFOPLIST_FILE = LeanCloudFeedbackDemo/Info.plist; 404 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 405 | PRODUCT_BUNDLE_IDENTIFIER = "com.leancloud.$(PRODUCT_NAME:rfc1034identifier)"; 406 | PRODUCT_NAME = "$(TARGET_NAME)"; 407 | }; 408 | name = Debug; 409 | }; 410 | 5CC505061B0F01D3004D0CB1 /* Release */ = { 411 | isa = XCBuildConfiguration; 412 | baseConfigurationReference = 7465E688A3D8D6072420F996 /* Pods-LeanCloudFeedbackDemo.release.xcconfig */; 413 | buildSettings = { 414 | ALWAYS_SEARCH_USER_PATHS = NO; 415 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 416 | CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; 417 | DEVELOPMENT_TEAM = 7J5XFNL99Q; 418 | ENABLE_BITCODE = NO; 419 | FRAMEWORK_SEARCH_PATHS = ( 420 | "$(inherited)", 421 | "$(PROJECT_DIR)", 422 | .., 423 | ); 424 | HEADER_SEARCH_PATHS = ( 425 | "\"$(OBJROOT)/UninstalledProducts/include\"", 426 | "$(inherited)", 427 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 428 | ); 429 | INFOPLIST_FILE = LeanCloudFeedbackDemo/Info.plist; 430 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 431 | PRODUCT_BUNDLE_IDENTIFIER = "com.leancloud.$(PRODUCT_NAME:rfc1034identifier)"; 432 | PRODUCT_NAME = "$(TARGET_NAME)"; 433 | }; 434 | name = Release; 435 | }; 436 | /* End XCBuildConfiguration section */ 437 | 438 | /* Begin XCConfigurationList section */ 439 | 5CC504DC1B0F01D3004D0CB1 /* Build configuration list for PBXProject "LeanCloudFeedbackDemo" */ = { 440 | isa = XCConfigurationList; 441 | buildConfigurations = ( 442 | 5CC505021B0F01D3004D0CB1 /* Debug */, 443 | 5CC505031B0F01D3004D0CB1 /* Release */, 444 | ); 445 | defaultConfigurationIsVisible = 0; 446 | defaultConfigurationName = Release; 447 | }; 448 | 5CC505041B0F01D3004D0CB1 /* Build configuration list for PBXNativeTarget "LeanCloudFeedbackDemo" */ = { 449 | isa = XCConfigurationList; 450 | buildConfigurations = ( 451 | 5CC505051B0F01D3004D0CB1 /* Debug */, 452 | 5CC505061B0F01D3004D0CB1 /* Release */, 453 | ); 454 | defaultConfigurationIsVisible = 0; 455 | defaultConfigurationName = Release; 456 | }; 457 | /* End XCConfigurationList section */ 458 | }; 459 | rootObject = 5CC504D91B0F01D3004D0CB1 /* Project object */; 460 | } 461 | -------------------------------------------------------------------------------- /LeanCloudFeedback/LCUserFeedbackViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // LCUserFeedbackViewController.m 3 | // Feedback 4 | // 5 | // Created by yang chaozhong on 4/24/14. 6 | // Copyright (c) 2014 LeanCloud. All rights reserved. 7 | // 8 | 9 | #import "LCUserFeedbackViewController.h" 10 | #import "LCUserFeedbackReplyCell.h" 11 | #import "LCUserFeedbackThread.h" 12 | #import "LCUserFeedbackThread_Internal.h" 13 | #import "LCUserFeedbackReply_Internal.h" 14 | #import "LCUserFeedbackReply.h" 15 | #import "LCUserFeedbackAgent.h" 16 | #import "LCUserFeedbackImageViewController.h" 17 | #import "LCUtils.h" 18 | 19 | #define kInputViewColor [UIColor colorWithRed:247.0f/255 green:248.0f/255 blue:248.0f/255 alpha:1] 20 | 21 | #define TAG_TABLEView_Header 1 22 | #define TAG_InputFiled 2 23 | 24 | static CGFloat const kInputViewHeight = 48; 25 | static CGFloat const kContactHeaderHeight = 48; 26 | static CGFloat const kAddImageButtonWidth = 40; 27 | static CGFloat const kSendButtonWidth = 60; 28 | 29 | @interface LCUserFeedbackViewController () { 30 | NSMutableArray *_feedbackReplies; 31 | LCUserFeedbackThread *_userFeedback; 32 | } 33 | 34 | @property(nonatomic, strong) UIRefreshControl *refreshControl; 35 | @property(nonatomic, strong) UITableView *tableView; 36 | @property(nonatomic, strong) UITextField *tableViewHeader; 37 | @property(nonatomic, strong) UITextField *inputTextField; 38 | @property(nonatomic, strong) UIButton *addImageButton; 39 | @property(nonatomic, strong) UIButton *sendButton; 40 | @property(nonatomic, strong) UIBarButtonItem *closeButtonItem; 41 | 42 | @end 43 | 44 | @implementation LCUserFeedbackViewController 45 | 46 | - (instancetype)init 47 | { 48 | self = [super init]; 49 | if (self) { 50 | _feedbackReplies = [[NSMutableArray alloc] init]; 51 | // Custom initialization 52 | _navigationBarStyle = LCUserFeedbackNavigationBarStyleBlue; 53 | _contactHeaderHidden = NO; 54 | _feedbackCellFont = [UIFont systemFontOfSize:16]; 55 | _presented = YES; 56 | } 57 | return self; 58 | } 59 | 60 | - (void)viewDidLoad 61 | { 62 | [super viewDidLoad]; 63 | [self setupUI]; 64 | // [self keyboardWillHide:nil]; 65 | [self loadFeedbackThreads]; 66 | } 67 | 68 | - (void)viewDidAppear:(BOOL)animated { 69 | [super viewDidAppear:animated]; 70 | } 71 | 72 | - (void)viewDidDisappear:(BOOL)animated { 73 | [super viewDidDisappear:animated]; 74 | // 记录最后阅读时,看到了多少条反馈 75 | if (_userFeedback !=nil && _feedbackReplies.count > 0) { 76 | NSString *localKey = [NSString stringWithFormat:@"feedback_%@", _userFeedback.objectId]; 77 | [[NSUserDefaults standardUserDefaults] setObject:@(_feedbackReplies.count) forKey:localKey]; 78 | } 79 | } 80 | 81 | - (void)setupUI { 82 | if (self.presented) { 83 | self.navigationItem.leftBarButtonItem = self.closeButtonItem; 84 | } 85 | 86 | [self.navigationItem setTitle:LCLocalizedString(@"User Feedback")]; 87 | [self setupNavigaionBar]; 88 | 89 | [self.view addSubview:self.tableView]; 90 | [self.view addSubview:self.addImageButton]; 91 | [self.view addSubview:self.sendButton]; 92 | [self.view addSubview:self.inputTextField]; 93 | 94 | [[[LCUserFeedbackReplyCell class] appearance] setCellFont:self.feedbackCellFont]; 95 | 96 | [self.tableView addSubview:self.refreshControl]; 97 | 98 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(closeKeyboard:)]; 99 | tap.cancelsTouchesInView = NO; 100 | [self.tableView addGestureRecognizer:tap]; 101 | } 102 | 103 | #pragma mark - Properties 104 | 105 | - (UIButton *)closeButton { 106 | UIButton *closeButton = [UIButton buttonWithType:UIButtonTypeCustom]; 107 | UIImage *closeButtonImage = [UIImage imageNamed:@"feedback_back"]; 108 | [closeButton setImage:closeButtonImage forState:UIControlStateNormal]; 109 | closeButton.frame = CGRectMake(0, 0, closeButtonImage.size.width, closeButtonImage.size.height); 110 | closeButton.autoresizingMask = UIViewAutoresizingFlexibleHeight; 111 | return closeButton; 112 | } 113 | 114 | - (UIBarButtonItem *)closeButtonItem { 115 | if (_closeButtonItem == nil) { 116 | if (self.presented) { 117 | _closeButtonItem = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemStop target:self action:@selector(closeButtonClicked:)]; 118 | } else { 119 | UIButton *closeButton = [self closeButton]; 120 | [closeButton addTarget:self action:@selector(closeButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; 121 | _closeButtonItem = [[UIBarButtonItem alloc] initWithCustomView:closeButton]; 122 | } 123 | 124 | } 125 | return _closeButtonItem; 126 | } 127 | 128 | 129 | - (UITableView *)tableView { 130 | if (_tableView == nil) { 131 | _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame) - kInputViewHeight) 132 | style:UITableViewStylePlain]; 133 | _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; 134 | _tableView.delegate = self; 135 | _tableView.dataSource = self; 136 | } 137 | return _tableView; 138 | } 139 | 140 | - (UIButton *)addImageButton { 141 | if (_addImageButton == nil) { 142 | _addImageButton = [[UIButton alloc] initWithFrame:CGRectMake(0, CGRectGetHeight(self.view.frame) - kInputViewHeight, kAddImageButtonWidth, kInputViewHeight)]; 143 | _addImageButton.backgroundColor = kInputViewColor; 144 | [_addImageButton setImage:[UIImage imageNamed:@"feedback_add_image"] forState:UIControlStateNormal]; 145 | _addImageButton.contentMode = UIViewContentModeScaleAspectFill; 146 | [_addImageButton addTarget:self action:@selector(addImageButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; 147 | } 148 | return _addImageButton; 149 | } 150 | 151 | - (UIButton *)sendButton { 152 | if (_sendButton == nil) { 153 | _sendButton = [UIButton buttonWithType:UIButtonTypeSystem]; 154 | _sendButton.frame = CGRectMake(CGRectGetWidth(self.view.frame) - kSendButtonWidth, CGRectGetHeight(self.view.frame) - kInputViewHeight, kSendButtonWidth, kInputViewHeight); 155 | [_sendButton.titleLabel setFont:[UIFont systemFontOfSize:12]]; 156 | [_sendButton setTitleColor:[UIColor colorWithRed:137.0f/255 green:137.0f/255 blue:137.0f/255 alpha:1] forState:UIControlStateNormal]; 157 | [_sendButton setTitle:LCLocalizedString(@"Send") forState:UIControlStateNormal]; 158 | [_sendButton setBackgroundColor: kInputViewColor]; 159 | [_sendButton addTarget:self action:@selector(sendButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; 160 | } 161 | return _sendButton; 162 | } 163 | 164 | - (UITextField *)inputTextField { 165 | if (_inputTextField == nil) { 166 | _inputTextField = [[UITextField alloc] initWithFrame:CGRectMake(kAddImageButtonWidth, CGRectGetMinY(self.sendButton.frame), CGRectGetWidth(self.view.frame)- kSendButtonWidth - kAddImageButtonWidth, kInputViewHeight)]; 167 | _inputTextField.tag = TAG_InputFiled; 168 | [_inputTextField setFont:[UIFont systemFontOfSize:12]]; 169 | _inputTextField.backgroundColor = kInputViewColor; 170 | _inputTextField.placeholder = LCLocalizedString(@"Please write your feedback"); 171 | UIView *paddingView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 5, 30)]; 172 | _inputTextField.leftView = paddingView; 173 | _inputTextField.leftViewMode = UITextFieldViewModeAlways; 174 | _inputTextField.returnKeyType = UIReturnKeyDone; 175 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; 176 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; 177 | _inputTextField.delegate = self; 178 | } 179 | return _inputTextField; 180 | } 181 | 182 | - (void)setupNavigaionBar { 183 | switch (self.navigationBarStyle) { 184 | case LCUserFeedbackNavigationBarStyleBlue: { 185 | [self.navigationController.navigationBar setTitleTextAttributes:@{NSForegroundColorAttributeName : [UIColor whiteColor]}]; 186 | UIColor *blue =[UIColor colorWithRed:85.0f/255 green:184.0f/255 blue:244.0f/255 alpha:1]; 187 | self.navigationController.navigationBar.barTintColor = blue; 188 | self.closeButtonItem.tintColor = [UIColor whiteColor]; 189 | break; 190 | } 191 | case LCUserFeedbackNavigationBarStyleNone: 192 | break; 193 | default: 194 | break; 195 | } 196 | } 197 | 198 | - (UIRefreshControl *)refreshControl { 199 | if (_refreshControl == nil) { 200 | _refreshControl = [[UIRefreshControl alloc] init]; 201 | [_refreshControl addTarget:self action:@selector(handleRefresh:) forControlEvents:UIControlEventValueChanged]; 202 | } 203 | return _refreshControl; 204 | } 205 | 206 | #pragma mark - load data 207 | 208 | - (void)fetchRepliesWithBlock:(AVArrayResultBlock)block { 209 | [LCUserFeedbackThread fetchFeedbackWithBlock:^(LCUserFeedbackThread *feedback, NSError *error) { 210 | if (error) { 211 | block(nil, error); 212 | } else { 213 | if (feedback) { 214 | self->_userFeedback = feedback; 215 | if (self.contact == nil) { 216 | self.contact = feedback.contact; 217 | } 218 | [self->_userFeedback fetchFeedbackRepliesInBackgroundWithBlock:block]; 219 | } else { 220 | block([NSArray array], nil); 221 | } 222 | } 223 | }]; 224 | } 225 | 226 | - (void)loadFeedbackThreads { 227 | if (![_refreshControl isRefreshing]) { 228 | [_refreshControl beginRefreshing]; 229 | } 230 | [self fetchRepliesWithBlock:^(NSArray *objects, NSError *error) { 231 | [self->_refreshControl endRefreshing]; 232 | if ([self filterError:error]) { 233 | if (objects.count > 0) { 234 | [self->_feedbackReplies removeAllObjects]; 235 | [self->_feedbackReplies addObjectsFromArray:objects]; 236 | 237 | [self->_tableView reloadData]; 238 | [self scrollToBottom]; 239 | } 240 | } 241 | }]; 242 | } 243 | 244 | - (void)handleRefresh:(id)sender { 245 | [self loadFeedbackThreads]; 246 | } 247 | 248 | #pragma mark - util 249 | 250 | - (void)alertWithTitle:(NSString *)title message:(NSString *)message { 251 | UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title message:message delegate:nil cancelButtonTitle:@"好的" otherButtonTitles:nil, nil]; 252 | [alertView show]; 253 | } 254 | 255 | - (BOOL)filterError:(NSError *)error { 256 | if (error) { 257 | [self alertWithTitle:@"出错了" message:[error description]]; 258 | return NO; 259 | } else { 260 | return YES; 261 | } 262 | } 263 | 264 | #pragma mark - send action 265 | 266 | - (void)popImageViewController:(id)sender { 267 | [self.navigationController popViewControllerAnimated:YES]; 268 | } 269 | 270 | - (void)didSelectImageViewOnFeedbackReply:(LCUserFeedbackReply *)feedbackReply { 271 | LCUserFeedbackImageViewController *imageViewController = [[LCUserFeedbackImageViewController alloc] init]; 272 | imageViewController.image = feedbackReply.attachmentImage; 273 | if (self.navigationBarStyle == LCUserFeedbackNavigationBarStyleBlue) { 274 | UIButton *button = [self closeButton]; 275 | [button addTarget:self action:@selector(popImageViewController:) forControlEvents:UIControlEventTouchUpInside]; 276 | UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:button]; 277 | imageViewController.navigationItem.leftBarButtonItem = item; 278 | } 279 | [self.navigationController pushViewController:imageViewController animated:YES]; 280 | } 281 | 282 | - (void)closeButtonClicked:(id)sender { 283 | if (self.presented) { 284 | [self dismissViewControllerAnimated:YES completion:^{ 285 | 286 | }]; 287 | } else { 288 | [self.navigationController popViewControllerAnimated:YES]; 289 | } 290 | } 291 | 292 | - (NSString *)currentContact { 293 | NSString *contact = self.tableViewHeader.text; 294 | return contact.length > 0 ? contact : _contact; 295 | } 296 | 297 | - (void)addImageButtonClicked:(id)sender { 298 | UIImagePickerController *pickerController = [[UIImagePickerController alloc] init]; 299 | pickerController.navigationBar.barStyle = UIBarStyleBlack; 300 | pickerController.delegate = self; 301 | pickerController.editing = NO; 302 | pickerController.allowsEditing = NO; 303 | pickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; 304 | [self presentViewController:pickerController animated:YES completion:nil]; 305 | } 306 | 307 | - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { 308 | UIImage *originImage = info[UIImagePickerControllerOriginalImage]; 309 | [self prepareFeedbackWithBlock:^(BOOL succeeded, NSError *error) { 310 | if ([self filterError:error]) { 311 | AVFile *attachment = [AVFile fileWithData:UIImageJPEGRepresentation(originImage, 0.6) name:@"feedback.png"]; 312 | [attachment uploadWithCompletionHandler:^(BOOL succeeded, NSError *error) { 313 | if ([self filterError:error]) { 314 | LCUserFeedbackReply *feedbackReply = [LCUserFeedbackReply feedbackReplyWithAttachment:attachment.url type:LCReplyTypeUser]; 315 | feedbackReply.attachmentImage = originImage; 316 | [self saveFeedbackReply:feedbackReply AtFeedback:self->_userFeedback]; 317 | } 318 | }]; 319 | } 320 | }]; 321 | [picker dismissViewControllerAnimated:YES completion:nil]; 322 | } 323 | 324 | - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { 325 | [picker dismissViewControllerAnimated:YES completion:nil]; 326 | } 327 | 328 | - (void)prepareFeedbackWithBlock:(AVBooleanResultBlock)block { 329 | NSString *contact = [self currentContact]; 330 | NSString *content = self.inputTextField.text; 331 | if (_userFeedback) { 332 | block(YES, nil); 333 | } else { 334 | _contact = contact; 335 | NSString *title = _feedbackTitle ?: content; 336 | if (title.length == 0) { 337 | title = @"用户反馈"; 338 | } 339 | [LCUserFeedbackThread feedbackWithContent:title contact:_contact create:YES withBlock:^(id object, NSError *error) { 340 | if (error) { 341 | block(NO, error); 342 | } else { 343 | self->_userFeedback = object; 344 | block(YES, nil); 345 | } 346 | }]; 347 | } 348 | } 349 | 350 | - (void)sendButtonClicked:(id)sender { 351 | NSString *content = self.inputTextField.text; 352 | if (content.length) { 353 | self.sendButton.enabled = NO; 354 | [self prepareFeedbackWithBlock:^(BOOL succeeded, NSError *error) { 355 | if ([self filterError:error]) { 356 | LCUserFeedbackReply *feedbackReply = [LCUserFeedbackReply feedbackReplyWithContent:content type:LCReplyTypeUser]; 357 | [self saveFeedbackReply:feedbackReply AtFeedback:self->_userFeedback]; 358 | } else { 359 | self.sendButton.enabled = YES; 360 | } 361 | }]; 362 | } 363 | } 364 | 365 | - (void)saveFeedbackReply:(LCUserFeedbackReply *)feedbackReply AtFeedback:(LCUserFeedbackThread *)feedback { 366 | [_userFeedback saveFeedbackReplyInBackground:feedbackReply withBlock:^(id object, NSError *error) { 367 | if ([self filterError:error]) { 368 | [self->_feedbackReplies addObject:feedbackReply]; 369 | [self.tableView reloadData]; 370 | [self scrollToBottom]; 371 | 372 | if ([self->_inputTextField.text length] > 0) { 373 | self->_inputTextField.text = @""; 374 | } 375 | } 376 | self.sendButton.enabled = YES; 377 | }]; 378 | } 379 | 380 | - (void)didReceiveMemoryWarning 381 | { 382 | [super didReceiveMemoryWarning]; 383 | } 384 | 385 | #pragma mark - UIKeyboard Notification 386 | 387 | - (void)updateHeightWhenKeyboardHide:(UIView *)bottomView { 388 | CGRect bottomViewFrame = bottomView.frame; 389 | bottomViewFrame.origin.y = self.view.bounds.size.height - bottomViewFrame.size.height; 390 | bottomView.frame = bottomViewFrame; 391 | } 392 | 393 | - (void)updateHeightWhenKeyboardShow:(UIView *)bottomView keyboardHeight:(CGFloat)keyboardHeight{ 394 | CGRect bottomViewFrame = bottomView.frame; 395 | bottomViewFrame.origin.y = self.view.bounds.size.height - keyboardHeight - bottomViewFrame.size.height; 396 | bottomView.frame = bottomViewFrame; 397 | } 398 | 399 | - (void)keyboardWillShow:(NSNotification *)notification { 400 | if ([self.tableViewHeader isFirstResponder]) { 401 | return; 402 | } 403 | 404 | float animationDuration = [[[notification userInfo] valueForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue]; 405 | CGFloat keyboardHeight = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; 406 | 407 | [UIView animateWithDuration:animationDuration 408 | delay:0 409 | options:UIViewAnimationOptionCurveEaseInOut 410 | animations:^{ 411 | 412 | [self updateHeightWhenKeyboardShow:self.addImageButton keyboardHeight:keyboardHeight]; 413 | [self updateHeightWhenKeyboardShow:self.inputTextField keyboardHeight:keyboardHeight]; 414 | [self updateHeightWhenKeyboardShow:self.sendButton keyboardHeight:keyboardHeight]; 415 | 416 | CGRect tableViewFrame = self.tableView.frame; 417 | tableViewFrame.size.height = self.view.bounds.size.height - self.navigationController.navigationBar.frame.size.height - keyboardHeight; 418 | self.tableView.frame = tableViewFrame; 419 | } 420 | completion:^(BOOL finished) { 421 | [self scrollToBottom]; 422 | } 423 | ]; 424 | } 425 | 426 | - (void)keyboardWillHide:(NSNotification *)notification { 427 | if ([self.tableViewHeader isFirstResponder]) { 428 | return; 429 | } 430 | 431 | [UIView beginAnimations:@"bottomBarDown" context:nil]; 432 | [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; 433 | 434 | [self updateHeightWhenKeyboardHide:self.addImageButton]; 435 | [self updateHeightWhenKeyboardHide:self.inputTextField]; 436 | [self updateHeightWhenKeyboardHide:self.sendButton]; 437 | 438 | CGRect tableViewFrame = self.tableView.frame; 439 | tableViewFrame.size.height = CGRectGetHeight(self.view.frame) - kInputViewHeight; 440 | self.tableView.frame = tableViewFrame; 441 | 442 | [UIView commitAnimations]; 443 | } 444 | 445 | - (void)closeKeyboard:(id)sender { 446 | [self.inputTextField resignFirstResponder]; 447 | } 448 | 449 | #pragma mark UITextField Delegate Methods 450 | 451 | - (BOOL)textFieldShouldReturn:(UITextField *)textField { 452 | [textField resignFirstResponder]; 453 | 454 | return YES; 455 | } 456 | 457 | - (void)textFieldDidEndEditing:(UITextField *)textField { 458 | if (textField.tag == TAG_TABLEView_Header && [textField.text length] > 0 && _userFeedback) { 459 | _userFeedback.contact = textField.text; 460 | [LCUserFeedbackThread updateFeedback:_userFeedback withBlock:^(id object, NSError *error) { 461 | if ([self filterError:error]) { 462 | [self alertWithTitle:@"提示" message:@"更改成功"]; 463 | } 464 | }]; 465 | } 466 | } 467 | 468 | #pragma mark - UIScrollViewDelegate 469 | - (void)scrollToBottom { 470 | if ([self.tableView numberOfRowsInSection:0] > 1) { 471 | NSInteger lastRowNumber = [self.tableView numberOfRowsInSection:0] - 1; 472 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:lastRowNumber inSection:0]; 473 | [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; 474 | } 475 | } 476 | 477 | #pragma mark - UITableViewDelegate and UITableViewDataSource 478 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 479 | return 1; 480 | } 481 | 482 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 483 | return [_feedbackReplies count]; 484 | } 485 | 486 | - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { 487 | if (self.contactHeaderHidden) { 488 | return 0; 489 | } else { 490 | return kContactHeaderHeight; 491 | } 492 | } 493 | 494 | - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { 495 | self.tableViewHeader = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 320, kContactHeaderHeight)]; 496 | UIView *paddingView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 30)]; 497 | self.tableViewHeader.leftView = paddingView; 498 | self.tableViewHeader.leftViewMode = UITextFieldViewModeAlways; 499 | 500 | self.tableViewHeader.delegate = (id ) self; 501 | self.tableViewHeader.tag = TAG_TABLEView_Header; 502 | [self.tableViewHeader setBackgroundColor:[UIColor colorWithRed:247.0f/255 green:248.0f/255 blue:248.0f/255 alpha:1]]; 503 | self.tableViewHeader.textAlignment = NSTextAlignmentLeft; 504 | self.tableViewHeader.placeholder = LCLocalizedString(@"Email Or QQ"); 505 | [self.tableViewHeader setFont:[UIFont systemFontOfSize:12.0f]]; 506 | if (_contact) { 507 | self.tableViewHeader.text = _contact; 508 | } 509 | return _tableViewHeader; 510 | } 511 | 512 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 513 | return [LCUserFeedbackReplyCell heightForFeedbackReply:(LCUserFeedbackReply *)_feedbackReplies[indexPath.row]]; 514 | } 515 | 516 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 517 | LCUserFeedbackReply *feedbackReply = _feedbackReplies[indexPath.row]; 518 | static NSString *cellIdentifier = @"feedbackReplyCell"; 519 | LCUserFeedbackReplyCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 520 | if (cell == nil) { 521 | cell = [[LCUserFeedbackReplyCell alloc] initWithFeedbackReply:feedbackReply reuseIdentifier:cellIdentifier];; 522 | cell.delegate = self; 523 | } 524 | [cell configuareCellWithFeedbackReply:feedbackReply]; 525 | return cell; 526 | } 527 | 528 | @end 529 | 530 | 531 | -------------------------------------------------------------------------------- /LeanCloudFeedback.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXAggregateTarget section */ 10 | 5CC504C71B0EFCBA004D0CB1 /* UniversalFramework */ = { 11 | isa = PBXAggregateTarget; 12 | buildConfigurationList = 5CC504CA1B0EFCBA004D0CB1 /* Build configuration list for PBXAggregateTarget "UniversalFramework" */; 13 | buildPhases = ( 14 | 5CC504CB1B0EFCC0004D0CB1 /* ShellScript */, 15 | ); 16 | dependencies = ( 17 | ); 18 | name = UniversalFramework; 19 | productName = UniversalFramework; 20 | }; 21 | /* End PBXAggregateTarget section */ 22 | 23 | /* Begin PBXBuildFile section */ 24 | 5CCAE1011B09B0FE009276B9 /* LeanCloudFeedback.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CCAE1001B09B0FE009276B9 /* LeanCloudFeedback.h */; settings = {ATTRIBUTES = (Public, ); }; }; 25 | 5CCAE1241B09B323009276B9 /* LCUserFeedbackAgent.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CCAE1171B09B323009276B9 /* LCUserFeedbackAgent.m */; }; 26 | 5CCAE1251B09B323009276B9 /* LCUserFeedbackThread.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CCAE1181B09B323009276B9 /* LCUserFeedbackThread.m */; }; 27 | 5CCAE1261B09B323009276B9 /* LCUserFeedbackViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CCAE1191B09B323009276B9 /* LCUserFeedbackViewController.m */; }; 28 | 5CCAE1271B09B323009276B9 /* LCUserFeedbackThread_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CCAE11A1B09B323009276B9 /* LCUserFeedbackThread_Internal.h */; }; 29 | 5CCAE1281B09B323009276B9 /* LCUserFeedbackThread.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CCAE11B1B09B323009276B9 /* LCUserFeedbackThread.h */; settings = {ATTRIBUTES = (Public, ); }; }; 30 | 5CCAE1291B09B323009276B9 /* LCUserFeedbackAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CCAE11C1B09B323009276B9 /* LCUserFeedbackAgent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 31 | 5CCAE12A1B09B323009276B9 /* LCUserFeedbackReplyCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CCAE11D1B09B323009276B9 /* LCUserFeedbackReplyCell.h */; }; 32 | 5CCAE12B1B09B323009276B9 /* LCUserFeedbackReplyCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CCAE11E1B09B323009276B9 /* LCUserFeedbackReplyCell.m */; }; 33 | 5CCAE12E1B09B323009276B9 /* LCUserFeedbackReply.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CCAE1211B09B323009276B9 /* LCUserFeedbackReply.h */; settings = {ATTRIBUTES = (Public, ); }; }; 34 | 5CCAE12F1B09B323009276B9 /* LCUserFeedbackReply.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CCAE1221B09B323009276B9 /* LCUserFeedbackReply.m */; }; 35 | 5CCAE1301B09B323009276B9 /* LCUserFeedbackViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CCAE1231B09B323009276B9 /* LCUserFeedbackViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 36 | 5CCAE1351B09C1F2009276B9 /* LCHttpClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CCAE1311B09C1F2009276B9 /* LCHttpClient.m */; }; 37 | 5CCAE1361B09C1F2009276B9 /* LCHttpClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CCAE1321B09C1F2009276B9 /* LCHttpClient.h */; }; 38 | 5CCAE1371B09C1F2009276B9 /* LCUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CCAE1331B09C1F2009276B9 /* LCUtils.m */; }; 39 | 5CCAE1381B09C1F2009276B9 /* LCUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CCAE1341B09C1F2009276B9 /* LCUtils.h */; }; 40 | 7000FCF21BEC9E450097C729 /* LeanCloudFeedback.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7000FCF11BEC9E450097C729 /* LeanCloudFeedback.strings */; }; 41 | 703074C31B9ABA6D00FD796E /* feedback_back@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 703074BC1B9ABA6D00FD796E /* feedback_back@2x.png */; }; 42 | 703074C41B9ABA6D00FD796E /* feedback_bg_1@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 703074BD1B9ABA6D00FD796E /* feedback_bg_1@2x.png */; }; 43 | 703074C51B9ABA6D00FD796E /* feedback_bg_2@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 703074BE1B9ABA6D00FD796E /* feedback_bg_2@2x.png */; }; 44 | 703074C61B9ABA6D00FD796E /* feedback_btn@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 703074BF1B9ABA6D00FD796E /* feedback_btn@2x.png */; }; 45 | 703074C71B9ABA6D00FD796E /* feedback_btn_new@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 703074C01B9ABA6D00FD796E /* feedback_btn_new@2x.png */; }; 46 | 703074C81B9ABA6D00FD796E /* feedback_warning@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 703074C11B9ABA6D00FD796E /* feedback_warning@2x.png */; }; 47 | 703074E81B9ABDA400FD796E /* feedback_add_image@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 703074E71B9ABDA400FD796E /* feedback_add_image@2x.png */; }; 48 | 70FB0B271B5E76D500FD8D70 /* LCUserFeedbackImageViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 70FB0B251B5E76D500FD8D70 /* LCUserFeedbackImageViewController.h */; }; 49 | 70FB0B281B5E76D500FD8D70 /* LCUserFeedbackImageViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 70FB0B261B5E76D500FD8D70 /* LCUserFeedbackImageViewController.m */; }; 50 | FA5979E6FAE10100718387EA /* libPods-LeanCloudFeedback.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 62FC55FE5788FF58EC0BC812 /* libPods-LeanCloudFeedback.a */; }; 51 | /* End PBXBuildFile section */ 52 | 53 | /* Begin PBXFileReference section */ 54 | 59AC16570E133BA472B3DABB /* Pods-LeanCloudFeedback.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LeanCloudFeedback.release.xcconfig"; path = "Pods/Target Support Files/Pods-LeanCloudFeedback/Pods-LeanCloudFeedback.release.xcconfig"; sourceTree = ""; }; 55 | 5CCAE0FB1B09B0FE009276B9 /* LeanCloudFeedback.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LeanCloudFeedback.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 56 | 5CCAE0FF1B09B0FE009276B9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 57 | 5CCAE1001B09B0FE009276B9 /* LeanCloudFeedback.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LeanCloudFeedback.h; sourceTree = ""; }; 58 | 5CCAE1171B09B323009276B9 /* LCUserFeedbackAgent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LCUserFeedbackAgent.m; sourceTree = ""; }; 59 | 5CCAE1181B09B323009276B9 /* LCUserFeedbackThread.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LCUserFeedbackThread.m; sourceTree = ""; }; 60 | 5CCAE1191B09B323009276B9 /* LCUserFeedbackViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LCUserFeedbackViewController.m; sourceTree = ""; }; 61 | 5CCAE11A1B09B323009276B9 /* LCUserFeedbackThread_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LCUserFeedbackThread_Internal.h; sourceTree = ""; }; 62 | 5CCAE11B1B09B323009276B9 /* LCUserFeedbackThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LCUserFeedbackThread.h; sourceTree = ""; }; 63 | 5CCAE11C1B09B323009276B9 /* LCUserFeedbackAgent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LCUserFeedbackAgent.h; sourceTree = ""; }; 64 | 5CCAE11D1B09B323009276B9 /* LCUserFeedbackReplyCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LCUserFeedbackReplyCell.h; sourceTree = ""; }; 65 | 5CCAE11E1B09B323009276B9 /* LCUserFeedbackReplyCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LCUserFeedbackReplyCell.m; sourceTree = ""; }; 66 | 5CCAE1211B09B323009276B9 /* LCUserFeedbackReply.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LCUserFeedbackReply.h; sourceTree = ""; }; 67 | 5CCAE1221B09B323009276B9 /* LCUserFeedbackReply.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LCUserFeedbackReply.m; sourceTree = ""; }; 68 | 5CCAE1231B09B323009276B9 /* LCUserFeedbackViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LCUserFeedbackViewController.h; sourceTree = ""; }; 69 | 5CCAE1311B09C1F2009276B9 /* LCHttpClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LCHttpClient.m; sourceTree = ""; }; 70 | 5CCAE1321B09C1F2009276B9 /* LCHttpClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LCHttpClient.h; sourceTree = ""; }; 71 | 5CCAE1331B09C1F2009276B9 /* LCUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LCUtils.m; sourceTree = ""; }; 72 | 5CCAE1341B09C1F2009276B9 /* LCUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LCUtils.h; sourceTree = ""; }; 73 | 62FC55FE5788FF58EC0BC812 /* libPods-LeanCloudFeedback.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-LeanCloudFeedback.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 74 | 7000FCF11BEC9E450097C729 /* LeanCloudFeedback.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; path = LeanCloudFeedback.strings; sourceTree = ""; }; 75 | 703074BC1B9ABA6D00FD796E /* feedback_back@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "feedback_back@2x.png"; sourceTree = ""; }; 76 | 703074BD1B9ABA6D00FD796E /* feedback_bg_1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "feedback_bg_1@2x.png"; sourceTree = ""; }; 77 | 703074BE1B9ABA6D00FD796E /* feedback_bg_2@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "feedback_bg_2@2x.png"; sourceTree = ""; }; 78 | 703074BF1B9ABA6D00FD796E /* feedback_btn@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "feedback_btn@2x.png"; sourceTree = ""; }; 79 | 703074C01B9ABA6D00FD796E /* feedback_btn_new@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "feedback_btn_new@2x.png"; sourceTree = ""; }; 80 | 703074C11B9ABA6D00FD796E /* feedback_warning@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "feedback_warning@2x.png"; sourceTree = ""; }; 81 | 703074E71B9ABDA400FD796E /* feedback_add_image@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "feedback_add_image@2x.png"; sourceTree = ""; }; 82 | 70FB0B1E1B5E0E4F00FD8D70 /* LCUserFeedbackReply_Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LCUserFeedbackReply_Internal.h; sourceTree = ""; }; 83 | 70FB0B251B5E76D500FD8D70 /* LCUserFeedbackImageViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LCUserFeedbackImageViewController.h; sourceTree = ""; }; 84 | 70FB0B261B5E76D500FD8D70 /* LCUserFeedbackImageViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LCUserFeedbackImageViewController.m; sourceTree = ""; }; 85 | D94FDB034BF527C80C647B30 /* Pods-LeanCloudFeedback.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LeanCloudFeedback.debug.xcconfig"; path = "Pods/Target Support Files/Pods-LeanCloudFeedback/Pods-LeanCloudFeedback.debug.xcconfig"; sourceTree = ""; }; 86 | /* End PBXFileReference section */ 87 | 88 | /* Begin PBXFrameworksBuildPhase section */ 89 | 5CCAE0F71B09B0FE009276B9 /* Frameworks */ = { 90 | isa = PBXFrameworksBuildPhase; 91 | buildActionMask = 2147483647; 92 | files = ( 93 | FA5979E6FAE10100718387EA /* libPods-LeanCloudFeedback.a in Frameworks */, 94 | ); 95 | runOnlyForDeploymentPostprocessing = 0; 96 | }; 97 | /* End PBXFrameworksBuildPhase section */ 98 | 99 | /* Begin PBXGroup section */ 100 | 56A155F7F6474AB06BF1EFA0 /* Frameworks */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 62FC55FE5788FF58EC0BC812 /* libPods-LeanCloudFeedback.a */, 104 | ); 105 | name = Frameworks; 106 | sourceTree = ""; 107 | }; 108 | 5CCAE0F11B09B0FE009276B9 = { 109 | isa = PBXGroup; 110 | children = ( 111 | 5CCAE0FD1B09B0FE009276B9 /* LeanCloudFeedback */, 112 | 5CCAE0FC1B09B0FE009276B9 /* Products */, 113 | 56A155F7F6474AB06BF1EFA0 /* Frameworks */, 114 | BFE6ABFA49B1037F183EDE3F /* Pods */, 115 | ); 116 | sourceTree = ""; 117 | }; 118 | 5CCAE0FC1B09B0FE009276B9 /* Products */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 5CCAE0FB1B09B0FE009276B9 /* LeanCloudFeedback.framework */, 122 | ); 123 | name = Products; 124 | sourceTree = ""; 125 | }; 126 | 5CCAE0FD1B09B0FE009276B9 /* LeanCloudFeedback */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | 703074BA1B9ABA6D00FD796E /* resources */, 130 | 5CCAE1321B09C1F2009276B9 /* LCHttpClient.h */, 131 | 5CCAE1311B09C1F2009276B9 /* LCHttpClient.m */, 132 | 5CCAE1341B09C1F2009276B9 /* LCUtils.h */, 133 | 5CCAE1331B09C1F2009276B9 /* LCUtils.m */, 134 | 5CCAE1001B09B0FE009276B9 /* LeanCloudFeedback.h */, 135 | 5CCAE11C1B09B323009276B9 /* LCUserFeedbackAgent.h */, 136 | 5CCAE1171B09B323009276B9 /* LCUserFeedbackAgent.m */, 137 | 5CCAE11B1B09B323009276B9 /* LCUserFeedbackThread.h */, 138 | 5CCAE11A1B09B323009276B9 /* LCUserFeedbackThread_Internal.h */, 139 | 5CCAE1181B09B323009276B9 /* LCUserFeedbackThread.m */, 140 | 5CCAE1211B09B323009276B9 /* LCUserFeedbackReply.h */, 141 | 70FB0B1E1B5E0E4F00FD8D70 /* LCUserFeedbackReply_Internal.h */, 142 | 5CCAE1221B09B323009276B9 /* LCUserFeedbackReply.m */, 143 | 5CCAE11D1B09B323009276B9 /* LCUserFeedbackReplyCell.h */, 144 | 5CCAE11E1B09B323009276B9 /* LCUserFeedbackReplyCell.m */, 145 | 5CCAE1231B09B323009276B9 /* LCUserFeedbackViewController.h */, 146 | 5CCAE1191B09B323009276B9 /* LCUserFeedbackViewController.m */, 147 | 70FB0B251B5E76D500FD8D70 /* LCUserFeedbackImageViewController.h */, 148 | 70FB0B261B5E76D500FD8D70 /* LCUserFeedbackImageViewController.m */, 149 | 5CCAE0FE1B09B0FE009276B9 /* Supporting Files */, 150 | ); 151 | path = LeanCloudFeedback; 152 | sourceTree = ""; 153 | }; 154 | 5CCAE0FE1B09B0FE009276B9 /* Supporting Files */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | 5CCAE0FF1B09B0FE009276B9 /* Info.plist */, 158 | ); 159 | name = "Supporting Files"; 160 | sourceTree = ""; 161 | }; 162 | 703074BA1B9ABA6D00FD796E /* resources */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | 703074E71B9ABDA400FD796E /* feedback_add_image@2x.png */, 166 | 703074BC1B9ABA6D00FD796E /* feedback_back@2x.png */, 167 | 703074BD1B9ABA6D00FD796E /* feedback_bg_1@2x.png */, 168 | 703074BE1B9ABA6D00FD796E /* feedback_bg_2@2x.png */, 169 | 703074BF1B9ABA6D00FD796E /* feedback_btn@2x.png */, 170 | 703074C01B9ABA6D00FD796E /* feedback_btn_new@2x.png */, 171 | 703074C11B9ABA6D00FD796E /* feedback_warning@2x.png */, 172 | 7000FCF11BEC9E450097C729 /* LeanCloudFeedback.strings */, 173 | ); 174 | path = resources; 175 | sourceTree = ""; 176 | }; 177 | BFE6ABFA49B1037F183EDE3F /* Pods */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | D94FDB034BF527C80C647B30 /* Pods-LeanCloudFeedback.debug.xcconfig */, 181 | 59AC16570E133BA472B3DABB /* Pods-LeanCloudFeedback.release.xcconfig */, 182 | ); 183 | name = Pods; 184 | sourceTree = ""; 185 | }; 186 | /* End PBXGroup section */ 187 | 188 | /* Begin PBXHeadersBuildPhase section */ 189 | 5CCAE0F81B09B0FE009276B9 /* Headers */ = { 190 | isa = PBXHeadersBuildPhase; 191 | buildActionMask = 2147483647; 192 | files = ( 193 | 5CCAE1291B09B323009276B9 /* LCUserFeedbackAgent.h in Headers */, 194 | 5CCAE1281B09B323009276B9 /* LCUserFeedbackThread.h in Headers */, 195 | 5CCAE12E1B09B323009276B9 /* LCUserFeedbackReply.h in Headers */, 196 | 5CCAE1361B09C1F2009276B9 /* LCHttpClient.h in Headers */, 197 | 5CCAE1301B09B323009276B9 /* LCUserFeedbackViewController.h in Headers */, 198 | 70FB0B271B5E76D500FD8D70 /* LCUserFeedbackImageViewController.h in Headers */, 199 | 5CCAE1381B09C1F2009276B9 /* LCUtils.h in Headers */, 200 | 5CCAE1271B09B323009276B9 /* LCUserFeedbackThread_Internal.h in Headers */, 201 | 5CCAE1011B09B0FE009276B9 /* LeanCloudFeedback.h in Headers */, 202 | 5CCAE12A1B09B323009276B9 /* LCUserFeedbackReplyCell.h in Headers */, 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | }; 206 | /* End PBXHeadersBuildPhase section */ 207 | 208 | /* Begin PBXNativeTarget section */ 209 | 5CCAE0FA1B09B0FE009276B9 /* LeanCloudFeedback */ = { 210 | isa = PBXNativeTarget; 211 | buildConfigurationList = 5CCAE1111B09B0FE009276B9 /* Build configuration list for PBXNativeTarget "LeanCloudFeedback" */; 212 | buildPhases = ( 213 | 84898830CC4C08815C7F6A2D /* [CP] Check Pods Manifest.lock */, 214 | 5CCAE0F61B09B0FE009276B9 /* Sources */, 215 | 5CCAE0F71B09B0FE009276B9 /* Frameworks */, 216 | 5CCAE0F81B09B0FE009276B9 /* Headers */, 217 | 5CCAE0F91B09B0FE009276B9 /* Resources */, 218 | 96A4361C0F19D5AEB1038786 /* [CP] Copy Pods Resources */, 219 | ); 220 | buildRules = ( 221 | ); 222 | dependencies = ( 223 | ); 224 | name = LeanCloudFeedback; 225 | productName = LeanCloudFeedback; 226 | productReference = 5CCAE0FB1B09B0FE009276B9 /* LeanCloudFeedback.framework */; 227 | productType = "com.apple.product-type.framework"; 228 | }; 229 | /* End PBXNativeTarget section */ 230 | 231 | /* Begin PBXProject section */ 232 | 5CCAE0F21B09B0FE009276B9 /* Project object */ = { 233 | isa = PBXProject; 234 | attributes = { 235 | LastUpgradeCheck = 1120; 236 | ORGANIZATIONNAME = LeanCloud; 237 | TargetAttributes = { 238 | 5CC504C71B0EFCBA004D0CB1 = { 239 | CreatedOnToolsVersion = 6.3.2; 240 | }; 241 | 5CCAE0FA1B09B0FE009276B9 = { 242 | CreatedOnToolsVersion = 6.3.1; 243 | }; 244 | }; 245 | }; 246 | buildConfigurationList = 5CCAE0F51B09B0FE009276B9 /* Build configuration list for PBXProject "LeanCloudFeedback" */; 247 | compatibilityVersion = "Xcode 3.2"; 248 | developmentRegion = English; 249 | hasScannedForEncodings = 0; 250 | knownRegions = ( 251 | English, 252 | en, 253 | "zh-Hans", 254 | ); 255 | mainGroup = 5CCAE0F11B09B0FE009276B9; 256 | productRefGroup = 5CCAE0FC1B09B0FE009276B9 /* Products */; 257 | projectDirPath = ""; 258 | projectRoot = ""; 259 | targets = ( 260 | 5CCAE0FA1B09B0FE009276B9 /* LeanCloudFeedback */, 261 | 5CC504C71B0EFCBA004D0CB1 /* UniversalFramework */, 262 | ); 263 | }; 264 | /* End PBXProject section */ 265 | 266 | /* Begin PBXResourcesBuildPhase section */ 267 | 5CCAE0F91B09B0FE009276B9 /* Resources */ = { 268 | isa = PBXResourcesBuildPhase; 269 | buildActionMask = 2147483647; 270 | files = ( 271 | 703074C41B9ABA6D00FD796E /* feedback_bg_1@2x.png in Resources */, 272 | 703074E81B9ABDA400FD796E /* feedback_add_image@2x.png in Resources */, 273 | 703074C71B9ABA6D00FD796E /* feedback_btn_new@2x.png in Resources */, 274 | 703074C31B9ABA6D00FD796E /* feedback_back@2x.png in Resources */, 275 | 703074C81B9ABA6D00FD796E /* feedback_warning@2x.png in Resources */, 276 | 7000FCF21BEC9E450097C729 /* LeanCloudFeedback.strings in Resources */, 277 | 703074C51B9ABA6D00FD796E /* feedback_bg_2@2x.png in Resources */, 278 | 703074C61B9ABA6D00FD796E /* feedback_btn@2x.png in Resources */, 279 | ); 280 | runOnlyForDeploymentPostprocessing = 0; 281 | }; 282 | /* End PBXResourcesBuildPhase section */ 283 | 284 | /* Begin PBXShellScriptBuildPhase section */ 285 | 5CC504CB1B0EFCC0004D0CB1 /* ShellScript */ = { 286 | isa = PBXShellScriptBuildPhase; 287 | buildActionMask = 2147483647; 288 | files = ( 289 | ); 290 | inputPaths = ( 291 | ); 292 | outputPaths = ( 293 | ); 294 | runOnlyForDeploymentPostprocessing = 0; 295 | shellPath = /bin/sh; 296 | shellScript = "# This script is based on Jacob Van Order's answer on apple dev forums\n\n# https://devforums.apple.com/message/971277\n# See also http://spin.atomicobject.com/2011/12/13/building-a-universal-framework-for-ios/ for the start\n\n\n# To get this to work with a Xcode 6 Cocoa Touch Framework, create Framework\n# Then create a new Aggregate Target. Throw this script into a Build Script Phrase on the Aggregate\n\n\n######################\n# Options\n######################\n\nREVEAL_ARCHIVE_IN_FINDER=true\n\nFRAMEWORK_NAME=\"${PROJECT_NAME}\"\n\nSIMULATOR_LIBRARY_PATH=\"${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${FRAMEWORK_NAME}.framework\"\n\nDEVICE_LIBRARY_PATH=\"${BUILD_DIR}/${CONFIGURATION}-iphoneos/${FRAMEWORK_NAME}.framework\"\n\nUNIVERSAL_LIBRARY_DIR=\"./build/${CONFIGURATION}-iphoneuniversal\"\n\nFRAMEWORK=\"${UNIVERSAL_LIBRARY_DIR}/${FRAMEWORK_NAME}.framework\"\n\n\n######################\n# Build Frameworks\n######################\n\nxcodebuild -project ${PROJECT_NAME}.xcodeproj -sdk iphonesimulator -target ${PROJECT_NAME} -configuration ${CONFIGURATION} clean build CONFIGURATION_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphonesimulator | echo\n\nxcodebuild -project ${PROJECT_NAME}.xcodeproj -sdk iphoneos -target ${PROJECT_NAME} -configuration ${CONFIGURATION} clean build CONFIGURATION_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphoneos | echo\n\n#xcodebuild -target ${PROJECT_NAME} ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos BUILD_DIR=\"${BUILD_DIR}\" BUILD_ROOT=\"${BUILD_ROOT}\" | echo\n\n#xcodebuild -target ${PROJECT_NAME} ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphonesimulator BUILD_DIR=\"${BUILD_DIR}\" BUILD_ROOT=\"${BUILD_ROOT}\" | echo\n\n\n######################\n# Create directory for universal\n######################\n\nrm -rf \"${UNIVERSAL_LIBRARY_DIR}\"\n\nmkdir \"${UNIVERSAL_LIBRARY_DIR}\"\n\nmkdir \"${FRAMEWORK}\"\n\n\n######################\n# Copy files Framework\n######################\n\ncp -r \"${DEVICE_LIBRARY_PATH}/.\" \"${FRAMEWORK}\"\n\n\n######################\n# Make fat universal binary\n######################\n\nlipo \"${SIMULATOR_LIBRARY_PATH}/${FRAMEWORK_NAME}\" \"${DEVICE_LIBRARY_PATH}/${FRAMEWORK_NAME}\" -create -output \"${FRAMEWORK}/${FRAMEWORK_NAME}\" | echo\n\nchmod -x \"${FRAMEWORK}/${FRAMEWORK_NAME}\"\n\nopen \"${UNIVERSAL_LIBRARY_DIR}/\"\n######################\n# On Release, copy the result to desktop folder\n######################\n\n#if [ \"${CONFIGURATION}\" == \"Release\" ]; then\n#mkdir \"${HOME}/Desktop/${FRAMEWORK_NAME}-${CONFIGURATION}-iphoneuniversal/\"\n#cp -r \"${FRAMEWORK}\" \"${HOME}/Desktop/${FRAMEWORK_NAME}-${CONFIGURATION}-iphoneuniversal/\"\n#fi\n\n\n######################\n# If needed, open the Framework folder\n######################\n\n#if [ ${REVEAL_ARCHIVE_IN_FINDER} = true ]; then\n#if [ \"${CONFIGURATION}\" == \"Release\" ]; then\n#open \"${HOME}/Desktop/${FRAMEWORK_NAME}-${CONFIGURATION}-iphoneuniversal/\"\n#else\n#open \"${UNIVERSAL_LIBRARY_DIR}/\"\n#fi\n#fi"; 297 | }; 298 | 84898830CC4C08815C7F6A2D /* [CP] Check Pods Manifest.lock */ = { 299 | isa = PBXShellScriptBuildPhase; 300 | buildActionMask = 2147483647; 301 | files = ( 302 | ); 303 | inputPaths = ( 304 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 305 | "${PODS_ROOT}/Manifest.lock", 306 | ); 307 | name = "[CP] Check Pods Manifest.lock"; 308 | outputPaths = ( 309 | "$(DERIVED_FILE_DIR)/Pods-LeanCloudFeedback-checkManifestLockResult.txt", 310 | ); 311 | runOnlyForDeploymentPostprocessing = 0; 312 | shellPath = /bin/sh; 313 | 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"; 314 | showEnvVarsInLog = 0; 315 | }; 316 | 96A4361C0F19D5AEB1038786 /* [CP] Copy Pods Resources */ = { 317 | isa = PBXShellScriptBuildPhase; 318 | buildActionMask = 2147483647; 319 | files = ( 320 | ); 321 | inputPaths = ( 322 | "${PODS_ROOT}/Target Support Files/Pods-LeanCloudFeedback/Pods-LeanCloudFeedback-resources.sh", 323 | "${PODS_ROOT}/AVOSCloud/AVOS/AVOSCloud/AVOSCloud_Art.inc", 324 | ); 325 | name = "[CP] Copy Pods Resources"; 326 | outputPaths = ( 327 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AVOSCloud_Art.inc", 328 | ); 329 | runOnlyForDeploymentPostprocessing = 0; 330 | shellPath = /bin/sh; 331 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-LeanCloudFeedback/Pods-LeanCloudFeedback-resources.sh\"\n"; 332 | showEnvVarsInLog = 0; 333 | }; 334 | /* End PBXShellScriptBuildPhase section */ 335 | 336 | /* Begin PBXSourcesBuildPhase section */ 337 | 5CCAE0F61B09B0FE009276B9 /* Sources */ = { 338 | isa = PBXSourcesBuildPhase; 339 | buildActionMask = 2147483647; 340 | files = ( 341 | 5CCAE12F1B09B323009276B9 /* LCUserFeedbackReply.m in Sources */, 342 | 5CCAE1251B09B323009276B9 /* LCUserFeedbackThread.m in Sources */, 343 | 5CCAE12B1B09B323009276B9 /* LCUserFeedbackReplyCell.m in Sources */, 344 | 70FB0B281B5E76D500FD8D70 /* LCUserFeedbackImageViewController.m in Sources */, 345 | 5CCAE1261B09B323009276B9 /* LCUserFeedbackViewController.m in Sources */, 346 | 5CCAE1371B09C1F2009276B9 /* LCUtils.m in Sources */, 347 | 5CCAE1241B09B323009276B9 /* LCUserFeedbackAgent.m in Sources */, 348 | 5CCAE1351B09C1F2009276B9 /* LCHttpClient.m in Sources */, 349 | ); 350 | runOnlyForDeploymentPostprocessing = 0; 351 | }; 352 | /* End PBXSourcesBuildPhase section */ 353 | 354 | /* Begin XCBuildConfiguration section */ 355 | 5CC504C81B0EFCBA004D0CB1 /* Debug */ = { 356 | isa = XCBuildConfiguration; 357 | buildSettings = { 358 | PRODUCT_NAME = "$(TARGET_NAME)"; 359 | }; 360 | name = Debug; 361 | }; 362 | 5CC504C91B0EFCBA004D0CB1 /* Release */ = { 363 | isa = XCBuildConfiguration; 364 | buildSettings = { 365 | PRODUCT_NAME = "$(TARGET_NAME)"; 366 | }; 367 | name = Release; 368 | }; 369 | 5CCAE10F1B09B0FE009276B9 /* Debug */ = { 370 | isa = XCBuildConfiguration; 371 | buildSettings = { 372 | ALWAYS_SEARCH_USER_PATHS = NO; 373 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 374 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 375 | CLANG_CXX_LIBRARY = "libc++"; 376 | CLANG_ENABLE_MODULES = YES; 377 | CLANG_ENABLE_OBJC_ARC = YES; 378 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 379 | CLANG_WARN_BOOL_CONVERSION = YES; 380 | CLANG_WARN_COMMA = YES; 381 | CLANG_WARN_CONSTANT_CONVERSION = YES; 382 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 383 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 384 | CLANG_WARN_EMPTY_BODY = YES; 385 | CLANG_WARN_ENUM_CONVERSION = YES; 386 | CLANG_WARN_INFINITE_RECURSION = YES; 387 | CLANG_WARN_INT_CONVERSION = YES; 388 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 389 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 390 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 391 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 392 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 393 | CLANG_WARN_STRICT_PROTOTYPES = YES; 394 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 395 | CLANG_WARN_UNREACHABLE_CODE = YES; 396 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 397 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 398 | COPY_PHASE_STRIP = NO; 399 | CURRENT_PROJECT_VERSION = 1; 400 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 401 | ENABLE_STRICT_OBJC_MSGSEND = YES; 402 | ENABLE_TESTABILITY = YES; 403 | GCC_C_LANGUAGE_STANDARD = gnu99; 404 | GCC_DYNAMIC_NO_PIC = NO; 405 | GCC_NO_COMMON_BLOCKS = YES; 406 | GCC_OPTIMIZATION_LEVEL = 0; 407 | GCC_PREPROCESSOR_DEFINITIONS = ( 408 | "DEBUG=1", 409 | "$(inherited)", 410 | ); 411 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 412 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 413 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 414 | GCC_WARN_UNDECLARED_SELECTOR = YES; 415 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 416 | GCC_WARN_UNUSED_FUNCTION = YES; 417 | GCC_WARN_UNUSED_VARIABLE = YES; 418 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 419 | MTL_ENABLE_DEBUG_INFO = YES; 420 | ONLY_ACTIVE_ARCH = YES; 421 | SDKROOT = iphoneos; 422 | TARGETED_DEVICE_FAMILY = "1,2"; 423 | VERSIONING_SYSTEM = "apple-generic"; 424 | VERSION_INFO_PREFIX = ""; 425 | }; 426 | name = Debug; 427 | }; 428 | 5CCAE1101B09B0FE009276B9 /* Release */ = { 429 | isa = XCBuildConfiguration; 430 | buildSettings = { 431 | ALWAYS_SEARCH_USER_PATHS = NO; 432 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 433 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 434 | CLANG_CXX_LIBRARY = "libc++"; 435 | CLANG_ENABLE_MODULES = YES; 436 | CLANG_ENABLE_OBJC_ARC = YES; 437 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 438 | CLANG_WARN_BOOL_CONVERSION = YES; 439 | CLANG_WARN_COMMA = YES; 440 | CLANG_WARN_CONSTANT_CONVERSION = YES; 441 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 442 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 443 | CLANG_WARN_EMPTY_BODY = YES; 444 | CLANG_WARN_ENUM_CONVERSION = YES; 445 | CLANG_WARN_INFINITE_RECURSION = YES; 446 | CLANG_WARN_INT_CONVERSION = YES; 447 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 448 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 449 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 450 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 451 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 452 | CLANG_WARN_STRICT_PROTOTYPES = YES; 453 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 454 | CLANG_WARN_UNREACHABLE_CODE = YES; 455 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 456 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 457 | COPY_PHASE_STRIP = NO; 458 | CURRENT_PROJECT_VERSION = 1; 459 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 460 | ENABLE_NS_ASSERTIONS = NO; 461 | ENABLE_STRICT_OBJC_MSGSEND = YES; 462 | GCC_C_LANGUAGE_STANDARD = gnu99; 463 | GCC_NO_COMMON_BLOCKS = YES; 464 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 465 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 466 | GCC_WARN_UNDECLARED_SELECTOR = YES; 467 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 468 | GCC_WARN_UNUSED_FUNCTION = YES; 469 | GCC_WARN_UNUSED_VARIABLE = YES; 470 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 471 | MTL_ENABLE_DEBUG_INFO = NO; 472 | SDKROOT = iphoneos; 473 | TARGETED_DEVICE_FAMILY = "1,2"; 474 | VALIDATE_PRODUCT = YES; 475 | VERSIONING_SYSTEM = "apple-generic"; 476 | VERSION_INFO_PREFIX = ""; 477 | }; 478 | name = Release; 479 | }; 480 | 5CCAE1121B09B0FE009276B9 /* Debug */ = { 481 | isa = XCBuildConfiguration; 482 | baseConfigurationReference = D94FDB034BF527C80C647B30 /* Pods-LeanCloudFeedback.debug.xcconfig */; 483 | buildSettings = { 484 | ALWAYS_SEARCH_USER_PATHS = NO; 485 | CODE_SIGN_IDENTITY = ""; 486 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 487 | DEFINES_MODULE = YES; 488 | DYLIB_COMPATIBILITY_VERSION = 1; 489 | DYLIB_CURRENT_VERSION = 1; 490 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 491 | FRAMEWORK_SEARCH_PATHS = ( 492 | "$(inherited)", 493 | "$(PROJECT_DIR)", 494 | ); 495 | INFOPLIST_FILE = LeanCloudFeedback/Info.plist; 496 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 497 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 498 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 499 | MACH_O_TYPE = staticlib; 500 | PRODUCT_BUNDLE_IDENTIFIER = "com.leancloud.$(PRODUCT_NAME:rfc1034identifier)"; 501 | PRODUCT_NAME = "$(TARGET_NAME)"; 502 | SKIP_INSTALL = YES; 503 | }; 504 | name = Debug; 505 | }; 506 | 5CCAE1131B09B0FE009276B9 /* Release */ = { 507 | isa = XCBuildConfiguration; 508 | baseConfigurationReference = 59AC16570E133BA472B3DABB /* Pods-LeanCloudFeedback.release.xcconfig */; 509 | buildSettings = { 510 | ALWAYS_SEARCH_USER_PATHS = NO; 511 | CODE_SIGN_IDENTITY = ""; 512 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 513 | DEFINES_MODULE = YES; 514 | DYLIB_COMPATIBILITY_VERSION = 1; 515 | DYLIB_CURRENT_VERSION = 1; 516 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 517 | FRAMEWORK_SEARCH_PATHS = ( 518 | "$(inherited)", 519 | "$(PROJECT_DIR)", 520 | ); 521 | INFOPLIST_FILE = LeanCloudFeedback/Info.plist; 522 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 523 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 524 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 525 | MACH_O_TYPE = staticlib; 526 | PRODUCT_BUNDLE_IDENTIFIER = "com.leancloud.$(PRODUCT_NAME:rfc1034identifier)"; 527 | PRODUCT_NAME = "$(TARGET_NAME)"; 528 | SKIP_INSTALL = YES; 529 | }; 530 | name = Release; 531 | }; 532 | /* End XCBuildConfiguration section */ 533 | 534 | /* Begin XCConfigurationList section */ 535 | 5CC504CA1B0EFCBA004D0CB1 /* Build configuration list for PBXAggregateTarget "UniversalFramework" */ = { 536 | isa = XCConfigurationList; 537 | buildConfigurations = ( 538 | 5CC504C81B0EFCBA004D0CB1 /* Debug */, 539 | 5CC504C91B0EFCBA004D0CB1 /* Release */, 540 | ); 541 | defaultConfigurationIsVisible = 0; 542 | defaultConfigurationName = Release; 543 | }; 544 | 5CCAE0F51B09B0FE009276B9 /* Build configuration list for PBXProject "LeanCloudFeedback" */ = { 545 | isa = XCConfigurationList; 546 | buildConfigurations = ( 547 | 5CCAE10F1B09B0FE009276B9 /* Debug */, 548 | 5CCAE1101B09B0FE009276B9 /* Release */, 549 | ); 550 | defaultConfigurationIsVisible = 0; 551 | defaultConfigurationName = Release; 552 | }; 553 | 5CCAE1111B09B0FE009276B9 /* Build configuration list for PBXNativeTarget "LeanCloudFeedback" */ = { 554 | isa = XCConfigurationList; 555 | buildConfigurations = ( 556 | 5CCAE1121B09B0FE009276B9 /* Debug */, 557 | 5CCAE1131B09B0FE009276B9 /* Release */, 558 | ); 559 | defaultConfigurationIsVisible = 0; 560 | defaultConfigurationName = Release; 561 | }; 562 | /* End XCConfigurationList section */ 563 | }; 564 | rootObject = 5CCAE0F21B09B0FE009276B9 /* Project object */; 565 | } 566 | --------------------------------------------------------------------------------