Commit 96d5810f authored by Aral Balkan's avatar Aral Balkan
Browse files

Integrated the messaging UI spike. Asset URL rewriting isn’t working (as...

Integrated the messaging UI spike. Asset URL rewriting isn’t working (as expected) but all else appears to be.
parent b0f9dd68
......@@ -69,6 +69,11 @@
A79C8B951A8CDF3300F1948A /* VoiceOverStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = A79C8B941A8CDF3300F1948A /* VoiceOverStatus.swift */; };
A79C8BAD1A8D5F2800F1948A /* balloon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = A79C8BAC1A8D5F2800F1948A /* balloon@2x.png */; };
A79EDA861AA4FDC400125690 /* last-panel-green-corner.png in Resources */ = {isa = PBXBuildFile; fileRef = A79EDA851AA4FDC400125690 /* last-panel-green-corner.png */; };
A79EDA8A1AA5033000125690 /* index.html in Resources */ = {isa = PBXBuildFile; fileRef = A79EDA891AA5033000125690 /* index.html */; };
A79EDA901AA5035C00125690 /* TimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A79EDA8C1AA5035C00125690 /* TimelineViewController.swift */; };
A79EDA911AA5035C00125690 /* MessageSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A79EDA8D1AA5035C00125690 /* MessageSplitViewController.swift */; };
A79EDA921AA5035C00125690 /* MessageEditorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A79EDA8E1AA5035C00125690 /* MessageEditorViewController.swift */; };
A79EDA931AA5035C00125690 /* IndieGrowingTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A79EDA8F1AA5035C00125690 /* IndieGrowingTextView.swift */; };
A7A14C631A82560000586EAD /* StepOneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7A14C621A82560000586EAD /* StepOneView.swift */; };
A7A14C651A8277C300586EAD /* SetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7A14C641A8277C300586EAD /* SetupView.swift */; };
A7A14C691A83A9C100586EAD /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = A7A14C6B1A83A9C100586EAD /* InfoPlist.strings */; };
......@@ -187,6 +192,11 @@
A79C8B941A8CDF3300F1948A /* VoiceOverStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VoiceOverStatus.swift; sourceTree = "<group>"; };
A79C8BAC1A8D5F2800F1948A /* balloon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "balloon@2x.png"; sourceTree = "<group>"; };
A79EDA851AA4FDC400125690 /* last-panel-green-corner.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "last-panel-green-corner.png"; sourceTree = "<group>"; };
A79EDA891AA5033000125690 /* index.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = index.html; path = html/index.html; sourceTree = "<group>"; };
A79EDA8C1AA5035C00125690 /* TimelineViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineViewController.swift; sourceTree = "<group>"; };
A79EDA8D1AA5035C00125690 /* MessageSplitViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageSplitViewController.swift; sourceTree = "<group>"; };
A79EDA8E1AA5035C00125690 /* MessageEditorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageEditorViewController.swift; sourceTree = "<group>"; };
A79EDA8F1AA5035C00125690 /* IndieGrowingTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IndieGrowingTextView.swift; sourceTree = "<group>"; };
A7A14C621A82560000586EAD /* StepOneView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StepOneView.swift; sourceTree = "<group>"; };
A7A14C641A8277C300586EAD /* SetupView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetupView.swift; sourceTree = "<group>"; };
A7A14C661A83A43900586EAD /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Main.strings; sourceTree = "<group>"; };
......@@ -292,6 +302,7 @@
A7296BFF1AA337C200D64CDA /* Components */ = {
isa = PBXGroup;
children = (
A79EDA8B1AA5034900125690 /* Main UI component */,
A7296C001AA337D900D64CDA /* Vertical tab bar component */,
);
name = Components;
......@@ -505,6 +516,26 @@
name = source.ind.ie;
sourceTree = "<group>";
};
A79EDA881AA5031A00125690 /* HTML */ = {
isa = PBXGroup;
children = (
A79EDA891AA5033000125690 /* index.html */,
);
name = HTML;
sourceTree = "<group>";
};
A79EDA8B1AA5034900125690 /* Main UI component */ = {
isa = PBXGroup;
children = (
A79EDA881AA5031A00125690 /* HTML */,
A79EDA8C1AA5035C00125690 /* TimelineViewController.swift */,
A79EDA8D1AA5035C00125690 /* MessageSplitViewController.swift */,
A79EDA8E1AA5035C00125690 /* MessageEditorViewController.swift */,
A79EDA8F1AA5035C00125690 /* IndieGrowingTextView.swift */,
);
name = "Main UI component";
sourceTree = "<group>";
};
A7A14C761A83FBA600586EAD /* github.com/damienromito/NSString-Matcher */ = {
isa = PBXGroup;
children = (
......@@ -739,6 +770,7 @@
A7E7D7CE1A616B3E003501C1 /* transparent.png in Resources */,
A7CD7F121A6FBB2400BE31D6 /* node.js in Resources */,
A7296C141AA337F900D64CDA /* Conversations-Template.png in Resources */,
A79EDA8A1AA5033000125690 /* index.html in Resources */,
A7296C121AA337F900D64CDA /* All-Friends-Template.png in Resources */,
A7E7D7CB1A616B3E003501C1 /* topLeftCornerIndicatorTemplate@2x.png in Resources */,
A7296C161AA337F900D64CDA /* Everyone-At-Indie-Template.png in Resources */,
......@@ -799,12 +831,16 @@
A77D76DF1A99207600F74F00 /* TransparentWindow.swift in Sources */,
A70634BB1A63114200A75BC0 /* StepOneViewController.swift in Sources */,
A77D76EF1A9922C800F74F00 /* ADBGeometry.m in Sources */,
A79EDA921AA5035C00125690 /* MessageEditorViewController.swift in Sources */,
A7A14C631A82560000586EAD /* StepOneView.swift in Sources */,
A79EDA901AA5035C00125690 /* TimelineViewController.swift in Sources */,
A77D76F01A9922C800F74F00 /* NSWindow+ADBWindowDimensions.m in Sources */,
A74CC6611A668A7B0083B288 /* Helpers.swift in Sources */,
A7E7D7BA1A616B1E003501C1 /* ContentViewController.swift in Sources */,
A7CC20071A6C992F00B49AA9 /* SwiftyJSON.swift in Sources */,
A79EDA911AA5035C00125690 /* MessageSplitViewController.swift in Sources */,
A75AABCC1A5ECFB000B105BA /* ViewController.swift in Sources */,
A79EDA931AA5035C00125690 /* IndieGrowingTextView.swift in Sources */,
A78DAE7C1A7FCA60009FDB15 /* NoodleCustomImageRep.m in Sources */,
A7E7D7B51A616B0B003501C1 /* IndieSplitView.swift in Sources */,
A77D76E11A99208200F74F00 /* MainWindow.swift in Sources */,
......
This diff is collapsed.
//
// IndieGrowingTextView.swift
// MessageTextEntryTextViewSpike
// Created by Aral Balkan on 03/01/2015.
// Copyright (c) 2015 ind.ie. All rights reserved.
//
// Adapted from TSTTextGrowth Objective-C class by
// Douglas Heriot
// (https://github.com/DouglasHeriot/AutoGrowingNSTextField)
//
// Based on the work of Scott O’Brien and inspried by:
// https://github.com/jerrykrinock/CategoriesObjC/blob/master/NS(Attributed)String%2BGeometrics/NS(Attributed)String%2BGeometrics.m
// https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/TextLayout/Tasks/StringHeight.html
// http://stackoverflow.com/questions/14107385/getting-a-nstextfield-to-grow-with-the-text-in-auto-layout
//
import Cocoa
enum Method:Selector {
case textDidBeginEditing = "textDidBeginEditing:"
case textDidEndEditing = "textDidEndEditing:"
case textDidChange = "textDidChange:"
static func named(methodName:Method) -> Selector
{
return methodName.rawValue
}
}
class IndieGrowingTextView: NSTextView {
override func didChangeText()
{
super.didChangeText()
if let textContainer = self.textContainer
{
if let layoutManager = textContainer.layoutManager
{
let usedRect:NSRect = layoutManager.usedRectForTextContainer(textContainer)
println("usedRect.size.height: \(usedRect.size.height)")
// Post a notification of the height change
// TODO: Only do on change
NSNotificationCenter.defaultCenter().postNotificationName("IndieTextViewContentHeightDidChange", object: self, userInfo: ["height": usedRect.size.height])
}
}
}
}
//
// MessageEditorViewController.swift
// MessageTextEntrySpike
//
// Created by Aral Balkan on 03/01/2015.
// Copyright (c) 2015 ind.ie. All rights reserved.
//
import Cocoa
public enum MessageNotification:String {
case SendMessage = "MessageSendNotification"
//
// Static convenience function so consuming code can have clearer intent
static func named(notification:MessageNotification) -> String {
return notification.rawValue
}
}
class MessageEditorViewController: NSViewController, NSTextViewDelegate, NSTextStorageDelegate {
@IBOutlet weak var stackViewHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var verticalSpaceToTopConstraint: NSLayoutConstraint!
@IBOutlet var messageTextView: IndieGrowingTextView!
override func viewDidLoad() {
super.viewDidLoad()
let 📡 = NSNotificationCenter.defaultCenter()
📡.when(youGet: "IndieTextViewContentHeightDidChange", call: "textViewContentHeightDidChange:", on: self)
// Vertical and horizontal insets between the text and the border
// of the TextView.
messageTextView.textContainerInset = NSMakeSize(10.0, 10.0)
// Initial stack view height constant.
stackViewHeightConstraint.constant = 76.0
messageTextView.textStorage?.delegate = self
}
// override func viewWillAppear() {
//
// }
func postMessage()
{
NSNotificationCenter.defaultCenter().postNotificationName("MessageSendNotification", object: nil, userInfo: ["message": messageTextView.attributedString()])
// .postNotification(notification: "MessageSendNotification", object: nil, userInfo: ["message": messageTextView.string])
messageTextView.string = ""
stackViewHeightConstraint.constant = 76.0
}
func textViewContentHeightDidChange(notification:NSNotification)
{
var x:AnyObject? = (notification.userInfo as Dictionary!)["height"]
var contentHeight:CGFloat = x as! CGFloat
println("<-----> \(contentHeight)")
// println("Height: \(contentHeight)")
// Limit to five lines at 14pt lines
if contentHeight > 70.0 {
contentHeight = CGFloat(70.0)
}
stackViewHeightConstraint.constant = contentHeight + 62.0
// println("Constant: \(stackViewHeightConstraint.constant)")
self.view.invalidateIntrinsicContentSize()
// //
// // Inspector bar test
// // With help from: http://stackoverflow.com/questions/11785350/positioning-inspector-bar-for-nstextview
// //
// let titlebarContainerView = self.view.window!.contentView.superview!!.subviews[1] as NSView
// let titlebarView = titlebarContainerView.subviews[0] as NSView
// let nsView = titlebarView.subviews[3] as NSView
// if let nsClipView = nsView.subviews[0] as? NSView {
// if nsClipView.subviews.count > 0 {
// let nsInspectorBarView = nsClipView.subviews[0] as NSView
//
// self.view.window?.visualizeConstraints(nsInspectorBarView.constraints)
//
// nsInspectorBarView.removeFromSuperview()
// self.view.addSubview(nsInspectorBarView)
// }
// }
}
// MARK: - NSTextViewDelegate methods
func textView(textView: NSTextView, doCommandBySelector commandSelector: Selector) -> Bool {
println("textView: \(textView) - commandSelector: \(commandSelector)")
if let currentEvent = NSApplication.sharedApplication().currentEvent
{
let flags = currentEvent.modifierFlags
let key = currentEvent.charactersIgnoringModifiers
// println("Command selector: >\(commandSelector)<")
println("KEY: \(key)")
// NSEventModifierFlags
let commandDown = flags & .CommandKeyMask != nil
let optionDown = flags & .AlternateKeyMask != nil
let shiftDown = flags & .ShiftKeyMask != nil
let controlDown = flags & .ControlKeyMask != nil
println("Modifier keys: Command: \(commandDown), Option: \(optionDown), Control: \(controlDown), Shift: \(shiftDown)")
//
// Make return, by itself (without modifiers), leave an empty space.
// (We will use Command + Enter as the shortcut to send.)
//
if commandSelector == "insertNewline:" && !commandDown && !optionDown && !controlDown && !shiftDown
{
textView.insertNewlineIgnoringFieldEditor(self)
return true
}
else if key == "\r" && commandDown
{
postMessage()
}
}
return false
}
func textView(view: NSTextView, draggedCell cell: NSTextAttachmentCellProtocol, inRect rect: NSRect, event: NSEvent, atIndex charIndex: Int) {
println("Dragged cell: \(cell)")
}
// func textView(textView: NSTextView, completions words: [AnyObject], forPartialWordRange charRange: NSRange, indexOfSelectedItem index: UnsafeMutablePointer<Int>) -> [AnyObject] {
//
// println("Completions: \(words)")
//
// return ["These", "Are", "Completions"]
// }
func textView(textView: NSTextView, shouldChangeTextInRange affectedCharRange: NSRange, replacementString: String) -> Bool {
println("Message text view should change text in range: \(affectedCharRange) to: >\(replacementString)<")
return true
}
// MARK: - NSTextStorageDelegate methods
func textStorageWillProcessEditing(notification: NSNotification) {
println("TEXT STORAGE:\n \(notification.name)")
let textStorage = notification.object as? NSTextStorage
println("Edited range: \(textStorage?.editedRange)")
println("Edited mask: \(textStorage?.editedMask)")
let attributedString:NSAttributedString = textStorage!.attributedSubstringFromRange(textStorage!.editedRange)
println("Length: \(attributedString.length)")
attributedString.enumerateAttribute("NSAttachment", inRange: NSMakeRange(0, attributedString.length), options: NSAttributedStringEnumerationOptions.allZeros) { (x:AnyObject!, y:NSRange, z:UnsafeMutablePointer<ObjCBool>) -> Void in
// println(">>> \(x)")
// let image = x as NSTextAttachment
// println("\(image.debugDescription)")
}
}
// MARK: - Actions
@IBAction func sendButtonWasPressed(sender: AnyObject) {
postMessage()
}
}
//
// MessageSplitViewController.swift
// MessageTextEntrySpike
//
// Created by Aral Balkan on 03/01/2015.
// Copyright (c) 2015 ind.ie. All rights reserved.
//
import Cocoa
class MessageSplitViewController: NSSplitViewController {
override func viewDidLoad()
{
super.viewDidLoad()
NSNotificationCenter.defaultCenter().when(youGet: MessageNotification.named(.SendMessage), call: "sendMessage:", on: self)
}
func sendMessage(notification:NSNotification)
{
let timelineViewController = (self.splitViewItems[0] as! NSSplitViewItem).viewController as! TimelineViewController
if let userInfo = (notification.userInfo as Dictionary!)
{
let message = userInfo["message"] as! NSAttributedString
println("Send message: received text: \(message)")
//
// Create a file wrapper to save the contents as a directory.
//
let messageDirectoryWrapper:NSFileWrapper? = message.fileWrapperFromRange(NSMakeRange(0, message.length), documentAttributes: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: NSUTF8StringEncoding, NSExcludedElementsDocumentAttribute: ["xml", "html", "head", "body", "font", "span"], NSPrefixSpacesDocumentAttribute: 4], error: nil)
let fileURL:NSURL? = NSURL(fileURLWithPath: NSHomeDirectory().stringByAppendingPathComponent("message"), isDirectory: true)
if let messageDirectoryWrapper = messageDirectoryWrapper, fileURL = fileURL
{
let messageFileWrappers:[NSObject:AnyObject] = messageDirectoryWrapper.fileWrappers
for (fileName, fileWrapper) in messageFileWrappers
{
println(" * \(fileName): \(fileWrapper)")
if fileName == "index.html"
{
let indexData:NSData? = fileWrapper.regularFileContents
if let indexData = indexData
{
let indexHTML = NSString(data: indexData, encoding: NSUTF8StringEncoding)
if var indexHTML = indexHTML
{
//
// Massage the HTML.
//
// Fix image URLs
indexHTML = indexHTML.stringByReplacingOccurrencesOfString("file:///", withString: "")
// Remove redundant empty lines.
indexHTML = indexHTML.stringByReplacingOccurrencesOfString("<p><br></p>", withString: "")
//
// Update the index.html.
//
// Remove the index.html file wrapper.
messageDirectoryWrapper.removeFileWrapper(fileWrapper as! NSFileWrapper)
// And replace it with the new index.html we created.
let newIndexHTMLData:NSData? = indexHTML.dataUsingEncoding(NSUTF8StringEncoding)
if let newIndexHTMLData = newIndexHTMLData
{
let newIndexHTMLFileWrapper = NSFileWrapper(regularFileWithContents: newIndexHTMLData)
newIndexHTMLFileWrapper.preferredFilename = "index.html"
messageDirectoryWrapper.addFileWrapper(newIndexHTMLFileWrapper)
break
}
}
}
}
}
// TODO: Error checking.
messageDirectoryWrapper.writeToURL(fileURL, options: NSFileWrapperWritingOptions.Atomic, originalContentsURL: nil, error: nil)
println(messageDirectoryWrapper)
}
timelineViewController.showMessageAtURL(fileURL!)
}
}
}
//
// TimelineViewController.swift
// MessageTextEntrySpike
//
// Created by Aral Balkan on 03/01/2015.
// Copyright (c) 2015 ind.ie. All rights reserved.
//
import Cocoa
import WebKit
class TimelineViewController: NSViewController {
@IBOutlet weak var topLeftCornerIndicatorImageView: NSImageView!
@IBOutlet weak var topRightCornerIndicatorImageView: NSImageView!
@IBOutlet weak var bottomRightCornerIndicatorImageView: NSImageView!
@IBOutlet weak var bottomLeftCornerIndicatorImageView: NSImageView!
var webkit: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
//
// Set up the web kit view.
//
webkit = WKWebView(frame: CGRectMake(0, 0, self.view.frame.width, self.view.frame.height))
view.addSubview(self.webkit)
var url = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("index", ofType: "html")!)
var urlRequest = NSURLRequest(URL: url!)
webkit.loadRequest(urlRequest)
//
// Set up the auto layout constraints
//
webkit.translatesAutoresizingMaskIntoConstraints = false
let viewsDictionary:[NSObject : AnyObject] = ["webkit": webkit, "topLeftCornerIndicatorImageView": topLeftCornerIndicatorImageView, "topRightCornerIndicatorImageView": topRightCornerIndicatorImageView, "bottomRightCornerIndicatorImageView": bottomRightCornerIndicatorImageView, "bottomLeftCornerIndicatorImageView": bottomLeftCornerIndicatorImageView]
let horizontalConstraints:[AnyObject]! = NSLayoutConstraint.constraintsWithVisualFormat("H:[topLeftCornerIndicatorImageView]-(-13)-[webkit]-(-13)-[topRightCornerIndicatorImageView]", options: nil, metrics: nil, views: viewsDictionary)
let verticalConstraints:[AnyObject]! = NSLayoutConstraint.constraintsWithVisualFormat("V:[topLeftCornerIndicatorImageView]-(-13)-[webkit]-(-13)-[bottomLeftCornerIndicatorImageView]", options: nil, metrics: nil, views: viewsDictionary)
view.addConstraints(horizontalConstraints)
view.addConstraints(verticalConstraints)
}
func showMessageAtURL(messageFolderURL:NSURL)
{
println("Asked to show message at URL: \(messageFolderURL)")
let indexURL:NSURL = messageFolderURL.URLByAppendingPathComponent("index.html")
let urlRequest = NSURLRequest(URL: indexURL)
let indexFile = NSFileWrapper(URL: indexURL, options: NSFileWrapperReadingOptions.Immediate, error: nil)
let messageHTMLData = indexFile?.regularFileContents
var messageHTML = NSString(data: messageHTMLData!, encoding: NSUTF8StringEncoding)!
messageHTML = messageHTML.stringByReplacingOccurrencesOfString("\n", withString: "")
// Just for now — in the main app, this will be different
let baseURLStringForMessages = NSHomeDirectory().stringByAppendingPathExtension("message")
messageHTML = messageHTML.stringByReplacingOccurrencesOfString("src=\"", withString: "src=\"\(messageFolderURL)/")
messageHTML = messageHTML.stringByReplacingOccurrencesOfString("\"", withString: "\\\"")
println(messageHTML)
let javascript = "updateTimeline(\"\(messageHTML)\");"
println(javascript)
webkit.evaluateJavaScript(javascript)
{
(result, error) -> Void in
if error != nil
{
println("Error: \(error)")
}
else
{
println("Result: \(result)")
}
}
}
}
<!doctype html>
<html>
<head>
<meta charset='utf-8'>
<title>Timeline</title>
</head>
<body>
<h1>Timeline</h1>
<div id="messages">
</div>
<script>
// Update the timeline with the passed HTML.
function updateTimeline(messageHTML)
{
console.log("Updating the timeline…")
var messagesDiv = document.getElementById('messages');
var updatedHTML = messagesDiv.innerHTML + messageHTML
messagesDiv.innerHTML = updatedHTML
window.setTimeout(function(){
// Scroll the messages to the bottom so the user can see the latest message.
document.body.scrollTop = document.body.scrollHeight
}, 0);
}
</script>
</body>
</html>
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment