Commit 7af124f7 authored by Aral Balkan's avatar Aral Balkan

Refactored the timeline notifications so that actual timeline objects are...

Refactored the timeline notifications so that actual timeline objects are being passed. Conversations, when selected, now appear as a timeline (with dummy path, etc.)
parent 9d908403
......@@ -13,7 +13,7 @@ import Foundation
//
//
// Accessibility
// MARK: - Accessibility
//
func makeAccessibilityAnnouncement(message:String)
......@@ -25,6 +25,29 @@ func makeAccessibilityAnnouncement(message:String)
NSAccessibilityPostNotificationWithUserInfo(NSApp.mainWindow, NSAccessibilityAnnouncementRequestedNotification, [NSAccessibilityAnnouncementKey: message, NSAccessibilityPriorityKey: NSAccessibilityPriorityLevel.High.rawValue])
}
//
// MARK: - Date
//
var globalISO8601DateFormatter:ISO8601DateFormatter?
//
// Returns a current timestamp (UTC) in our standard ISO8601-based string format.
//
func ISO8601DateString() -> String
{
if globalISO8601DateFormatter == nil
{
// Set up the ISO 8601 date formatter.
globalISO8601DateFormatter = ISO8601DateFormatter()
globalISO8601DateFormatter!.format = .Calendar
globalISO8601DateFormatter!.includeTime = true
globalISO8601DateFormatter!.useMillisecondPrecision = true
globalISO8601DateFormatter!.timeSeparator = ("_" as NSString).characterAtIndex(0)
}
return globalISO8601DateFormatter!.stringFromDate(NSDate(), timeZone: NSTimeZone(abbreviation: "UTC"))
}
//
// Other
......@@ -35,8 +58,11 @@ func makeAccessibilityAnnouncement(message:String)
// Allows graceful overriding of any NSResponder subclass’s initialisation
// using a common initialiser.
//
extension NSResponder {
enum InitMethod {
// MARK: - Extension
extension NSResponder
{
enum InitMethod
{
case Default
case Coder(NSCoder)
}
......@@ -46,8 +72,9 @@ extension NSResponder {
// Coverted to Swift from Cameron Lowell Palmer’s Obj-C category
// (http://stackoverflow.com/a/23606211/253485)
//
extension String {
// MARK: - Extension
extension String
{
func stringByStrippingCharactersInSet(set:NSCharacterSet) -> String
{
return (self.componentsSeparatedByCharactersInSet(set) as NSArray).componentsJoinedByString("")
......@@ -67,34 +94,46 @@ extension String {
// A usable String interface for common actions, courtesy of
// http://stackoverflow.com/a/25152652/253485
//
extension String {
// MARK: - Extension
extension String
{
// MARK: - substring
func substringToIndex(index:Int) -> String {
func substringToIndex(index:Int) -> String
{
return self.substringToIndex(advance(self.startIndex, index))
}
func substringFromIndex(index:Int) -> String {
func substringFromIndex(index:Int) -> String
{
return self.substringFromIndex(advance(self.startIndex, index))
}
func substringWithRange(range:Range<Int>) -> String {
func substringWithRange(range:Range<Int>) -> String
{
let start = advance(self.startIndex, range.startIndex)
let end = advance(self.startIndex, range.endIndex)
return self.substringWithRange(start..<end)
}
subscript(index:Int) -> Character{
subscript(index:Int) -> Character
{
return self[advance(self.startIndex, index)]
}
subscript(range:Range<Int>) -> String {
subscript(range:Range<Int>) -> String
{
let start = advance(self.startIndex, range.startIndex)
let end = advance(self.startIndex, range.endIndex)
return self[start..<end]
}
// MARK: - replace
func replaceCharactersInRange(range:Range<Int>, withString: String!) -> String {
func replaceCharactersInRange(range:Range<Int>, withString: String!) -> String
{
var result:NSMutableString = NSMutableString(string: self)
result.replaceCharactersInRange(NSRange(range), withString: withString)
return result as String
......@@ -104,22 +143,29 @@ extension String {
//
// Thanks to http://sketchytech.blogspot.co.uk/2014/08/pure-swift-stringbyreplacingoccurrences.html
//
extension String {
func rangesOfString(findStr:String) -> [Range<String.Index>] {
// MARK: - Extension
extension String
{
func rangesOfString(findStr:String) -> [Range<String.Index>]
{
var arr = [Range<String.Index>]()
var startInd = self.startIndex
var i = 0
// test first of all whether the string is likely to appear at all
if contains(self, first(findStr)!) {
if contains(self, first(findStr)!)
{
startInd = find(self,first(findStr)!)!
}
else {
else
{
return arr
}
// set starting point for search based on the finding of the first character
i = distance(self.startIndex, startInd)
while i<=count(self)-count(findStr) {
if self[advance(self.startIndex, i)..<advance(self.startIndex, i+count(findStr))] == findStr {
while i<=count(self)-count(findStr)
{
if self[advance(self.startIndex, i)..<advance(self.startIndex, i+count(findStr))] == findStr
{
arr.append(Range(start:advance(self.startIndex, i),end:advance(self.startIndex, i+count(findStr))))
i = i+count(findStr)
}
......@@ -128,30 +174,34 @@ extension String {
return arr
} // try further optimization by repeating the initial act of finding first character after each found string
func stringByReplacingOccurrencesOfString(string:String, replacement:String) -> String {
func stringByReplacingOccurrencesOfString(string:String, replacement:String) -> String
{
// get ranges first using rangesOfString: method, then glue together the string using ranges of existing string and old string
let ranges = self.rangesOfString(string)
// if the string isn't found return unchanged string
if ranges.isEmpty {
if ranges.isEmpty
{
return self
}
var newString = ""
var startInd = self.startIndex
for r in ranges {
for r in ranges
{
newString += self[startInd..<minElement(r)]
newString += replacement
if maxElement(r) < self.endIndex {
if maxElement(r) < self.endIndex
{
startInd = advance(maxElement(r),1)
}
}
// add the last part of the string after the final find
if maxElement(ranges.last!) < self.endIndex {
if maxElement(ranges.last!) < self.endIndex
{
newString += self[advance(maxElement(ranges.last!),1)..<self.endIndex]
}
......
......@@ -420,12 +420,12 @@ class MainInterfaceSplitViewController: NSSplitViewController
{
/* with */ notification in
if let userInfo = notification.userInfo, timeline = userInfo["timeline"] as? String
if let userInfo = notification.userInfo, timeline = userInfo["timeline"] as? Timeline
{
// For the pre-alpha, the Private timeline will be a single
// timeline. In the future, it will feature separate
// “document” timelines.
if (timeline == TheTimeline.Conversations.rawValue /*|| timeline == TheTimeline.Private.rawValue*/)
if (timeline.id == TheTimeline.Conversations /*|| timeline == TheTimeline.Private*/)
{
// TODO: Make it appear, not toggle.
self.togglePanel(1, show: true)
......
......@@ -13,29 +13,12 @@ import Cocoa
class MessageSplitViewController: NSSplitViewController
{
var iso8601DateFormatter:ISO8601DateFormatter!
var sendMessageNotificationHandler:NotificationHandler?
//
// MARK: - View lifecycle
//
override func viewDidLoad()
{
super.viewDidLoad()
// Set up the ISO 8601 date formatter.
iso8601DateFormatter = ISO8601DateFormatter()
iso8601DateFormatter.format = .Calendar
iso8601DateFormatter.includeTime = true
iso8601DateFormatter.useMillisecondPrecision = true
iso8601DateFormatter.timeSeparator = ("_" as NSString).characterAtIndex(0)
}
override func viewWillAppear()
{
createConstraints()
......@@ -90,7 +73,7 @@ class MessageSplitViewController: NSSplitViewController
let messageDirectoryWrapper:NSFileWrapper? = messageAttributedString.fileWrapperFromRange(NSMakeRange(0, messageAttributedString.length), documentAttributes: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: NSUTF8StringEncoding, NSExcludedElementsDocumentAttribute: ["xml", "html", "head", "body", "font", "span"], NSPrefixSpacesDocumentAttribute: 4], error: nil)
// The file name of the message folder (this is the unique ID — unique per message folder) is the current timestamp in ISO 8601 format
let messageFileName = self.iso8601DateFormatter.stringFromDate(NSDate(), timeZone: NSTimeZone(abbreviation: "UTC"))
let messageFileName = ISO8601DateString()
// This is the URL that will be prefixed to all assets in the HTML (images, etc.)
let assetURLPrefix = CurrentTimeline.relativeWebPrefix!.stringByAppendingPathComponent(messageFileName)
......
......@@ -24,7 +24,7 @@ class SidebarViewController:NSViewController
@IBOutlet weak var tableViewScrollView: NSScrollView!
var data: [[String: String]]?
var data: [Timeline]?
// MARK: - View lifecycle
......@@ -39,13 +39,8 @@ class SidebarViewController:NSViewController
// Dummy data for now.
data = [
[
"name": "Laura Kalbag",
"description": "Private conversation" ],
[
"name": "Jo Porter",
"description": "Private conversation"
]
Timeline(id: .Conversations, title: "Laura Kalbag", path: "DUMMY", relativeWebPrefix: "DUMMY"),
Timeline(id: .Conversations, title: "Jo Porter", path: "DUMMY", relativeWebPrefix: "DUMMY")
]
let nib = NSNib(nibNamed: "ConversationCellView", bundle: NSBundle.mainBundle())
......@@ -106,10 +101,10 @@ extension SidebarViewController:NSTableViewDataSource, NSTableViewDelegate
if let cell = cell
{
let item: [String: String] = (data!)[row]
let timeline: Timeline = (data!)[row]
cell.conversationName.stringValue = item["name"]!
cell.conversationDescription.stringValue = item["description"]!
cell.conversationName.stringValue = timeline.title
cell.conversationDescription.stringValue = "Private conversation"
cell.conversationImage.image = SetupData.photo // TODO: Hardcoded — use actual friend image.
// Mask the conversation image with a circular mask.
......@@ -124,4 +119,13 @@ extension SidebarViewController:NSTableViewDataSource, NSTableViewDelegate
return cell
}
func tableView(tableView: NSTableView, shouldSelectRow row: Int) -> Bool
{
let selectedTimeline = (data!)[row]
let userInfo = ["timeline": selectedTimeline]
post(TimelineNavigationRequestNotification, from: self, with: userInfo)
return true
}
}
......@@ -59,18 +59,8 @@ class TimelineViewController: NSViewController
var webkit: WKWebView!
var timelines:[Timeline]
var currentTimeline:TheTimeline = TheTimeline.Everyone
let timelineTitles = ["Everyone on Ind.ie", "Public posts", "All friends", "Conversations", "Private posts"]
let timelineMessageWritingFolders = [
TheTimeline.Everyone: "5. Everyone (public)/to",
TheTimeline.Public: "5. Everyone (public)/to",
TheTimeline.AllFriends: "3. All your friends/to",
TheTimeline.Private: "1. Just yourself (private)"
]
let timelineKeys:[String] = [TheTimeline.Everyone.rawValue, TheTimeline.Public.rawValue, TheTimeline.AllFriends.rawValue, TheTimeline.Conversations.rawValue, TheTimeline.Private.rawValue]
// TODO: Do not hardcode this — it should default to last selected timeline.
var currentTimeline:Timeline = Timeline(id: .Everyone, title: NSLocalizedString("Everyone", comment:"Timeline name"), path: "5. Everyone (public)/to", relativeWebPrefix: nil)
//
// Notification handlers
......@@ -83,35 +73,6 @@ class TimelineViewController: NSViewController
// MARK: - Setup
//
required init?(coder: NSCoder)
{
// TODO: Unused and the model needs to be improved so that it is generic and can work with
// ===== any number of timelines.
// Create the timelines
timelines = [Timeline]()
// Everyone
timelines.append(Timeline(id: .Everyone, title: NSLocalizedString("Everyone", comment:"Timeline name"), path: "5. Everyone (public)/to", relativeWebPrefix: nil))
// Public
timelines.append(Timeline(id: .Public, title: NSLocalizedString("Public", comment:"Timeline name"), path: "5. Everyone (public)/to", relativeWebPrefix: "/public"))
// All Friends
timelines.append(Timeline(id: .AllFriends, title: NSLocalizedString("All Friends", comment:"Timeline name"), path: "3. All your friends/to", relativeWebPrefix: "/all-friends/to"))
// Conversations
// NOTE: Placeholder — need better model as conversations are multiple timelines
timelines.append(Timeline(id: .Conversations, title: NSLocalizedString("Conversations", comment:"Timeline name"), path: nil, relativeWebPrefix: nil))
// Private
// NOTE: Placeholder — Private, like conversations, should support multiple timelines. Need better model to support this.
timelines.append(Timeline(id: .Private, title: NSLocalizedString("Private", comment:"Timeline name"), path: "1. Just yourself (private)", relativeWebPrefix: "/private"))
super.init(coder:coder)
}
//
// MARK: - View lifecycle
//
......@@ -215,7 +176,7 @@ class TimelineViewController: NSViewController
println("Received TimelineNavigationRequestNotification: \(notification)…")
if let userInfo = notification.userInfo, timeline = userInfo["timeline"] as? String
if let userInfo = notification.userInfo, timeline = userInfo["timeline"] as? Timeline
{
//
// A request has been received to navigate to a different timeline. Carry out the navigation.
......@@ -311,7 +272,7 @@ class TimelineViewController: NSViewController
{
println("WebKit Load complete. About to update the title…")
let javascript = "setTitle('\(self.currentTimeline.rawValue)')"
let javascript = "setTitle('\(self.currentTimeline.title)')"
println(">> \(javascript)")
......@@ -334,45 +295,6 @@ class TimelineViewController: NSViewController
}
func currentTimelinePath() -> String?
{
if currentTimeline == TheTimeline.Conversations
{
println("Current timeline path called on conversations tab. Unimplemented. Returning nil.")
return nil
}
let currentTimelinePath = timelineMessageWritingFolders[currentTimeline]
println("currentTimelinePath: \(currentTimelinePath)")
return currentTimelinePath
}
//
// Returns the web prefix for the private / public servers to use when loading static assets for a given message.
// This needs to be prefixed to the message folder name and that relative URL must be used as the prefix of any
// assets in the HTML.
//
// TODO: When all timelines have been implemented, this should *not* return an optional.
//
func currentTimelineRelativeWebPrefix() -> String?
{
switch currentTimeline
{
case .Private:
return "/private"
case .Public:
return "/public"
case .AllFriends:
return "/all-friends/to"
default:
println("Warning: URLs not implemented yet for current timeline (\(currentTimeline.rawValue))")
return nil
}
}
func loadIndexPage()
{
......@@ -381,26 +303,17 @@ class TimelineViewController: NSViewController
}
func showTimeline(timelineName:String)
func showTimeline(timeline:Timeline)
{
let _timeline:TheTimeline? = TheTimeline(rawValue: timelineName)
if _timeline == nil
{
fatalError("showTimeline called with invalid timeline string: \(timelineName)")
}
let timeline = _timeline!
println("Showing timeline: \(timeline.rawValue)…")
println("Showing timeline: \(timeline.title)…")
// Save the value in the view state
self.currentTimeline = timeline
// Update the CurrentTimeline Model
CurrentTimeline.name = timelineName
CurrentTimeline.path = currentTimelinePath()
CurrentTimeline.relativeWebPrefix = currentTimelineRelativeWebPrefix()
CurrentTimeline.name = timeline.title
CurrentTimeline.path = timeline.path
CurrentTimeline.relativeWebPrefix = timeline.relativeWebPrefix
// Notify anyone who cares that the timeline has changed.
post(TimelineNotification.named(.TimelineDidChange), from: self)
......@@ -408,7 +321,7 @@ class TimelineViewController: NSViewController
//
// Special case: everyone timeline is loaded from Waystone.
//
if timeline == TheTimeline.Everyone
if timeline.id == TheTimeline.Everyone
{
let indieURL = NSURL(string: "http://192.168.59.103:3000") // NOTE: HARDCODED DEV URL
webkit.loadRequest(NSURLRequest(URL: indieURL!))
......
......@@ -28,16 +28,49 @@ class VerticalTabBarViewController: NSViewController
@IBOutlet weak var activeTabIndicatorImageView: VerticalTabBarActiveTabIndicatorImageView!
@IBOutlet weak var activeTabIndicatorBottomVerticalSpaceConstraint: NSLayoutConstraint!
var timelines:[TheTimeline: Timeline]
// MARK: View lifecycle
required init?(coder: NSCoder)
{
//
// Create main timelines
//
timelines = [TheTimeline: Timeline]()
// Everyone
timelines[.Everyone] = Timeline(id: .Everyone, title: NSLocalizedString("Everyone", comment:"Timeline name"), path: "5. Everyone (public)/to", relativeWebPrefix: nil)
// Public
timelines[.Public] = Timeline(id: .Public, title: NSLocalizedString("Public", comment:"Timeline name"), path: "5. Everyone (public)/to", relativeWebPrefix: "/public")
// All Friends
timelines[.AllFriends] = Timeline(id: .AllFriends, title: NSLocalizedString("All Friends", comment:"Timeline name"), path: "3. All your friends/to", relativeWebPrefix: "/all-friends/to")
// Conversations
// NOTE: Placeholder — need better model as conversations are multiple timelines
timelines[.Conversations] = Timeline(id: .Conversations, title: NSLocalizedString("Conversations", comment:"Timeline name"), path: nil, relativeWebPrefix: nil)
// Private
// NOTE: Placeholder — Private, like conversations, should support multiple timelines. Need better model to support this.
timelines[.Private] = Timeline(id: .Private, title: NSLocalizedString("Private", comment:"Timeline name"), path: "1. Just yourself (private)", relativeWebPrefix: "/private")
super.init(coder: coder)
}
override func viewDidLoad()
{
super.viewDidLoad()
self.buttons = [everyoneAtIndieButton, publicButton, allFriendsButton, conversationsButton, privateButton]
self.sections = [.Everyone, .Public, .AllFriends, .Conversations, .Private]
buttons = [everyoneAtIndieButton, publicButton, allFriendsButton, conversationsButton, privateButton]
sections = [.Everyone, .Public, .AllFriends, .Conversations, .Private]
// Keyboard shortcuts
//
// Setup keyboard shortcuts
//
let command1Shortcut = MASShortcut(keyCode: UInt(kVK_ANSI_1), modifierFlags: NSEventModifierFlags.CommandKeyMask.rawValue)
let command2Shortcut = MASShortcut(keyCode: UInt(kVK_ANSI_2), modifierFlags: NSEventModifierFlags.CommandKeyMask.rawValue)
......@@ -45,21 +78,11 @@ class VerticalTabBarViewController: NSViewController
let command4Shortcut = MASShortcut(keyCode: UInt(kVK_ANSI_4), modifierFlags: NSEventModifierFlags.CommandKeyMask.rawValue)
let command5Shortcut = MASShortcut(keyCode: UInt(kVK_ANSI_5), modifierFlags: NSEventModifierFlags.CommandKeyMask.rawValue)
MASShortcutMonitor.sharedMonitor().registerShortcut(command1Shortcut, withAction: { () -> Void in
self.simulateButtonPress(self.everyoneAtIndieButton)
})
MASShortcutMonitor.sharedMonitor().registerShortcut(command2Shortcut, withAction: { () -> Void in
self.simulateButtonPress(self.publicButton)
})
MASShortcutMonitor.sharedMonitor().registerShortcut(command3Shortcut, withAction: { () -> Void in
self.simulateButtonPress(self.allFriendsButton)
})
MASShortcutMonitor.sharedMonitor().registerShortcut(command4Shortcut, withAction: { () -> Void in
self.simulateButtonPress(self.conversationsButton)
})
MASShortcutMonitor.sharedMonitor().registerShortcut(command5Shortcut, withAction: { () -> Void in
self.simulateButtonPress(self.privateButton)
})
MASShortcutMonitor.sharedMonitor().registerShortcut(command1Shortcut, withAction: { self.simulateButtonPress(self.everyoneAtIndieButton) })
MASShortcutMonitor.sharedMonitor().registerShortcut(command2Shortcut, withAction: { self.simulateButtonPress(self.publicButton) })
MASShortcutMonitor.sharedMonitor().registerShortcut(command3Shortcut, withAction: { self.simulateButtonPress(self.allFriendsButton) })
MASShortcutMonitor.sharedMonitor().registerShortcut(command4Shortcut, withAction: { self.simulateButtonPress(self.conversationsButton) })
MASShortcutMonitor.sharedMonitor().registerShortcut(command5Shortcut, withAction: { self.simulateButtonPress(self.privateButton) })
}
......@@ -86,8 +109,10 @@ class VerticalTabBarViewController: NSViewController
}
else
{
let selectedTimeline = self.sections[i]
// This is the section we want, ask app to navigate there.
let userInfo = ["timeline": self.sections[i].rawValue]
let userInfo = ["timeline": timelines[selectedTimeline]!]
post(TimelineNavigationRequestNotification, from: self, with: userInfo)
}
i++
......
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