diff --git a/Podfile.lock b/Podfile.lock index ac242a5a..4f1ab3ae 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -52,7 +52,7 @@ DEPENDENCIES: EXTERNAL SOURCES: MZFayeClient: - :path: "../CatchLib-iOS/MZFayeClient/" + :path: ../CatchLib-iOS/MZFayeClient/ SPEC CHECKSUMS: 1PasswordExtension: 676a216461ffa9189db66541b5a2465e10ae4743 diff --git a/Yep.xcodeproj/project.pbxproj b/Yep.xcodeproj/project.pbxproj index 78880765..34bd7fa0 100644 --- a/Yep.xcodeproj/project.pbxproj +++ b/Yep.xcodeproj/project.pbxproj @@ -207,6 +207,7 @@ 50A640EA1B6F45FA0050E4A3 /* DoNotDisturbPeriodCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 50A640E81B6F45FA0050E4A3 /* DoNotDisturbPeriodCell.xib */; }; 50A640EC1B6F4E420050E4A3 /* DoNotDisturbPeriodViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A640EB1B6F4E420050E4A3 /* DoNotDisturbPeriodViewController.swift */; }; 50A820241B969B0F007CD30E /* NSTimeZone+Yep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A820231B969B0F007CD30E /* NSTimeZone+Yep.swift */; }; + 50A93A9D1BD8C515007005DB /* FeedSkillPickerItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A93A9C1BD8C515007005DB /* FeedSkillPickerItemView.swift */; settings = {ASSET_TAGS = (); }; }; 50A9D44B1ACD37A8000B2599 /* NSFileManager+Yep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A9D44A1ACD37A8000B2599 /* NSFileManager+Yep.swift */; }; 50A9D44E1ACD382B000B2599 /* NSDateTimeAgo.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 50A9D44C1ACD382B000B2599 /* NSDateTimeAgo.bundle */; }; 50A9D4511ACD383E000B2599 /* NSDate+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A9D4501ACD383E000B2599 /* NSDate+Extension.swift */; }; @@ -510,6 +511,7 @@ 50A640E81B6F45FA0050E4A3 /* DoNotDisturbPeriodCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = DoNotDisturbPeriodCell.xib; path = Views/Cells/DoNotDisturb/DoNotDisturbPeriodCell.xib; sourceTree = ""; }; 50A640EB1B6F4E420050E4A3 /* DoNotDisturbPeriodViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DoNotDisturbPeriodViewController.swift; path = ViewControllers/DoNotDisturbPeriod/DoNotDisturbPeriodViewController.swift; sourceTree = ""; }; 50A820231B969B0F007CD30E /* NSTimeZone+Yep.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "NSTimeZone+Yep.swift"; path = "Extensions/NSTimeZone+Yep.swift"; sourceTree = ""; }; + 50A93A9C1BD8C515007005DB /* FeedSkillPickerItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FeedSkillPickerItemView.swift; path = Views/PickerItems/FeedSkill/FeedSkillPickerItemView.swift; sourceTree = ""; }; 50A9D44A1ACD37A8000B2599 /* NSFileManager+Yep.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "NSFileManager+Yep.swift"; path = "Extensions/NSFileManager+Yep.swift"; sourceTree = ""; }; 50A9D44C1ACD382B000B2599 /* NSDateTimeAgo.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; name = NSDateTimeAgo.bundle; path = Extensions/NSDateTimeAgo.bundle; sourceTree = ""; }; 50A9D4501ACD383E000B2599 /* NSDate+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "NSDate+Extension.swift"; path = "Extensions/NSDate+Extension.swift"; sourceTree = ""; }; @@ -994,6 +996,7 @@ 50AACEC91BB54132004DD54B /* Feed */, 50F5AF891BBBB2B30033C9BC /* HorizontalLine */, 50535D1B1BCD19B4002FE1A4 /* TouchClosures */, + 50A93A9E1BD8C51D007005DB /* PickerItems */, ); name = Views; sourceTree = ""; @@ -1676,6 +1679,22 @@ name = DoNotDisturbPeriod; sourceTree = ""; }; + 50A93A9E1BD8C51D007005DB /* PickerItems */ = { + isa = PBXGroup; + children = ( + 50A93A9F1BD8C52C007005DB /* FeedSkill */, + ); + name = PickerItems; + sourceTree = ""; + }; + 50A93A9F1BD8C52C007005DB /* FeedSkill */ = { + isa = PBXGroup; + children = ( + 50A93A9C1BD8C515007005DB /* FeedSkillPickerItemView.swift */, + ); + name = FeedSkill; + sourceTree = ""; + }; 50A9D4581ACD3943000B2599 /* AudioWaves */ = { isa = PBXGroup; children = ( @@ -2450,6 +2469,7 @@ 84385C441B8592120071130D /* ShowStepGeniusViewController.swift in Sources */, 6A5632931BC911CB00397B53 /* DiscoverNormalUserCell.swift in Sources */, 5053AD5A1AF83B4200B3CBFA /* ChatLeftLocationCell.swift in Sources */, + 50A93A9D1BD8C515007005DB /* FeedSkillPickerItemView.swift in Sources */, 50A640E91B6F45FA0050E4A3 /* DoNotDisturbPeriodCell.swift in Sources */, 502B605A1BD1102B00D9C17E /* TitleCell.swift in Sources */, 840CD23C1B7B59D5000D2680 /* ShowStepViewController.swift in Sources */, diff --git a/Yep/Images.xcassets/feed_skill_channel_arrow.imageset/Contents.json b/Yep/Images.xcassets/feed_skill_channel_arrow.imageset/Contents.json new file mode 100644 index 00000000..d4431997 --- /dev/null +++ b/Yep/Images.xcassets/feed_skill_channel_arrow.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "feed_skill_channel_arrow.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Yep/Images.xcassets/feed_skill_channel_arrow.imageset/feed_skill_channel_arrow.pdf b/Yep/Images.xcassets/feed_skill_channel_arrow.imageset/feed_skill_channel_arrow.pdf new file mode 100644 index 00000000..28649110 Binary files /dev/null and b/Yep/Images.xcassets/feed_skill_channel_arrow.imageset/feed_skill_channel_arrow.pdf differ diff --git a/Yep/Images.xcassets/skill_bubble_empty.imageset/Contents.json b/Yep/Images.xcassets/skill_bubble_empty.imageset/Contents.json new file mode 100644 index 00000000..830112a3 --- /dev/null +++ b/Yep/Images.xcassets/skill_bubble_empty.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "skill_bubble_empty.pdf", + "resizing" : { + "mode" : "3-part-horizontal", + "center" : { + "mode" : "tile", + "width" : 1 + }, + "cap-insets" : { + "right" : 11, + "left" : 11 + } + } + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Yep/Images.xcassets/skill_bubble_empty.imageset/skill_bubble_empty.pdf b/Yep/Images.xcassets/skill_bubble_empty.imageset/skill_bubble_empty.pdf new file mode 100644 index 00000000..d8df7280 Binary files /dev/null and b/Yep/Images.xcassets/skill_bubble_empty.imageset/skill_bubble_empty.pdf differ diff --git a/Yep/Services/YepService.swift b/Yep/Services/YepService.swift index 89dd26db..d03e27f5 100644 --- a/Yep/Services/YepService.swift +++ b/Yep/Services/YepService.swift @@ -178,6 +178,31 @@ struct Skill: Hashable { var hashValue: Int { return id.hashValue } + + static func fromJSONDictionary(skillInfo: JSONDictionary) -> Skill? { + if + let skillID = skillInfo["id"] as? String, + let skillName = skillInfo["name"] as? String, + let skillLocalName = skillInfo["name_string"] as? String { + + var skillCategory: SkillCategory? + if + let skillCategoryData = skillInfo["category"] as? JSONDictionary, + let categoryID = skillCategoryData["id"] as? String, + let categoryName = skillCategoryData["name"] as? String, + let categoryLocalName = skillCategoryData["name_string"] as? String { + skillCategory = SkillCategory(id: categoryID, name: categoryName, localName: categoryLocalName, skills: []) + } + + let coverURLString = skillInfo["cover_url"] as? String + + let skill = Skill(category: skillCategory, id: skillID, name: skillName, localName: skillLocalName, coverURLString: coverURLString) + + return skill + } + + return nil + } } func ==(lhs: Skill, rhs: Skill) -> Bool { @@ -222,25 +247,9 @@ func skillsFromSkillsData(skillsData: [JSONDictionary]) -> [Skill] { var skills = [Skill]() for skillInfo in skillsData { - if - let skillID = skillInfo["id"] as? String, - let skillName = skillInfo["name"] as? String, - let skillLocalName = skillInfo["name_string"] as? String { - var skillCategory: SkillCategory? - if - let skillCategoryData = skillInfo["category"] as? JSONDictionary, - let categoryID = skillCategoryData["id"] as? String, - let categoryName = skillCategoryData["name"] as? String, - let categoryLocalName = skillCategoryData["name_string"] as? String { - skillCategory = SkillCategory(id: categoryID, name: categoryName, localName: categoryLocalName, skills: []) - } - - let coverURLString = skillInfo["cover_url"] as? String - - let skill = Skill(category: skillCategory, id: skillID, name: skillName, localName: skillLocalName, coverURLString: coverURLString) - - skills.append(skill) + if let skill = Skill.fromJSONDictionary(skillInfo) { + skills.append(skill) } } @@ -2300,7 +2309,12 @@ struct DiscoveredFeed: Hashable { let attachments = attachmentsData.map({ DiscoveredAttachment.fromJSONDictionary($0) }).flatMap({ $0 }) - return DiscoveredFeed(id: id, allowComment: allowComment, createdUnixTime: createdUnixTime, updatedUnixTime: updatedUnixTime, creator: creator, body: body, attachments: attachments, distance: distance, skill: nil, groupID: groupID, messageCount: messageCount) + var skill: Skill? + if let skillInfo = json["skill"] as? JSONDictionary { + skill = Skill.fromJSONDictionary(skillInfo) + } + + return DiscoveredFeed(id: id, allowComment: allowComment, createdUnixTime: createdUnixTime, updatedUnixTime: updatedUnixTime, creator: creator, body: body, attachments: attachments, distance: distance, skill: skill, groupID: groupID, messageCount: messageCount) } } diff --git a/Yep/ViewControllers/NewFeed/NewFeed.storyboard b/Yep/ViewControllers/NewFeed/NewFeed.storyboard index 7111d8ff..b3a9728f 100644 --- a/Yep/ViewControllers/NewFeed/NewFeed.storyboard +++ b/Yep/ViewControllers/NewFeed/NewFeed.storyboard @@ -17,55 +17,161 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + @@ -82,4 +188,8 @@ + + + + diff --git a/Yep/ViewControllers/NewFeed/NewFeedViewController.swift b/Yep/ViewControllers/NewFeed/NewFeedViewController.swift index 225d96b8..6c0f5723 100644 --- a/Yep/ViewControllers/NewFeed/NewFeedViewController.swift +++ b/Yep/ViewControllers/NewFeed/NewFeedViewController.swift @@ -7,20 +7,35 @@ // import UIKit -import Proposer import CoreLocation import MobileCoreServices import Photos +import Proposer +import RealmSwift class NewFeedViewController: UIViewController { - @IBOutlet weak var feedWhiteBGView: UIView! - var afterCreatedFeedAction: ((feed: DiscoveredFeed) -> Void)? + @IBOutlet weak var feedWhiteBGView: UIView! + @IBOutlet weak var messageTextView: UITextView! @IBOutlet weak var mediaCollectionView: UICollectionView! + @IBOutlet weak var channelView: UIView! + @IBOutlet weak var channelViewTopConstraint: NSLayoutConstraint! + + @IBOutlet weak var channelViewTopLineView: HorizontalLineView! + @IBOutlet weak var channelViewBottomLineView: HorizontalLineView! + + @IBOutlet weak var channelLabel: UILabel! + @IBOutlet weak var choosePromptLabel: UILabel! + + @IBOutlet weak var pickedSkillBubbleImageView: UIImageView! + @IBOutlet weak var pickedSkillLabel: UILabel! + + @IBOutlet weak var skillPickerView: UIPickerView! + var imageAssets: [PHAsset] = [] var mediaImages = [UIImage]() { @@ -34,6 +49,27 @@ class NewFeedViewController: UIViewController { let feedMediaAddCellID = "FeedMediaAddCell" let feedMediaCellID = "FeedMediaCell" + let max = Int(INT16_MAX) + + let skills: [Skill] = { + if let + myUserID = YepUserDefaults.userID.value, + realm = try? Realm(), + me = userWithUserID(myUserID, inRealm: realm) { + return skillsFromUserSkillList(me.masterSkills) + skillsFromUserSkillList(me.learningSkills) + } + + return [] + }() + + var pickedSkill: Skill? { + willSet { + pickedSkillLabel.text = newValue?.localName + + choosePromptLabel.hidden = (newValue != nil) + } + } + override func viewDidLoad() { super.viewDidLoad() @@ -53,7 +89,7 @@ class NewFeedViewController: UIViewController { messageTextView.text = "" messageTextView.textContainer.lineFragmentPadding = 0 messageTextView.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) - messageTextView.becomeFirstResponder() + //messageTextView.becomeFirstResponder() mediaCollectionView.backgroundColor = UIColor.clearColor() @@ -63,6 +99,23 @@ class NewFeedViewController: UIViewController { mediaCollectionView.dataSource = self mediaCollectionView.delegate = self + // pick skill + + channelLabel.text = NSLocalizedString("Channel:", comment: "") + choosePromptLabel.text = NSLocalizedString("Choose...", comment: "") + + channelViewTopConstraint.constant = 30 + + skillPickerView.alpha = 0 + + pickedSkillBubbleImageView.alpha = 0 + pickedSkillLabel.alpha = 0 + + channelView.backgroundColor = UIColor.whiteColor() + channelView.userInteractionEnabled = true + let tap = UITapGestureRecognizer(target: self, action: "showSkillPickerView:") + channelView.addGestureRecognizer(tap) + // try turn on location let locationResource = PrivateResource.Location(.WhenInUse) @@ -106,8 +159,62 @@ class NewFeedViewController: UIViewController { // MARK: Actions + func showSkillPickerView(tap: UITapGestureRecognizer) { + + if pickedSkill == nil { + if !skills.isEmpty { + let centerRow = max / 2 + skillPickerView.selectRow(centerRow, inComponent: 0, animated: false) + pickedSkill = skills[centerRow % skills.count] + } + } + + UIView.animateWithDuration(0.25, delay: 0.0, options: .CurveEaseInOut, animations: { [weak self] in + + self?.channelView.backgroundColor = UIColor.clearColor() + self?.channelViewTopLineView.alpha = 0 + self?.channelViewBottomLineView.alpha = 0 + self?.choosePromptLabel.alpha = 0 + + self?.pickedSkillBubbleImageView.alpha = 0 + self?.pickedSkillLabel.alpha = 0 + + self?.skillPickerView.alpha = 1 + + self?.channelViewTopConstraint.constant = 108 + self?.view.layoutIfNeeded() + + }, completion: { [weak self] _ in + self?.channelView.userInteractionEnabled = false + }) + } + + func hideSkillPickerView() { + + UIView.animateWithDuration(0.25, delay: 0.0, options: .CurveEaseInOut, animations: { [weak self] in + + self?.channelView.backgroundColor = UIColor.whiteColor() + self?.channelViewTopLineView.alpha = 1 + self?.channelViewBottomLineView.alpha = 1 + self?.choosePromptLabel.alpha = 1 + + self?.pickedSkillBubbleImageView.alpha = 1 + self?.pickedSkillLabel.alpha = 1 + + self?.skillPickerView.alpha = 0 + + self?.channelViewTopConstraint.constant = 30 + self?.view.layoutIfNeeded() + + }, completion: { [weak self] _ in + self?.channelView.userInteractionEnabled = true + }) + } + func cancel(sender: UIBarButtonItem) { + messageTextView.resignFirstResponder() + self.dismissViewControllerAnimated(true, completion: nil) } @@ -148,7 +255,7 @@ class NewFeedViewController: UIViewController { } }) - dispatch_group_notify(uploadMediaImagesGroup, dispatch_get_main_queue()) { + dispatch_group_notify(uploadMediaImagesGroup, dispatch_get_main_queue()) { [weak self] in var mediaInfo: JSONDictionary? @@ -166,7 +273,7 @@ class NewFeedViewController: UIViewController { ] } - createFeedWithMessage(message, attachments: mediaInfo, coordinate: coordinate, skill: nil, allowComment: true, failureHandler: { [weak self] reason, errorMessage in + createFeedWithMessage(message, attachments: mediaInfo, coordinate: coordinate, skill: self?.pickedSkill, allowComment: true, failureHandler: { [weak self] reason, errorMessage in defaultFailureHandler(reason, errorMessage: errorMessage) YepAlert.alertSorry(message: errorMessage ?? NSLocalizedString("Create feed failed!", comment: ""), inViewController: self) @@ -263,3 +370,61 @@ extension NewFeedViewController: UICollectionViewDataSource, UICollectionViewDel } } +// MARK: - UIScrollViewDelegate + +extension NewFeedViewController: UITextViewDelegate { + + func textViewDidBeginEditing(textView: UITextView) { + + hideSkillPickerView() + } +} + +// MARK: - UIScrollViewDelegate + +extension NewFeedViewController: UIScrollViewDelegate { + + func scrollViewWillBeginDragging(scrollView: UIScrollView) { + + messageTextView.resignFirstResponder() + } +} + +// MARK: - UIPickerViewDataSource, UIPickerViewDelegate + +extension NewFeedViewController: UIPickerViewDataSource, UIPickerViewDelegate { + + func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int { + return skills.isEmpty ? 0 : 1 + } + + func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return max + } + + func pickerView(pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { + return 44 + } + + func pickerView(pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusingView view: UIView?) -> UIView { + + let skill = skills[row % skills.count] + + if let view = view as? FeedSkillPickerItemView { + view.configureWithSkill(skill) + return view + + } else { + let view = FeedSkillPickerItemView() + view.configureWithSkill(skill) + return view + } + } + + func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + + pickedSkill = skills[row % skills.count] + } +} + + diff --git a/Yep/Views/Cells/Feed/FeedCell.swift b/Yep/Views/Cells/Feed/FeedCell.swift index afecb8e1..9a214dfb 100644 --- a/Yep/Views/Cells/Feed/FeedCell.swift +++ b/Yep/Views/Cells/Feed/FeedCell.swift @@ -13,6 +13,8 @@ class FeedCell: UITableViewCell { @IBOutlet weak var avatarImageView: UIImageView! @IBOutlet weak var nicknameLabel: UILabel! @IBOutlet weak var distanceLabel: UILabel! + @IBOutlet weak var skillBubbleImageView: UIImageView! + @IBOutlet weak var skillLabel: UILabel! @IBOutlet weak var messageTextView: FeedTextView! @IBOutlet weak var messageTextViewHeightConstraint: NSLayoutConstraint! @@ -78,6 +80,7 @@ class FeedCell: UITableViewCell { distanceLabel.textColor = UIColor.grayColor() timeLabel.textColor = UIColor.grayColor() messageCountLabel.textColor = UIColor.yepTintColor() + skillLabel.textColor = UIColor.yepTintColor() messageTextView.font = UIFont.feedMessageFont() messageTextView.textContainer.lineFragmentPadding = 0 @@ -144,6 +147,17 @@ class FeedCell: UITableViewCell { calHeightOfMessageTextView() + if let skill = feed.skill { + skillLabel.text = skill.localName + + skillBubbleImageView.hidden = false + skillLabel.hidden = false + + } else { + skillBubbleImageView.hidden = true + skillLabel.hidden = true + } + let hasMedia = !feed.attachments.isEmpty if feed.attachments.count > 1 { diff --git a/Yep/Views/Cells/Feed/FeedCell.xib b/Yep/Views/Cells/Feed/FeedCell.xib index 82cdc640..7f9bb0ff 100644 --- a/Yep/Views/Cells/Feed/FeedCell.xib +++ b/Yep/Views/Cells/Feed/FeedCell.xib @@ -43,7 +43,7 @@ - + @@ -61,7 +61,7 @@