├── .gitignore ├── .gitmodules ├── LICENSE ├── Podfile ├── Podfile.lock ├── README.md ├── Resources ├── Default-568h@2x.png ├── Launch Images │ ├── Default-568h@2x.png │ ├── Default-Landscape.png │ ├── Default-Landscape@2x.png │ ├── Default-Portrait.png │ ├── Default-Portrait@2x.png │ ├── Default-iOS7-Landscape.png │ ├── Default-iOS7-Landscape@2x.png │ ├── Default-iOS7-Portrait.png │ ├── Default-iOS7-Portrait@2x.png │ ├── Default.png │ └── Default@2x.png ├── blue-icon │ ├── README │ ├── zulip-1024-blue.png │ ├── zulip-blue-29.png │ ├── zulip-blue-29@2x.png │ ├── zulip-blue-40.png │ ├── zulip-blue-40@2x.png │ ├── zulip-blue-50.png │ ├── zulip-blue-50@2x.png │ ├── zulip-blue-512.png │ ├── zulip-blue-512@2x.png │ ├── zulip-blue-57.png │ ├── zulip-blue-57@2x.png │ ├── zulip-blue-60.png │ ├── zulip-blue-60@2x.png │ ├── zulip-blue-60@3x.png │ ├── zulip-blue-72.png │ ├── zulip-blue-72@2x.png │ ├── zulip-blue-76.png │ └── zulip-blue-76@2x.png ├── bullhorn.png ├── bullhorn@2x.png ├── emoji_map.json ├── emoji_map_reverse.json ├── fonts │ ├── SourceSansPro-Black-webfont.ttf │ ├── SourceSansPro-BlackIt-webfont.ttf │ ├── SourceSansPro-Bold-webfont.ttf │ ├── SourceSansPro-BoldIt-webfont.ttf │ ├── SourceSansPro-ExtraLight-webfont.ttf │ ├── SourceSansPro-ExtraLightIt-webfont.ttf │ ├── SourceSansPro-It-webfont.ttf │ ├── SourceSansPro-Light-webfont.ttf │ ├── SourceSansPro-LightIt-webfont.ttf │ ├── SourceSansPro-Regular-webfont.ttf │ ├── SourceSansPro-Semibold-webfont.ttf │ └── SourceSansPro-SemiboldIt-webfont.ttf ├── my_avatar_placeholder.png ├── my_avatar_placeholder@2x.png ├── user-toolbar.png ├── user-toolbar@2x.png ├── zulip-1024.png ├── zulip-120.png ├── zulip-144.png ├── zulip-152.png ├── zulip-29.png ├── zulip-50.png ├── zulip-512.png ├── zulip-58.png ├── zulip-72.png ├── zulip-76.png ├── zulip-80.png ├── zulip.png └── zulip@2x.png ├── ThirdParty ├── AFNetworking │ ├── AFHTTPClient.h │ ├── AFHTTPClient.m │ ├── AFHTTPRequestOperation.h │ ├── AFHTTPRequestOperation.m │ ├── AFImageRequestOperation.h │ ├── AFImageRequestOperation.m │ ├── AFJSONRequestOperation.h │ ├── AFJSONRequestOperation.m │ ├── AFNetworkActivityIndicatorManager.h │ ├── AFNetworkActivityIndicatorManager.m │ ├── AFNetworking.h │ ├── AFPropertyListRequestOperation.h │ ├── AFPropertyListRequestOperation.m │ ├── AFURLConnectionOperation.h │ ├── AFURLConnectionOperation.m │ ├── AFXMLRequestOperation.h │ ├── AFXMLRequestOperation.m │ ├── UIImageView+AFNetworking.h │ └── UIImageView+AFNetworking.m └── JASidePanels │ ├── JASidePanelController.h │ ├── JASidePanelController.m │ ├── UIViewController+JASidePanel.h │ └── UIViewController+JASidePanel.m ├── Zulip.css ├── Zulip.xcodeproj └── project.pbxproj ├── Zulip ├── AutocompleteResults.h ├── AutocompleteResults.m ├── Categories │ ├── NSArray+Blocks.h │ └── NSArray+Blocks.m ├── ComposeViewController~ipad.xib ├── ComposeViewController~iphone.xib ├── Controllers │ ├── LongPoller.h │ ├── LongPoller.m │ ├── UnreadManager.h │ ├── UnreadManager.m │ ├── ViewControllers │ │ ├── AboutViewController.h │ │ ├── AboutViewController.m │ │ ├── AboutViewController.xib │ │ ├── HomeViewController.h │ │ ├── HomeViewController.m │ │ ├── LeftSidebarViewController.h │ │ ├── LeftSidebarViewController.m │ │ ├── LeftSidebarViewController.xib │ │ ├── NarrowViewController.h │ │ ├── NarrowViewController.m │ │ ├── RightSidebarViewController.h │ │ └── RightSidebarViewController.m │ ├── ZulipAPIController.h │ └── ZulipAPIController.m ├── ErrorViewController.h ├── ErrorViewController.m ├── ErrorViewController~ipad.xib ├── ErrorViewController~iphone.xib ├── GoogleOAuthManager.h ├── GoogleOAuthManager.m ├── Images.xcassets │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── zulip-blue-29-1.png │ │ ├── zulip-blue-29.png │ │ ├── zulip-blue-29@2x-1.png │ │ ├── zulip-blue-29@2x.png │ │ ├── zulip-blue-40.png │ │ ├── zulip-blue-40@2x-1.png │ │ ├── zulip-blue-40@2x.png │ │ ├── zulip-blue-50.png │ │ ├── zulip-blue-512.png │ │ ├── zulip-blue-512@2x.png │ │ ├── zulip-blue-57.png │ │ ├── zulip-blue-57@2x-1.png │ │ ├── zulip-blue-57@2x-2.png │ │ ├── zulip-blue-57@2x.png │ │ ├── zulip-blue-60@2x.png │ │ ├── zulip-blue-60@3x.png │ │ ├── zulip-blue-72.png │ │ ├── zulip-blue-76.png │ │ └── zulip-blue-76@2x.png ├── KeychainItemWrapper.h ├── KeychainItemWrapper.m ├── LoginViewController.h ├── LoginViewController.m ├── LoginViewController~ipad.xib ├── LoginViewController~iphone.xib ├── MessageCell.h ├── MessageCell.m ├── MessageCellView.xib ├── Models │ ├── RangePair.h │ ├── RangePair.m │ ├── RawMessage.h │ ├── RawMessage.m │ ├── ZMessage.h │ ├── ZMessage.m │ ├── ZSubscription.h │ ├── ZSubscription.m │ ├── ZUser.h │ ├── ZUser.m │ ├── ZUserPresence.h │ ├── ZUserPresence.m │ └── Zulip.xcdatamodeld │ │ ├── .xccurrentversion │ │ ├── Zulip 1.0.xcdatamodel │ │ └── contents │ │ ├── Zulip.xcdatamodel │ │ └── contents │ │ └── Zulip2.xcdatamodel │ │ └── contents ├── NSString+Encode.h ├── NSString+Encode.m ├── NarrowOperators.h ├── NarrowOperators.m ├── PreferencesWrapper.h ├── PreferencesWrapper.m ├── PresenceManager.h ├── PresenceManager.m ├── Protocols │ └── MessageComposing.h ├── RenderedMarkdownMunger.h ├── RenderedMarkdownMunger.m ├── SidebarUserCell.h ├── SidebarUserCell.m ├── StreamComposeView.h ├── StreamComposeView.m ├── StreamViewController.h ├── StreamViewController.m ├── StreamViewController.xib ├── UIColor+HexColor.h ├── UIColor+HexColor.m ├── UIView+Layout.h ├── UIView+Layout.m ├── Views │ ├── AboutView~ipad.xib │ ├── AboutView~iphone.xib │ ├── ComposeAutocompleteView.h │ ├── ComposeAutocompleteView.m │ ├── SidebarSectionHeader.h │ ├── SidebarSectionHeader.m │ ├── SidebarSectionHeader.xib │ ├── SidebarStreamCell.h │ ├── SidebarStreamCell.m │ ├── SidebarStreamCell.xib │ ├── SidebarUserCell.xib │ ├── StreamProgressView.h │ ├── StreamProgressView.m │ ├── UserCell.h │ ├── UserCell.m │ └── UserCellView.xib ├── Zulip-Info.plist ├── Zulip-Prefix.pch ├── ZulipAPIClient.h ├── ZulipAPIClient.m ├── ZulipAppDelegate.h ├── ZulipAppDelegate.m ├── en.lproj │ ├── FirstViewController.xib │ ├── InfoPlist.strings │ └── MainWindow~iphone.xib ├── iPad │ └── en.lproj │ │ └── MainWindow~ipad.xib └── main.m ├── ZulipTests ├── ZulipTests-Info.plist ├── ZulipTests.h ├── ZulipTests.m └── en.lproj │ └── InfoPlist.strings ├── delete-trailing-whitespace.sh ├── docs └── event_queues.md ├── images ├── launch-1024x768.png ├── launch-1536x2048.png ├── launch-2048x1536.png ├── launch-320x480.png ├── launch-640x1136.png ├── launch-640x960.png └── launch-768x1024.png └── tools └── emoji_dict_generator.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Cocoapods 2 | Pods/* 3 | 4 | # OS X Finder 5 | .DS_Store 6 | 7 | # Xcode per-user config 8 | *.mode1 9 | *.mode1v3 10 | *.mode2v3 11 | *.perspective 12 | *.perspectivev3 13 | *.pbxuser 14 | *.xcworkspace 15 | xcuserdata 16 | DerivedData/ 17 | 18 | # Build products 19 | build/ 20 | *.o 21 | *.LinkFileList 22 | *.hmap 23 | 24 | # Automatic backup files 25 | *~.nib/ 26 | *.swp 27 | *~ 28 | *.dat 29 | *.dep 30 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ThirdParty/MBProgressHUD"] 2 | path = ThirdParty/MBProgressHUD 3 | url = https://github.com/lfranchi/MBProgressHUD.git 4 | [submodule "ThirdParty/WebViewController"] 5 | path = ThirdParty/WebViewController 6 | url = https://github.com/Zulip/Browser-View-Controller--iPhone-.git 7 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, "6.0" 2 | 3 | pod 'FontAwesomeKit/FontAwesome' 4 | pod 'HockeySDK' 5 | pod 'DTCoreText' 6 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - DTCoreText (1.6.16): 3 | - DTFoundation/Core (~> 1.7.5) 4 | - DTFoundation/DTAnimatedGIF (~> 1.7.5) 5 | - DTFoundation/DTHTMLParser (~> 1.7.5) 6 | - DTFoundation/UIKit (~> 1.7.5) 7 | - DTFoundation/Core (1.7.8) 8 | - DTFoundation/DTAnimatedGIF (1.7.8) 9 | - DTFoundation/DTHTMLParser (1.7.8): 10 | - DTFoundation/Core 11 | - DTFoundation/UIKit (1.7.8): 12 | - DTFoundation/Core 13 | - FontAwesomeKit/Core (2.1.4) 14 | - FontAwesomeKit/FontAwesome (2.1.4): 15 | - FontAwesomeKit/Core 16 | - HockeySDK (3.6.2) 17 | 18 | DEPENDENCIES: 19 | - DTCoreText 20 | - FontAwesomeKit/FontAwesome 21 | - HockeySDK 22 | 23 | SPEC CHECKSUMS: 24 | DTCoreText: 934a16fe9ffdd169c96261721b39dc312b75713d 25 | DTFoundation: 46e2b2fafe78d67febfd8d1f85fe9845a093f00e 26 | FontAwesomeKit: b02e4e9bf2d12b757b924c2ad2cc3782be530636 27 | HockeySDK: 51ade1aff04463137e852797d2eb4dfb2511d6a9 28 | 29 | COCOAPODS: 0.38.2 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is the legacy Zulip iOS app; it is no longer in the app store and 2 | is no longer developed. You probably are looking for the 3 | [Zulip React Native app](https://github.com/zulip/zulip-mobile), which 4 | is actively developed. 5 | 6 | This project remains primarily for historical interest. 7 | 8 | ---------- 9 | 10 | The following steps are needed to build and run this project: 11 | 12 | ``` 13 | git submodule init 14 | git submodule --recursive update 15 | pod install 16 | 17 | open Zulip.xcworkspace 18 | ``` 19 | -------------------------------------------------------------------------------- /Resources/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/Default-568h@2x.png -------------------------------------------------------------------------------- /Resources/Launch Images/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/Launch Images/Default-568h@2x.png -------------------------------------------------------------------------------- /Resources/Launch Images/Default-Landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/Launch Images/Default-Landscape.png -------------------------------------------------------------------------------- /Resources/Launch Images/Default-Landscape@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/Launch Images/Default-Landscape@2x.png -------------------------------------------------------------------------------- /Resources/Launch Images/Default-Portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/Launch Images/Default-Portrait.png -------------------------------------------------------------------------------- /Resources/Launch Images/Default-Portrait@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/Launch Images/Default-Portrait@2x.png -------------------------------------------------------------------------------- /Resources/Launch Images/Default-iOS7-Landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/Launch Images/Default-iOS7-Landscape.png -------------------------------------------------------------------------------- /Resources/Launch Images/Default-iOS7-Landscape@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/Launch Images/Default-iOS7-Landscape@2x.png -------------------------------------------------------------------------------- /Resources/Launch Images/Default-iOS7-Portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/Launch Images/Default-iOS7-Portrait.png -------------------------------------------------------------------------------- /Resources/Launch Images/Default-iOS7-Portrait@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/Launch Images/Default-iOS7-Portrait@2x.png -------------------------------------------------------------------------------- /Resources/Launch Images/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/Launch Images/Default.png -------------------------------------------------------------------------------- /Resources/Launch Images/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/Launch Images/Default@2x.png -------------------------------------------------------------------------------- /Resources/blue-icon/README: -------------------------------------------------------------------------------- 1 | to generate these again, from zsh do: 2 | 3 | 4 | for i in 29 40 57 60 50 72 76 512; do ((j=i*2));convert zulip-1024-blue.png -resize $jx$j zulip-blue-$i@2x.png; done 5 | for i in 29 40 57 60 50 72 76 512; do convert zulip-1024-blue.png -resize $jx$j zulip-blue-$i.png; done 6 | -------------------------------------------------------------------------------- /Resources/blue-icon/zulip-1024-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/blue-icon/zulip-1024-blue.png -------------------------------------------------------------------------------- /Resources/blue-icon/zulip-blue-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/blue-icon/zulip-blue-29.png -------------------------------------------------------------------------------- /Resources/blue-icon/zulip-blue-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/blue-icon/zulip-blue-29@2x.png -------------------------------------------------------------------------------- /Resources/blue-icon/zulip-blue-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/blue-icon/zulip-blue-40.png -------------------------------------------------------------------------------- /Resources/blue-icon/zulip-blue-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/blue-icon/zulip-blue-40@2x.png -------------------------------------------------------------------------------- /Resources/blue-icon/zulip-blue-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/blue-icon/zulip-blue-50.png -------------------------------------------------------------------------------- /Resources/blue-icon/zulip-blue-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/blue-icon/zulip-blue-50@2x.png -------------------------------------------------------------------------------- /Resources/blue-icon/zulip-blue-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/blue-icon/zulip-blue-512.png -------------------------------------------------------------------------------- /Resources/blue-icon/zulip-blue-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/blue-icon/zulip-blue-512@2x.png -------------------------------------------------------------------------------- /Resources/blue-icon/zulip-blue-57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/blue-icon/zulip-blue-57.png -------------------------------------------------------------------------------- /Resources/blue-icon/zulip-blue-57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/blue-icon/zulip-blue-57@2x.png -------------------------------------------------------------------------------- /Resources/blue-icon/zulip-blue-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/blue-icon/zulip-blue-60.png -------------------------------------------------------------------------------- /Resources/blue-icon/zulip-blue-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/blue-icon/zulip-blue-60@2x.png -------------------------------------------------------------------------------- /Resources/blue-icon/zulip-blue-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/blue-icon/zulip-blue-60@3x.png -------------------------------------------------------------------------------- /Resources/blue-icon/zulip-blue-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/blue-icon/zulip-blue-72.png -------------------------------------------------------------------------------- /Resources/blue-icon/zulip-blue-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/blue-icon/zulip-blue-72@2x.png -------------------------------------------------------------------------------- /Resources/blue-icon/zulip-blue-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/blue-icon/zulip-blue-76.png -------------------------------------------------------------------------------- /Resources/blue-icon/zulip-blue-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/blue-icon/zulip-blue-76@2x.png -------------------------------------------------------------------------------- /Resources/bullhorn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/bullhorn.png -------------------------------------------------------------------------------- /Resources/bullhorn@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/bullhorn@2x.png -------------------------------------------------------------------------------- /Resources/fonts/SourceSansPro-Black-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/fonts/SourceSansPro-Black-webfont.ttf -------------------------------------------------------------------------------- /Resources/fonts/SourceSansPro-BlackIt-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/fonts/SourceSansPro-BlackIt-webfont.ttf -------------------------------------------------------------------------------- /Resources/fonts/SourceSansPro-Bold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/fonts/SourceSansPro-Bold-webfont.ttf -------------------------------------------------------------------------------- /Resources/fonts/SourceSansPro-BoldIt-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/fonts/SourceSansPro-BoldIt-webfont.ttf -------------------------------------------------------------------------------- /Resources/fonts/SourceSansPro-ExtraLight-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/fonts/SourceSansPro-ExtraLight-webfont.ttf -------------------------------------------------------------------------------- /Resources/fonts/SourceSansPro-ExtraLightIt-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/fonts/SourceSansPro-ExtraLightIt-webfont.ttf -------------------------------------------------------------------------------- /Resources/fonts/SourceSansPro-It-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/fonts/SourceSansPro-It-webfont.ttf -------------------------------------------------------------------------------- /Resources/fonts/SourceSansPro-Light-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/fonts/SourceSansPro-Light-webfont.ttf -------------------------------------------------------------------------------- /Resources/fonts/SourceSansPro-LightIt-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/fonts/SourceSansPro-LightIt-webfont.ttf -------------------------------------------------------------------------------- /Resources/fonts/SourceSansPro-Regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/fonts/SourceSansPro-Regular-webfont.ttf -------------------------------------------------------------------------------- /Resources/fonts/SourceSansPro-Semibold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/fonts/SourceSansPro-Semibold-webfont.ttf -------------------------------------------------------------------------------- /Resources/fonts/SourceSansPro-SemiboldIt-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/fonts/SourceSansPro-SemiboldIt-webfont.ttf -------------------------------------------------------------------------------- /Resources/my_avatar_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/my_avatar_placeholder.png -------------------------------------------------------------------------------- /Resources/my_avatar_placeholder@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/my_avatar_placeholder@2x.png -------------------------------------------------------------------------------- /Resources/user-toolbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/user-toolbar.png -------------------------------------------------------------------------------- /Resources/user-toolbar@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/user-toolbar@2x.png -------------------------------------------------------------------------------- /Resources/zulip-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/zulip-1024.png -------------------------------------------------------------------------------- /Resources/zulip-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/zulip-120.png -------------------------------------------------------------------------------- /Resources/zulip-144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/zulip-144.png -------------------------------------------------------------------------------- /Resources/zulip-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/zulip-152.png -------------------------------------------------------------------------------- /Resources/zulip-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/zulip-29.png -------------------------------------------------------------------------------- /Resources/zulip-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/zulip-50.png -------------------------------------------------------------------------------- /Resources/zulip-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/zulip-512.png -------------------------------------------------------------------------------- /Resources/zulip-58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/zulip-58.png -------------------------------------------------------------------------------- /Resources/zulip-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/zulip-72.png -------------------------------------------------------------------------------- /Resources/zulip-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/zulip-76.png -------------------------------------------------------------------------------- /Resources/zulip-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/zulip-80.png -------------------------------------------------------------------------------- /Resources/zulip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/zulip.png -------------------------------------------------------------------------------- /Resources/zulip@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Resources/zulip@2x.png -------------------------------------------------------------------------------- /ThirdParty/AFNetworking/AFJSONRequestOperation.h: -------------------------------------------------------------------------------- 1 | // AFJSONRequestOperation.h 2 | // 3 | // Copyright (c) 2011 Gowalla (http://gowalla.com/) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | #import 24 | #import "AFHTTPRequestOperation.h" 25 | 26 | /** 27 | `AFJSONRequestOperation` is a subclass of `AFHTTPRequestOperation` for downloading and working with JSON response data. 28 | 29 | ## Acceptable Content Types 30 | 31 | By default, `AFJSONRequestOperation` accepts the following MIME types, which includes the official standard, `application/json`, as well as other commonly-used types: 32 | 33 | - `application/json` 34 | - `text/json` 35 | 36 | @warning JSON parsing will use the built-in `NSJSONSerialization` class. 37 | */ 38 | @interface AFJSONRequestOperation : AFHTTPRequestOperation 39 | 40 | ///---------------------------- 41 | /// @name Getting Response Data 42 | ///---------------------------- 43 | 44 | /** 45 | A JSON object constructed from the response data. If an error occurs while parsing, `nil` will be returned, and the `error` property will be set to the error. 46 | */ 47 | @property (readonly, nonatomic, strong) id responseJSON; 48 | 49 | /** 50 | Options for reading the response JSON data and creating the Foundation objects. For possible values, see the `NSJSONSerialization` documentation section "NSJSONReadingOptions". 51 | */ 52 | @property (nonatomic, assign) NSJSONReadingOptions JSONReadingOptions; 53 | 54 | ///---------------------------------- 55 | /// @name Creating Request Operations 56 | ///---------------------------------- 57 | 58 | /** 59 | Creates and returns an `AFJSONRequestOperation` object and sets the specified success and failure callbacks. 60 | 61 | @param urlRequest The request object to be loaded asynchronously during execution of the operation 62 | @param success A block object to be executed when the operation finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the JSON object created from the response data of request. 63 | @param failure A block object to be executed when the operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data as JSON. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error describing the network or parsing error that occurred. 64 | 65 | @return A new JSON request operation 66 | */ 67 | + (instancetype)JSONRequestOperationWithRequest:(NSURLRequest *)urlRequest 68 | success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON))success 69 | failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON))failure; 70 | 71 | @end 72 | -------------------------------------------------------------------------------- /ThirdParty/AFNetworking/AFNetworkActivityIndicatorManager.h: -------------------------------------------------------------------------------- 1 | // AFNetworkActivityIndicatorManager.h 2 | // 3 | // Copyright (c) 2011 Gowalla (http://gowalla.com/) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | #import 24 | 25 | #import 26 | 27 | #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) 28 | #import 29 | 30 | /** 31 | `AFNetworkActivityIndicatorManager` manages the state of the network activity indicator in the status bar. When enabled, it will listen for notifications indicating that a network request operation has started or finished, and start or stop animating the indicator accordingly. The number of active requests is incremented and decremented much like a stack or a semaphore, and the activity indicator will animate so long as that number is greater than zero. 32 | 33 | You should enable the shared instance of `AFNetworkActivityIndicatorManager` when your application finishes launching. In `AppDelegate application:didFinishLaunchingWithOptions:` you can do so with the following code: 34 | 35 | [[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES]; 36 | 37 | By setting `isNetworkActivityIndicatorVisible` to `YES` for `sharedManager`, the network activity indicator will show and hide automatically as requests start and finish. You should not ever need to call `incrementActivityCount` or `decrementActivityCount` yourself. 38 | 39 | See the Apple Human Interface Guidelines section about the Network Activity Indicator for more information: 40 | http://developer.apple.com/library/iOS/#documentation/UserExperience/Conceptual/MobileHIG/UIElementGuidelines/UIElementGuidelines.html#//apple_ref/doc/uid/TP40006556-CH13-SW44 41 | */ 42 | @interface AFNetworkActivityIndicatorManager : NSObject 43 | 44 | /** 45 | A Boolean value indicating whether the manager is enabled. 46 | 47 | If YES, the manager will change status bar network activity indicator according to network operation notifications it receives. The default value is NO. 48 | */ 49 | @property (nonatomic, assign, getter = isEnabled) BOOL enabled; 50 | 51 | /** 52 | A Boolean value indicating whether the network activity indicator is currently displayed in the status bar. 53 | */ 54 | @property (readonly, nonatomic, assign) BOOL isNetworkActivityIndicatorVisible; 55 | 56 | /** 57 | Returns the shared network activity indicator manager object for the system. 58 | 59 | @return The systemwide network activity indicator manager. 60 | */ 61 | + (instancetype)sharedManager; 62 | 63 | /** 64 | Increments the number of active network requests. If this number was zero before incrementing, this will start animating the status bar network activity indicator. 65 | */ 66 | - (void)incrementActivityCount; 67 | 68 | /** 69 | Decrements the number of active network requests. If this number becomes zero before decrementing, this will stop animating the status bar network activity indicator. 70 | */ 71 | - (void)decrementActivityCount; 72 | 73 | @end 74 | 75 | #endif 76 | -------------------------------------------------------------------------------- /ThirdParty/AFNetworking/AFNetworking.h: -------------------------------------------------------------------------------- 1 | // AFNetworking.h 2 | // 3 | // Copyright (c) 2011 Gowalla (http://gowalla.com/) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | #import 24 | #import 25 | 26 | #ifndef _AFNETWORKING_ 27 | #define _AFNETWORKING_ 28 | 29 | #import "AFURLConnectionOperation.h" 30 | 31 | #import "AFHTTPRequestOperation.h" 32 | #import "AFJSONRequestOperation.h" 33 | #import "AFXMLRequestOperation.h" 34 | #import "AFPropertyListRequestOperation.h" 35 | #import "AFHTTPClient.h" 36 | 37 | #import "AFImageRequestOperation.h" 38 | 39 | #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) 40 | #import "AFNetworkActivityIndicatorManager.h" 41 | #import "UIImageView+AFNetworking.h" 42 | #endif 43 | #endif /* _AFNETWORKING_ */ 44 | -------------------------------------------------------------------------------- /ThirdParty/AFNetworking/AFPropertyListRequestOperation.h: -------------------------------------------------------------------------------- 1 | // AFPropertyListRequestOperation.h 2 | // 3 | // Copyright (c) 2011 Gowalla (http://gowalla.com/) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | #import 24 | #import "AFHTTPRequestOperation.h" 25 | 26 | /** 27 | `AFPropertyListRequestOperation` is a subclass of `AFHTTPRequestOperation` for downloading and deserializing objects with property list (plist) response data. 28 | 29 | ## Acceptable Content Types 30 | 31 | By default, `AFPropertyListRequestOperation` accepts the following MIME types: 32 | 33 | - `application/x-plist` 34 | */ 35 | @interface AFPropertyListRequestOperation : AFHTTPRequestOperation 36 | 37 | ///---------------------------- 38 | /// @name Getting Response Data 39 | ///---------------------------- 40 | 41 | /** 42 | An object deserialized from a plist constructed using the response data. 43 | */ 44 | @property (readonly, nonatomic) id responsePropertyList; 45 | 46 | ///-------------------------------------- 47 | /// @name Managing Property List Behavior 48 | ///-------------------------------------- 49 | 50 | /** 51 | One of the `NSPropertyListMutabilityOptions` options, specifying the mutability of objects deserialized from the property list. By default, this is `NSPropertyListImmutable`. 52 | */ 53 | @property (nonatomic, assign) NSPropertyListReadOptions propertyListReadOptions; 54 | 55 | /** 56 | Creates and returns an `AFPropertyListRequestOperation` object and sets the specified success and failure callbacks. 57 | 58 | @param urlRequest The request object to be loaded asynchronously during execution of the operation 59 | @param success A block object to be executed when the operation finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the object deserialized from a plist constructed using the response data. 60 | @param failure A block object to be executed when the operation finishes unsuccessfully, or that finishes successfully, but encountered an error while deserializing the object from a property list. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error describing the network or parsing error that occurred. 61 | 62 | @return A new property list request operation 63 | */ 64 | + (instancetype)propertyListRequestOperationWithRequest:(NSURLRequest *)urlRequest 65 | success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id propertyList))success 66 | failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id propertyList))failure; 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /ThirdParty/AFNetworking/AFPropertyListRequestOperation.m: -------------------------------------------------------------------------------- 1 | // AFPropertyListRequestOperation.m 2 | // 3 | // Copyright (c) 2011 Gowalla (http://gowalla.com/) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | #import "AFPropertyListRequestOperation.h" 24 | 25 | static dispatch_queue_t property_list_request_operation_processing_queue() { 26 | static dispatch_queue_t af_property_list_request_operation_processing_queue; 27 | static dispatch_once_t onceToken; 28 | dispatch_once(&onceToken, ^{ 29 | af_property_list_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.property-list-request.processing", DISPATCH_QUEUE_CONCURRENT); 30 | }); 31 | 32 | return af_property_list_request_operation_processing_queue; 33 | } 34 | 35 | @interface AFPropertyListRequestOperation () 36 | @property (readwrite, nonatomic) id responsePropertyList; 37 | @property (readwrite, nonatomic, assign) NSPropertyListFormat propertyListFormat; 38 | @property (readwrite, nonatomic) NSError *propertyListError; 39 | @end 40 | 41 | @implementation AFPropertyListRequestOperation 42 | @synthesize responsePropertyList = _responsePropertyList; 43 | @synthesize propertyListReadOptions = _propertyListReadOptions; 44 | @synthesize propertyListFormat = _propertyListFormat; 45 | @synthesize propertyListError = _propertyListError; 46 | 47 | + (instancetype)propertyListRequestOperationWithRequest:(NSURLRequest *)request 48 | success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id propertyList))success 49 | failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id propertyList))failure 50 | { 51 | AFPropertyListRequestOperation *requestOperation = [(AFPropertyListRequestOperation *)[self alloc] initWithRequest:request]; 52 | [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { 53 | if (success) { 54 | success(operation.request, operation.response, responseObject); 55 | } 56 | } failure:^(AFHTTPRequestOperation *operation, NSError *error) { 57 | if (failure) { 58 | failure(operation.request, operation.response, error, [(AFPropertyListRequestOperation *)operation responsePropertyList]); 59 | } 60 | }]; 61 | 62 | return requestOperation; 63 | } 64 | 65 | - (id)initWithRequest:(NSURLRequest *)urlRequest { 66 | self = [super initWithRequest:urlRequest]; 67 | if (!self) { 68 | return nil; 69 | } 70 | 71 | self.propertyListReadOptions = NSPropertyListImmutable; 72 | 73 | return self; 74 | } 75 | 76 | 77 | - (id)responsePropertyList { 78 | if (!_responsePropertyList && [self.responseData length] > 0 && [self isFinished]) { 79 | NSPropertyListFormat format; 80 | NSError *error = nil; 81 | self.responsePropertyList = [NSPropertyListSerialization propertyListWithData:self.responseData options:self.propertyListReadOptions format:&format error:&error]; 82 | self.propertyListFormat = format; 83 | self.propertyListError = error; 84 | } 85 | 86 | return _responsePropertyList; 87 | } 88 | 89 | - (NSError *)error { 90 | if (_propertyListError) { 91 | return _propertyListError; 92 | } else { 93 | return [super error]; 94 | } 95 | } 96 | 97 | #pragma mark - AFHTTPRequestOperation 98 | 99 | + (NSSet *)acceptableContentTypes { 100 | return [NSSet setWithObjects:@"application/x-plist", nil]; 101 | } 102 | 103 | + (BOOL)canProcessRequest:(NSURLRequest *)request { 104 | return [[[request URL] pathExtension] isEqualToString:@"plist"] || [super canProcessRequest:request]; 105 | } 106 | 107 | - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success 108 | failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure 109 | { 110 | #pragma clang diagnostic push 111 | #pragma clang diagnostic ignored "-Warc-retain-cycles" 112 | #pragma clang diagnostic ignored "-Wgnu" 113 | self.completionBlock = ^ { 114 | if (self.error) { 115 | if (failure) { 116 | dispatch_async(self.failureCallbackQueue ?: dispatch_get_main_queue(), ^{ 117 | failure(self, self.error); 118 | }); 119 | } 120 | } else { 121 | dispatch_async(property_list_request_operation_processing_queue(), ^(void) { 122 | id propertyList = self.responsePropertyList; 123 | 124 | if (self.propertyListError) { 125 | if (failure) { 126 | dispatch_async(self.failureCallbackQueue ?: dispatch_get_main_queue(), ^{ 127 | failure(self, self.error); 128 | }); 129 | } 130 | } else { 131 | if (success) { 132 | dispatch_async(self.successCallbackQueue ?: dispatch_get_main_queue(), ^{ 133 | success(self, propertyList); 134 | }); 135 | } 136 | } 137 | }); 138 | } 139 | }; 140 | #pragma clang diagnostic pop 141 | } 142 | 143 | @end 144 | -------------------------------------------------------------------------------- /ThirdParty/AFNetworking/AFXMLRequestOperation.h: -------------------------------------------------------------------------------- 1 | // AFXMLRequestOperation.h 2 | // 3 | // Copyright (c) 2011 Gowalla (http://gowalla.com/) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | #import 24 | #import "AFHTTPRequestOperation.h" 25 | 26 | #import 27 | 28 | /** 29 | `AFXMLRequestOperation` is a subclass of `AFHTTPRequestOperation` for downloading and working with XML response data. 30 | 31 | ## Acceptable Content Types 32 | 33 | By default, `AFXMLRequestOperation` accepts the following MIME types, which includes the official standard, `application/xml`, as well as other commonly-used types: 34 | 35 | - `application/xml` 36 | - `text/xml` 37 | 38 | ## Use With AFHTTPClient 39 | 40 | When `AFXMLRequestOperation` is registered with `AFHTTPClient`, the response object in the success callback of `HTTPRequestOperationWithRequest:success:failure:` will be an instance of `NSXMLParser`. On platforms that support `NSXMLDocument`, you have the option to ignore the response object, and simply use the `responseXMLDocument` property of the operation argument of the callback. 41 | */ 42 | @interface AFXMLRequestOperation : AFHTTPRequestOperation 43 | 44 | ///---------------------------- 45 | /// @name Getting Response Data 46 | ///---------------------------- 47 | 48 | /** 49 | An `NSXMLParser` object constructed from the response data. 50 | */ 51 | @property (readonly, nonatomic, strong) NSXMLParser *responseXMLParser; 52 | 53 | #ifdef __MAC_OS_X_VERSION_MIN_REQUIRED 54 | /** 55 | An `NSXMLDocument` object constructed from the response data. If an error occurs while parsing, `nil` will be returned, and the `error` property will be set to the error. 56 | */ 57 | @property (readonly, nonatomic, strong) NSXMLDocument *responseXMLDocument; 58 | #endif 59 | 60 | /** 61 | Creates and returns an `AFXMLRequestOperation` object and sets the specified success and failure callbacks. 62 | 63 | @param urlRequest The request object to be loaded asynchronously during execution of the operation 64 | @param success A block object to be executed when the operation finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the XML parser constructed with the response data of request. 65 | @param failure A block object to be executed when the operation finishes unsuccessfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error describing the network error that occurred. 66 | 67 | @return A new XML request operation 68 | */ 69 | + (instancetype)XMLParserRequestOperationWithRequest:(NSURLRequest *)urlRequest 70 | success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLParser *XMLParser))success 71 | failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSXMLParser *XMLParser))failure; 72 | 73 | 74 | #ifdef __MAC_OS_X_VERSION_MIN_REQUIRED 75 | /** 76 | Creates and returns an `AFXMLRequestOperation` object and sets the specified success and failure callbacks. 77 | 78 | @param urlRequest The request object to be loaded asynchronously during execution of the operation 79 | @param success A block object to be executed when the operation finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the XML document created from the response data of request. 80 | @param failure A block object to be executed when the operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data as XML. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error describing the network or parsing error that occurred. 81 | 82 | @return A new XML request operation 83 | */ 84 | + (instancetype)XMLDocumentRequestOperationWithRequest:(NSURLRequest *)urlRequest 85 | success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLDocument *document))success 86 | failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSXMLDocument *document))failure; 87 | #endif 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /ThirdParty/AFNetworking/UIImageView+AFNetworking.h: -------------------------------------------------------------------------------- 1 | // UIImageView+AFNetworking.h 2 | // 3 | // Copyright (c) 2011 Gowalla (http://gowalla.com/) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | #import 24 | #import "AFImageRequestOperation.h" 25 | 26 | #import 27 | 28 | #if __IPHONE_OS_VERSION_MIN_REQUIRED 29 | #import 30 | 31 | /** 32 | This category adds methods to the UIKit framework's `UIImageView` class. The methods in this category provide support for loading remote images asynchronously from a URL. 33 | */ 34 | @interface UIImageView (AFNetworking) 35 | 36 | /** 37 | Creates and enqueues an image request operation, which asynchronously downloads the image from the specified URL, and sets it the request is finished. Any previous image request for the receiver will be cancelled. If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished. 38 | 39 | By default, URL requests have a cache policy of `NSURLCacheStorageAllowed` and a timeout interval of 30 seconds, and are set not handle cookies. To configure URL requests differently, use `setImageWithURLRequest:placeholderImage:success:failure:` 40 | 41 | @param url The URL used for the image request. 42 | */ 43 | - (void)setImageWithURL:(NSURL *)url; 44 | 45 | /** 46 | Creates and enqueues an image request operation, which asynchronously downloads the image from the specified URL. Any previous image request for the receiver will be cancelled. If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished. 47 | 48 | By default, URL requests have a cache policy of `NSURLCacheStorageAllowed` and a timeout interval of 30 seconds, and are set not handle cookies. To configure URL requests differently, use `setImageWithURLRequest:placeholderImage:success:failure:` 49 | 50 | @param url The URL used for the image request. 51 | @param placeholderImage The image to be set initially, until the image request finishes. If `nil`, the image view will not change its image until the image request finishes. 52 | */ 53 | - (void)setImageWithURL:(NSURL *)url 54 | placeholderImage:(UIImage *)placeholderImage; 55 | 56 | /** 57 | Creates and enqueues an image request operation, which asynchronously downloads the image with the specified URL request object. Any previous image request for the receiver will be cancelled. If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished. 58 | 59 | If a success block is specified, it is the responsibility of the block to set the image of the image view before returning. If no success block is specified, the default behavior of setting the image with `self.image = image` is executed. 60 | 61 | @param urlRequest The URL request used for the image request. 62 | @param placeholderImage The image to be set initially, until the image request finishes. If `nil`, the image view will not change its image until the image request finishes. 63 | @param success A block to be executed when the image request operation finishes successfully, with a status code in the 2xx range, and with an acceptable content type (e.g. `image/png`). This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the image created from the response data of request. If the image was returned from cache, the request and response parameters will be `nil`. 64 | @param failure A block object to be executed when the image request operation finishes unsuccessfully, or that finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error object describing the network or parsing error that occurred. 65 | */ 66 | - (void)setImageWithURLRequest:(NSURLRequest *)urlRequest 67 | placeholderImage:(UIImage *)placeholderImage 68 | success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success 69 | failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure; 70 | 71 | /** 72 | Cancels any executing image request operation for the receiver, if one exists. 73 | */ 74 | - (void)cancelImageRequestOperation; 75 | 76 | @end 77 | 78 | #endif 79 | -------------------------------------------------------------------------------- /ThirdParty/JASidePanels/UIViewController+JASidePanel.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012 Jesse Andersen. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | If you happen to meet one of the copyright holders in a bar you are obligated 15 | to buy them one pint of beer. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | #import 27 | 28 | @class JASidePanelController; 29 | 30 | /* This optional category provides a convenience method for finding the current 31 | side panel controller that your view controller belongs to. It is similar to the 32 | Apple provided "navigationController" and "tabBarController" methods. 33 | */ 34 | @interface UIViewController (JASidePanel) 35 | 36 | // The nearest ancestor in the view controller hierarchy that is a side panel controller. 37 | @property (nonatomic, weak, readonly) JASidePanelController *findSidePanelController; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /ThirdParty/JASidePanels/UIViewController+JASidePanel.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012 Jesse Andersen. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | If you happen to meet one of the copyright holders in a bar you are obligated 15 | to buy them one pint of beer. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | #import "UIViewController+JASidePanel.h" 27 | 28 | #import "JASidePanelController.h" 29 | 30 | @implementation UIViewController (JASidePanel) 31 | 32 | - (JASidePanelController *)findSidePanelController { 33 | UIViewController *iter = self.parentViewController; 34 | while (iter) { 35 | if ([iter isKindOfClass:[JASidePanelController class]]) { 36 | return (JASidePanelController *)iter; 37 | } else if (iter.parentViewController && iter.parentViewController != iter) { 38 | iter = iter.parentViewController; 39 | } else { 40 | iter = nil; 41 | } 42 | } 43 | return nil; 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /Zulip.css: -------------------------------------------------------------------------------- 1 | code { 2 | padding: 2px 4px; 3 | color: #d14; 4 | background-color: #f7f7f9; 5 | border: 1px solid #e1e1e8; 6 | } 7 | 8 | img { 9 | max-height: 200px; 10 | } 11 | 12 | img.emoji { 13 | height: 1.4em; 14 | width: 1.4em; 15 | } 16 | -------------------------------------------------------------------------------- /Zulip/AutocompleteResults.h: -------------------------------------------------------------------------------- 1 | // 2 | // AutocompleteResults.h 3 | // Zulip 4 | // 5 | // Created by Michael Walker on 1/3/14. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface AutocompleteResults : NSObject 12 | 13 | @property (readonly) NSOrderedSet *prefixMatches; 14 | @property (readonly) NSOrderedSet *nonPrefixMatches; 15 | @property (readonly) NSArray *orderedResults; 16 | @property (readonly) BOOL isExactMatch; 17 | 18 | - (id)initWithArray:(NSArray *)data query:(NSString *)query; 19 | - (id)initWithDictionary:(NSDictionary *)data query:(NSString *)query; 20 | - (id)initWithSet:(NSSet *)data query:(NSString *)query; 21 | 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /Zulip/AutocompleteResults.m: -------------------------------------------------------------------------------- 1 | // 2 | // AutocompleteResults.m 3 | // Zulip 4 | // 5 | // Created by Michael Walker on 1/3/14. 6 | // 7 | // 8 | 9 | #import "AutocompleteResults.h" 10 | 11 | @interface AutocompleteResults () 12 | 13 | @property (strong, nonatomic, readwrite) NSOrderedSet *prefixMatches; 14 | @property (strong, nonatomic, readwrite) NSOrderedSet *nonPrefixMatches; 15 | @property (assign, nonatomic, readwrite) BOOL isExactMatch; 16 | 17 | @property (strong, nonatomic) id data; 18 | @property (strong, nonatomic) NSString *query; 19 | 20 | @end 21 | 22 | @implementation AutocompleteResults 23 | 24 | - (id)initWithArray:(NSArray *)data query:(NSString *)query { 25 | if (self = [super init]) { 26 | [self performSearchWithData:data query:query]; 27 | } 28 | return self; 29 | } 30 | 31 | - (id)initWithDictionary:(NSDictionary *)data query:(NSString *)query { 32 | if (self = [super init]) { 33 | [self performSearchWithData:data query:query]; 34 | } 35 | return self; 36 | } 37 | 38 | - (id)initWithSet:(NSSet *)data query:(NSString *)query { 39 | if (self = [super init]) { 40 | [self performSearchWithData:data.allObjects query:query]; 41 | } 42 | return self; 43 | } 44 | 45 | - (void)performSearchWithData:(id)data query:(NSString *)query { 46 | self.data = data; 47 | self.query = query; 48 | 49 | NSMutableOrderedSet *mutablePrefixSet = [[NSMutableOrderedSet alloc] init]; 50 | NSMutableOrderedSet *mutableNonPrefixSet = [[NSMutableOrderedSet alloc] init]; 51 | 52 | for(NSString *candidate in data) { 53 | NSString *comparableObject = candidate; 54 | if ([data isKindOfClass:[NSDictionary class]]) { 55 | comparableObject = data[candidate]; 56 | } 57 | 58 | NSUInteger index = [comparableObject rangeOfString:query options:NSCaseInsensitiveSearch].location; 59 | if (index == 0) { 60 | if ([comparableObject.lowercaseString isEqualToString:query.lowercaseString]) { 61 | self.isExactMatch = YES; 62 | } 63 | [mutablePrefixSet addObject:candidate]; 64 | } else if (index != NSNotFound) { 65 | [mutableNonPrefixSet addObject:candidate]; 66 | } 67 | } 68 | 69 | self.prefixMatches = [mutablePrefixSet copy]; 70 | self.nonPrefixMatches = [mutableNonPrefixSet copy]; 71 | } 72 | 73 | - (NSArray *)orderedResults { 74 | return [self.prefixMatches.array arrayByAddingObjectsFromArray:self.nonPrefixMatches.array]; 75 | } 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /Zulip/Categories/NSArray+Blocks.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+Blocks.h 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 10/22/13. 6 | // 7 | // 8 | 9 | #import 10 | 11 | typedef BOOL(^FilterPredicate)(id obj); 12 | typedef id(^MapPredicate)(id obj); 13 | 14 | @interface NSArray (Blocks) 15 | 16 | - (NSArray *)filter:(FilterPredicate)predicate; 17 | 18 | - (NSArray *)map:(MapPredicate)predicate; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Zulip/Categories/NSArray+Blocks.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+Blocks.m 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 10/22/13. 6 | // 7 | // 8 | 9 | #import "NSArray+Blocks.h" 10 | 11 | @implementation NSArray (Blocks) 12 | 13 | - (NSArray *)filter:(FilterPredicate)predicate 14 | { 15 | if (!predicate) { 16 | return self; 17 | } 18 | 19 | NSIndexSet *indices = [self indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { 20 | return predicate(obj); 21 | }]; 22 | 23 | return [self objectsAtIndexes:indices]; 24 | } 25 | 26 | - (NSArray *)map:(MapPredicate)predicate 27 | { 28 | if (!predicate) { 29 | return self; 30 | } 31 | 32 | NSMutableArray *newArray = [[NSMutableArray alloc] init]; 33 | for (id obj in self) { 34 | id newObj = predicate(obj); 35 | [newArray addObject:newObj ? newObj : [NSNull null]]; 36 | } 37 | 38 | return newArray; 39 | } 40 | @end 41 | -------------------------------------------------------------------------------- /Zulip/Controllers/LongPoller.h: -------------------------------------------------------------------------------- 1 | // 2 | // LongPoller.h 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 8/5/13. 6 | // 7 | // 8 | 9 | #import 10 | 11 | typedef void(^EventBlock)(NSArray *events); 12 | typedef void(^InitialData)(NSDictionary *data); 13 | typedef void(^ErrorHandler)(void); 14 | 15 | @interface LongPoller : NSObject 16 | 17 | @property (nonatomic, retain) NSString *queueId; 18 | 19 | - (id)initWithInitialBlock:(InitialData)initial andEventBlock:(EventBlock)events; 20 | - (void)registerErrorHandler:(ErrorHandler)handler; 21 | 22 | - (void)registerWithOptions:(NSDictionary *)opts; 23 | - (void)reset; 24 | 25 | // If this queue is to be persistent (saving its queueid across restarts, 26 | // and attempting to reuse it). 27 | - (void)makePersistentWithUniqueName:(NSString *)name; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Zulip/Controllers/UnreadManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // UnreadManager.h 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 8/2/13. 6 | // 7 | // 8 | 9 | #import 10 | 11 | #import "RawMessage.h" 12 | 13 | /** 14 | Keeps track of unread counts for streams and views, 15 | and sends out NSNotifications for when they change 16 | */ 17 | extern NSString * const ZUnreadCountChangeNotification; 18 | extern NSString * const ZUnreadCountChangeNotificationData; 19 | 20 | @interface UnreadManager : NSObject 21 | 22 | @property (nonatomic, retain, readonly) NSDictionary *unreadCounts; 23 | 24 | - (void)handleIncomingMessage:(RawMessage *)message; 25 | - (void)markMessageRead:(RawMessage *)message; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Zulip/Controllers/UnreadManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // UnreadManager.m 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 8/2/13. 6 | // 7 | // 8 | 9 | #import "UnreadManager.h" 10 | 11 | @interface UnreadManager () 12 | 13 | @property (nonatomic, retain) NSMutableDictionary *stream_unread; 14 | @property (nonatomic, retain) NSMutableDictionary *users_unread; 15 | @property (nonatomic, retain) NSMutableSet *home_unread; 16 | @property (nonatomic, retain) NSMutableSet *pms_unread; 17 | 18 | @end 19 | 20 | NSString * const ZUnreadCountChangeNotification = @"UnreadMessageCountNotification"; 21 | NSString * const ZUnreadCountChangeNotificationData = @"UnreadMessageCountNotificationData"; 22 | 23 | @implementation UnreadManager 24 | 25 | - (id)init 26 | { 27 | self = [super init]; 28 | if (self) { 29 | self.stream_unread = [[NSMutableDictionary alloc] init]; 30 | self.users_unread = [[NSMutableDictionary alloc] init]; 31 | self.home_unread = [[NSMutableSet alloc] init]; 32 | self.pms_unread = [[NSMutableSet alloc] init]; 33 | } 34 | return self; 35 | } 36 | 37 | - (void)handleIncomingMessage:(RawMessage *)message 38 | { 39 | // We ignore new messages if they are read 40 | if ([message read]) 41 | return; 42 | 43 | if (message.subscription) { 44 | if (message.subscription.name) { 45 | // Stream message that we have stream information for 46 | NSString *stream = message.subscription.name; 47 | 48 | NSMutableSet *stream_set = [self setForStream:stream]; 49 | [stream_set addObject:message.messageID]; 50 | [self.stream_unread setObject:stream_set forKey:stream]; 51 | } else { 52 | // FIXME: Send log message to server once we have a runtime 53 | // logging system. 54 | NSLog(@"Found ZSubscription in message without name: %@", message); 55 | } 56 | } else { 57 | // PM 58 | NSString *sender = message.sender.email; 59 | if (!self.users_unread[sender]) { 60 | self.users_unread[sender] = [[NSMutableSet alloc] init]; 61 | } 62 | 63 | [self.users_unread[sender] addObject:message.messageID]; 64 | [self.pms_unread addObject:message.messageID]; 65 | } 66 | 67 | // In home view 68 | if (!message.subscription || [message.subscription.in_home_view boolValue]) { 69 | [self.home_unread addObject:message.messageID]; 70 | } 71 | 72 | [self calculateCounts]; 73 | } 74 | 75 | - (void)markMessageRead:(RawMessage *)message 76 | { 77 | if (message.subscription) { 78 | NSString *stream = message.subscription.name; 79 | 80 | NSMutableSet *stream_set = [self setForStream:message.subscription.name]; 81 | [stream_set removeObject:message.messageID]; 82 | [self.stream_unread setObject:stream_set forKey:stream]; 83 | } else { 84 | NSString *sender = message.sender.email; 85 | 86 | [self.pms_unread removeObject:message.messageID]; 87 | [self.users_unread[sender] removeObject:message.messageID]; 88 | } 89 | 90 | [self.home_unread removeObject:message.messageID]; 91 | 92 | [self calculateCounts]; 93 | } 94 | 95 | - (NSMutableSet *)setForStream:(NSString *)stream 96 | { 97 | if (![self.stream_unread objectForKey:stream]) { 98 | [self.stream_unread setObject:[[NSMutableSet alloc] init] forKey:stream]; 99 | } 100 | 101 | return [self.stream_unread objectForKey:stream]; 102 | } 103 | 104 | - (void)calculateCounts 105 | { 106 | NSMutableDictionary *unread_counts = [[NSMutableDictionary alloc] init]; 107 | [unread_counts setObject:[NSNumber numberWithInt:[self.home_unread count]] forKey:@"home"]; 108 | [unread_counts setObject:[NSNumber numberWithInt:[self.pms_unread count]] forKey:@"pms"]; 109 | 110 | NSMutableDictionary *stream_counts = [[NSMutableDictionary alloc] init]; 111 | for (NSString *streamName in self.stream_unread) { 112 | NSMutableSet *unread = [self.stream_unread objectForKey:streamName]; 113 | [stream_counts setObject:[NSNumber numberWithInt:[unread count]] forKey:streamName]; 114 | } 115 | [unread_counts setObject:stream_counts forKey:@"streams"]; 116 | 117 | NSMutableDictionary *user_counts = [[NSMutableDictionary alloc] init]; 118 | for (NSString *user in self.users_unread) { 119 | NSMutableSet *unread = self.users_unread[user]; 120 | user_counts[user] = @(unread.count); 121 | } 122 | unread_counts[@"user"] = user_counts; 123 | 124 | _unreadCounts = unread_counts; 125 | 126 | NSNotification *unreadChanges = [NSNotification notificationWithName:ZUnreadCountChangeNotification 127 | object:self 128 | userInfo:@{ZUnreadCountChangeNotificationData: self.unreadCounts}]; 129 | [[NSNotificationCenter defaultCenter] postNotification:unreadChanges]; 130 | } 131 | 132 | @end 133 | -------------------------------------------------------------------------------- /Zulip/Controllers/ViewControllers/AboutViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AboutViewController.h 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 9/26/13. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface AboutViewController : UIViewController 12 | 13 | @property (strong, nonatomic) IBOutlet UILabel *version; 14 | 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Zulip/Controllers/ViewControllers/AboutViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AboutViewController.m 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 9/26/13. 6 | // 7 | // 8 | 9 | #import "AboutViewController.h" 10 | 11 | @interface AboutViewController () 12 | 13 | @end 14 | 15 | @implementation AboutViewController 16 | 17 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 18 | { 19 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 20 | if (self) { 21 | } 22 | return self; 23 | } 24 | 25 | - (void)viewDidLoad 26 | { 27 | [super viewDidLoad]; 28 | // On iOS 7, don't extend our content under the toolbar 29 | if ([[[UIDevice currentDevice] systemVersion] compare:@"7.0" options:NSNumericSearch] != NSOrderedAscending) { 30 | self.edgesForExtendedLayout = UIRectEdgeLeft | UIRectEdgeBottom | UIRectEdgeRight; 31 | } 32 | 33 | NSString *versionNum = [NSString stringWithFormat:@"Version %@", [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]]; 34 | self.version.text = versionNum; 35 | 36 | } 37 | 38 | - (void)viewWillDisappear:(BOOL)animated 39 | { 40 | [super viewWillDisappear:animated]; 41 | [self.navigationController setNavigationBarHidden:YES animated:NO]; 42 | } 43 | 44 | - (void)didReceiveMemoryWarning 45 | { 46 | [super didReceiveMemoryWarning]; 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /Zulip/Controllers/ViewControllers/AboutViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Zulip/Controllers/ViewControllers/HomeViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // HomeViewController.h 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 7/30/13. 6 | // 7 | // 8 | 9 | #import "StreamViewController.h" 10 | 11 | @interface HomeViewController : StreamViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Zulip/Controllers/ViewControllers/LeftSidebarViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // LeftSidebarViewController.h 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 8/7/13. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface LeftSidebarViewController : UITableViewController 12 | 13 | - (void)reset; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Zulip/Controllers/ViewControllers/LeftSidebarViewController.xib: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /Zulip/Controllers/ViewControllers/NarrowViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // NarrowViewController.h 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 7/30/13. 6 | // 7 | // 8 | 9 | #import 10 | #import "StreamViewController.h" 11 | #import "NarrowOperators.h" 12 | 13 | @interface NarrowViewController : StreamViewController 14 | 15 | - (id)initWithOperators:(NarrowOperators *)operators; 16 | 17 | // Default behaviour is to scroll to first unread 18 | // This will attempt to scroll to the desired message ID 19 | // when loaded if the message is not yet loaded 20 | - (void)scrollToMessageID:(long)messageId; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Zulip/Controllers/ViewControllers/NarrowViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // NarrowViewController.m 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 7/30/13. 6 | // 7 | // 8 | 9 | #import "NarrowViewController.h" 10 | #import "ZulipAPIController.h" 11 | #import "StreamComposeView.h" 12 | #import "StreamProgressView.h" 13 | #import "UIView+Layout.h" 14 | 15 | typedef enum { 16 | // Ugh Obj-C at making enum values globally visible 17 | NarrowScrollToFirstUnread = 0, 18 | NarrowScrollToMessageID = 1 19 | } InitialScrollSettings; 20 | 21 | @interface NarrowViewController () 22 | 23 | @property (nonatomic, assign) InitialScrollSettings initialScrollSetting; 24 | @property (nonatomic, assign) long scrollMessageID; 25 | 26 | @end 27 | 28 | @implementation NarrowViewController 29 | 30 | - (id)initWithOperators:(NarrowOperators *)operators 31 | { 32 | self = [super init]; 33 | 34 | if (self) { 35 | self.operators = operators; 36 | self.initialScrollSetting = NarrowScrollToFirstUnread; 37 | self.scrollMessageID = -1; 38 | 39 | self.title = self.operators.title; 40 | 41 | if ([self.title isEqualToString:@"Private Messages"]) { 42 | self.composeView.isPrivate = YES; 43 | } else { 44 | self.composeView.defaultRecipient = self.title; 45 | } 46 | 47 | [self initialPopulate]; 48 | } 49 | 50 | return self; 51 | } 52 | 53 | - (void)scrollToMessageID:(long)messageId 54 | { 55 | int rowToScroll = [self rowWithId:self.scrollMessageID]; 56 | if (rowToScroll > -1) { 57 | [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:rowToScroll inSection:0] 58 | atScrollPosition:UITableViewScrollPositionMiddle animated:NO]; ; 59 | } else { 60 | self.initialScrollSetting = NarrowScrollToMessageID; 61 | self.scrollMessageID = messageId; 62 | } 63 | } 64 | 65 | #pragma mark - StreamViewControllerDelegate 66 | 67 | - (void)initialPopulate 68 | { 69 | // Clear any messages first 70 | if ([self.messages count]) { 71 | [self clearMessages]; 72 | } 73 | 74 | self.tableView.tableFooterView = [[StreamProgressView alloc] initWithFrame:CGRectMake(0, 0, self.view.width, [StreamProgressView height])]; 75 | 76 | [[ZulipAPIController sharedInstance] loadMessagesAroundAnchor:[[ZulipAPIController sharedInstance] pointer] 77 | before:12 78 | after:0 79 | withOperators:self.operators 80 | completionBlock:^(NSArray *messages, BOOL isFinished) { 81 | NSLog(@"Initially loaded %i messages!", (int)[messages count]); 82 | 83 | [self loadMessages:messages]; 84 | 85 | if ([self.messages count] == 0) 86 | return; 87 | 88 | if (isFinished) { 89 | self.tableView.tableFooterView = nil; 90 | } 91 | 92 | // TODO: This is very similar (but not exactly the same as) HomeViewController.m:60 93 | // We should find a way to consolidate the ugly loadMessageAroundAnchor: method call 94 | RawMessage *last = [self.messages lastObject]; 95 | if ([last.messageID longValue] < [ZulipAPIController sharedInstance].maxServerMessageId) { 96 | // More messages to load 97 | [[ZulipAPIController sharedInstance] loadMessagesAroundAnchor:[[ZulipAPIController sharedInstance] pointer] 98 | before:0 99 | after:20 100 | withOperators:self.operators 101 | completionBlock:^(NSArray *newerMessages, BOOL isFinishedLoading) { 102 | NSLog(@"Initially loaded forward %i messages!", (int)[newerMessages count]); 103 | [self loadMessages:newerMessages]; 104 | [self newMessagesLoaded]; 105 | }]; 106 | } else { 107 | [self newMessagesLoaded]; 108 | } 109 | 110 | 111 | [self newMessagesLoaded]; 112 | }]; 113 | } 114 | 115 | - (void)resumePopulate 116 | { 117 | } 118 | 119 | - (NSIndexPath *)indexOfFirstUnread 120 | { 121 | NSIndexPath *unread = nil; 122 | for (NSUInteger i = 0; i < [self.messages count]; i++) { 123 | RawMessage *msg = [self.messages objectAtIndex:i]; 124 | if (![msg read]) { 125 | unread = [NSIndexPath indexPathForRow:i inSection:0]; 126 | break; 127 | } 128 | } 129 | return unread; 130 | } 131 | 132 | - (NSIndexPath *)indexOfScrollToMessage:(long)messageID 133 | { 134 | int rowToScroll = [self rowWithId:self.scrollMessageID]; 135 | if (rowToScroll == -1) { 136 | NSLog(@"Narrow initially loaded messages but DID NOT FIND scroll-to message id %li", self.scrollMessageID); 137 | return nil; 138 | } 139 | return[NSIndexPath indexPathForRow:rowToScroll inSection:0]; 140 | } 141 | 142 | - (void)newMessagesLoaded 143 | { 144 | if ([self.messages count] == 0) { 145 | return; 146 | } 147 | 148 | NSIndexPath *target = nil; 149 | if (self.initialScrollSetting == NarrowScrollToFirstUnread) { 150 | target = [self indexOfFirstUnread]; 151 | } else if (self.initialScrollSetting == NarrowScrollToMessageID && 152 | self.scrollMessageID > -1) { 153 | target = [self indexOfScrollToMessage:self.scrollMessageID]; 154 | } else { 155 | NSLog(@"New messages loaded in narrow but BAD scroll data!"); 156 | return; 157 | } 158 | 159 | if (!target) { 160 | target = [NSIndexPath indexPathForRow:[self.messages count] - 1 inSection:0]; 161 | } 162 | // Scroll to desired message in the middle of the screen 163 | NSLog(@"Scrolling to desired row on narrow, with setting %i and target %@", self.initialScrollSetting, target); 164 | [self.tableView scrollToRowAtIndexPath:target atScrollPosition:UITableViewScrollPositionMiddle animated:NO]; 165 | } 166 | 167 | @end 168 | -------------------------------------------------------------------------------- /Zulip/Controllers/ViewControllers/RightSidebarViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RightSidebarViewController.h 3 | // Zulip 4 | // 5 | // Created by Michael Walker on 12/27/13. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface RightSidebarViewController : UIViewController 12 | 13 | @property (strong, nonatomic) UITableView *tableView; 14 | 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Zulip/Controllers/ZulipAPIController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZulipAPIController.h 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 7/24/13. 6 | // 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import "ZSubscription.h" 13 | #import "NarrowOperators.h" 14 | #import "UnreadManager.h" 15 | 16 | @class StreamViewController; 17 | 18 | /** 19 | This class is the entry point for making API requests to the Zulip API. 20 | 21 | It will add the desired data to Core Data, so to be notified of additions, 22 | use Core Data techniques. 23 | 24 | This class also takes care of long polling and translating long polling 25 | events into Core Data objects. 26 | */ 27 | 28 | // Notification for messages that are received via long polling 29 | extern NSString * const kLongPollMessageNotification; 30 | extern NSString * const kLongPollMessageData; 31 | 32 | // Login and logout notifications 33 | extern NSString * const kLogoutNotification; 34 | extern NSString * const kLoginNotification; 35 | 36 | // Long poll message notifications 37 | // Once we've loaded from the server messages that 38 | // we were push-notified for, this is fired 39 | extern NSString * const kPushNotificationMessagePayloadNotification; 40 | extern NSString * const kPushNotificationMessagePayloadData; 41 | 42 | // Callback with resulting RawMessage* from desired query 43 | typedef void(^MessagesDelivered)(NSArray *messages, BOOL isFinished); 44 | 45 | @interface ZulipAPIController : NSObject 46 | 47 | + (ZulipAPIController *) sharedInstance; 48 | 49 | - (void) login:(NSString *)email password:(NSString *)password result:(void (^) (bool success))result; 50 | - (void) logout; 51 | 52 | - (BOOL) loggedIn; 53 | - (NSString *)domain; 54 | 55 | - (UIColor *)streamColor:(NSString *)name withDefault:(UIColor *)defaultColor; 56 | 57 | // Use this to fetch messagesd from **either** the db or zulip api around a certain anchor 58 | - (void)loadMessagesAroundAnchor:(int)anchor before:(int)before after:(int)after withOperators:(NarrowOperators *)operators completionBlock:(MessagesDelivered)block; 59 | 60 | // Use this to force a network request to fetch messages. They will be inserted in core data if the narrow is a Home View narrow 61 | - (void)loadServerMessagesAroundAnchor:(long)anchor 62 | before:(NSInteger)before 63 | after:(NSInteger)after 64 | withOperators:(NarrowOperators *)narrow 65 | completionBlock:(MessagesDelivered)block; 66 | 67 | - (void)sendMessageFlagsUpdated:(RawMessage *)message withOperation:(NSString *)operation andFlag:(NSString *)flag; 68 | 69 | - (ZUser*)getPersonFromCoreDataWithEmail:(NSString *)email; 70 | 71 | - (void)applicationWillTerminate; 72 | 73 | @property(assign) long pointer; 74 | @property(assign) BOOL backgrounded; 75 | @property(assign) long maxServerMessageId; 76 | @property(nonatomic, retain) NSString *email; 77 | @property(nonatomic, retain) NSString *fullName; 78 | @property(nonatomic, readonly, retain) NSString *realm; 79 | @property(nonatomic, retain) StreamViewController *homeViewController; 80 | @property(nonatomic, retain, readonly) UnreadManager *unreadManager; 81 | @property(nonatomic, retain) NSMutableDictionary *fullNameLookupDict; 82 | 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /Zulip/ErrorViewController.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface ErrorViewController : UIViewController 4 | 5 | @property (strong, nonatomic) IBOutlet UILabel *errorMessage; 6 | 7 | - (IBAction) goBack; 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /Zulip/ErrorViewController.m: -------------------------------------------------------------------------------- 1 | #import "ErrorViewController.h" 2 | 3 | @interface ErrorViewController () 4 | 5 | @end 6 | 7 | @implementation ErrorViewController 8 | 9 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 10 | { 11 | return [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 12 | } 13 | 14 | - (void)viewDidLoad 15 | { 16 | [super viewDidLoad]; 17 | self.errorMessage.lineBreakMode = NSLineBreakByWordWrapping; 18 | self.errorMessage.numberOfLines = 0; 19 | } 20 | 21 | - (void)didReceiveMemoryWarning 22 | { 23 | [super didReceiveMemoryWarning]; 24 | // Dispose of any resources that can be recreated. 25 | } 26 | 27 | - (IBAction)goBack 28 | { 29 | [self.view removeFromSuperview]; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Zulip/ErrorViewController~iphone.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 26 | 27 | 28 | 29 | 30 | 37 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Zulip/GoogleOAuthManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // GoogleOAuthManager.h 3 | // Zulip 4 | // 5 | // Created by Michael Walker on 1/31/14. 6 | // 7 | // 8 | 9 | #import 10 | 11 | typedef void (^GoogleOAuthSuccessBlock)(NSDictionary *result); 12 | typedef void (^GoogleOAuthFailureBlock)(NSError *error); 13 | 14 | @interface GoogleOAuthManager : NSObject 15 | 16 | - (UIViewController *)showAuthScreenWithSuccess:(GoogleOAuthSuccessBlock)success 17 | failure:(GoogleOAuthFailureBlock)failure; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Zulip/GoogleOAuthManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // GoogleOAuthManager.m 3 | // Zulip 4 | // 5 | // Created by Michael Walker on 1/31/14. 6 | // 7 | // 8 | 9 | #import "GoogleOAuthManager.h" 10 | 11 | #import "BrowserViewController.h" 12 | #import "AFHTTPClient.h" 13 | 14 | static NSString * const GoogleOAuthURLRoot = @"https://accounts.google.com/o/oauth2"; 15 | static NSString * const GoogleOAuthClientId = @"835904834568-gs3ncqe5d182tsh2brcv37hfc4vvdk07.apps.googleusercontent.com"; 16 | static NSString * const GoogleOAuthClientSecret = @"RVLTUT3UQrjJsYGjl-pha9bb"; 17 | static NSString * const GoogleOAuthAudience = @"835904834568-77mtr5mtmpgspj9b051del9i9r5t4g4n.apps.googleusercontent.com"; 18 | static NSString * const GoogleOAuthRedirectURI = @"http://localhost"; 19 | static NSString * const GoogleOAuthScope = @"email"; 20 | 21 | @interface GoogleOAuthManager () 22 | @property (copy, nonatomic) GoogleOAuthSuccessBlock success; 23 | @property (copy, nonatomic) GoogleOAuthFailureBlock failure; 24 | 25 | @end 26 | 27 | @implementation GoogleOAuthManager 28 | 29 | - (UIViewController *)showAuthScreenWithSuccess:(GoogleOAuthSuccessBlock)success failure:(GoogleOAuthFailureBlock)failure { 30 | self.success = success; 31 | self.failure = failure; 32 | 33 | NSString *urlString = [NSString stringWithFormat:@"%@/auth?scope=%@&redirect_uri=%@&response_type=code&client_id=%@", GoogleOAuthURLRoot, GoogleOAuthScope, GoogleOAuthRedirectURI, GoogleOAuthClientId]; 34 | NSURL *url = [[NSURL alloc] initWithString:urlString]; 35 | 36 | BrowserViewController *browser = [[BrowserViewController alloc] initWithUrls:url]; 37 | browser.delegate = self; 38 | 39 | return browser; 40 | } 41 | 42 | - (BOOL)openURL:(NSURL *)url { 43 | if ([url.host isEqualToString:@"localhost"]) { 44 | NSArray *queryPairs = [url.query componentsSeparatedByString:@"&"]; 45 | NSMutableDictionary *queryArgs = [NSMutableDictionary new]; 46 | for (NSString *pair in queryPairs) { 47 | NSArray *components = [pair componentsSeparatedByString:@"="]; 48 | queryArgs[components[0]] = components[1]; 49 | } 50 | 51 | if (queryArgs[@"code"]) { 52 | [self fetchTokenForCode:queryArgs[@"code"]]; 53 | } else { 54 | self.failure(nil); 55 | } 56 | return NO; 57 | } 58 | return YES; 59 | } 60 | 61 | - (void)fetchTokenForCode:(NSString *)code { 62 | NSDictionary *params = @{@"code": code, 63 | @"client_id": GoogleOAuthClientId, 64 | @"client_secret": GoogleOAuthClientSecret, 65 | @"redirect_uri": GoogleOAuthRedirectURI, 66 | @"grant_type": @"authorization_code", 67 | @"audience": GoogleOAuthAudience, 68 | @"aud": GoogleOAuthAudience}; 69 | AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:GoogleOAuthURLRoot]]; 70 | [client postPath:@"token" parameters:params success:^(AFHTTPRequestOperation *operation, NSData *responseObject) { 71 | NSError *jsonError; 72 | NSDictionary *result = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingAllowFragments error:&jsonError]; 73 | if (!jsonError && result[@"id_token"]) { 74 | self.success(result); 75 | } else { 76 | self.failure(jsonError); 77 | } 78 | 79 | } failure:^(AFHTTPRequestOperation *operation, NSError *error) { 80 | self.failure(error); 81 | }]; 82 | } 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /Zulip/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "29x29", 5 | "idiom" : "iphone", 6 | "filename" : "zulip-blue-29.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "29x29", 11 | "idiom" : "iphone", 12 | "filename" : "zulip-blue-29@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "idiom" : "iphone", 17 | "size" : "29x29", 18 | "scale" : "3x" 19 | }, 20 | { 21 | "size" : "40x40", 22 | "idiom" : "iphone", 23 | "filename" : "zulip-blue-40@2x.png", 24 | "scale" : "2x" 25 | }, 26 | { 27 | "idiom" : "iphone", 28 | "size" : "40x40", 29 | "scale" : "3x" 30 | }, 31 | { 32 | "size" : "57x57", 33 | "idiom" : "iphone", 34 | "filename" : "zulip-blue-57.png", 35 | "scale" : "1x" 36 | }, 37 | { 38 | "size" : "57x57", 39 | "idiom" : "iphone", 40 | "filename" : "zulip-blue-57@2x.png", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "size" : "60x60", 45 | "idiom" : "iphone", 46 | "filename" : "zulip-blue-60@2x.png", 47 | "scale" : "2x" 48 | }, 49 | { 50 | "size" : "60x60", 51 | "idiom" : "iphone", 52 | "filename" : "zulip-blue-60@3x.png", 53 | "scale" : "3x" 54 | }, 55 | { 56 | "size" : "29x29", 57 | "idiom" : "ipad", 58 | "filename" : "zulip-blue-29-1.png", 59 | "scale" : "1x" 60 | }, 61 | { 62 | "size" : "29x29", 63 | "idiom" : "ipad", 64 | "filename" : "zulip-blue-29@2x-1.png", 65 | "scale" : "2x" 66 | }, 67 | { 68 | "size" : "40x40", 69 | "idiom" : "ipad", 70 | "filename" : "zulip-blue-40.png", 71 | "scale" : "1x" 72 | }, 73 | { 74 | "size" : "40x40", 75 | "idiom" : "ipad", 76 | "filename" : "zulip-blue-40@2x-1.png", 77 | "scale" : "2x" 78 | }, 79 | { 80 | "size" : "50x50", 81 | "idiom" : "ipad", 82 | "filename" : "zulip-blue-50.png", 83 | "scale" : "1x" 84 | }, 85 | { 86 | "size" : "50x50", 87 | "idiom" : "ipad", 88 | "filename" : "zulip-blue-57@2x-1.png", 89 | "scale" : "2x" 90 | }, 91 | { 92 | "size" : "72x72", 93 | "idiom" : "ipad", 94 | "filename" : "zulip-blue-72.png", 95 | "scale" : "1x" 96 | }, 97 | { 98 | "size" : "72x72", 99 | "idiom" : "ipad", 100 | "filename" : "zulip-blue-57@2x-2.png", 101 | "scale" : "2x" 102 | }, 103 | { 104 | "size" : "76x76", 105 | "idiom" : "ipad", 106 | "filename" : "zulip-blue-76.png", 107 | "scale" : "1x" 108 | }, 109 | { 110 | "size" : "76x76", 111 | "idiom" : "ipad", 112 | "filename" : "zulip-blue-76@2x.png", 113 | "scale" : "2x" 114 | }, 115 | { 116 | "size" : "512x512", 117 | "idiom" : "mac", 118 | "filename" : "zulip-blue-512.png", 119 | "scale" : "1x" 120 | }, 121 | { 122 | "size" : "512x512", 123 | "idiom" : "mac", 124 | "filename" : "zulip-blue-512@2x.png", 125 | "scale" : "2x" 126 | } 127 | ], 128 | "info" : { 129 | "version" : 1, 130 | "author" : "xcode" 131 | } 132 | } -------------------------------------------------------------------------------- /Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-29-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-29-1.png -------------------------------------------------------------------------------- /Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-29.png -------------------------------------------------------------------------------- /Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-29@2x-1.png -------------------------------------------------------------------------------- /Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-29@2x.png -------------------------------------------------------------------------------- /Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-40.png -------------------------------------------------------------------------------- /Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-40@2x-1.png -------------------------------------------------------------------------------- /Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-40@2x.png -------------------------------------------------------------------------------- /Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-50.png -------------------------------------------------------------------------------- /Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-512.png -------------------------------------------------------------------------------- /Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-512@2x.png -------------------------------------------------------------------------------- /Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-57.png -------------------------------------------------------------------------------- /Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-57@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-57@2x-1.png -------------------------------------------------------------------------------- /Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-57@2x-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-57@2x-2.png -------------------------------------------------------------------------------- /Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-57@2x.png -------------------------------------------------------------------------------- /Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-60@2x.png -------------------------------------------------------------------------------- /Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-60@3x.png -------------------------------------------------------------------------------- /Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-72.png -------------------------------------------------------------------------------- /Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-76.png -------------------------------------------------------------------------------- /Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/Zulip/Images.xcassets/AppIcon.appiconset/zulip-blue-76@2x.png -------------------------------------------------------------------------------- /Zulip/KeychainItemWrapper.h: -------------------------------------------------------------------------------- 1 | /* 2 | File: KeychainItemWrapper.h 3 | Abstract: 4 | Objective-C wrapper for accessing a single keychain item. 5 | 6 | Version: 1.2 7 | 8 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 9 | Inc. ("Apple") in consideration of your agreement to the following 10 | terms, and your use, installation, modification or redistribution of 11 | this Apple software constitutes acceptance of these terms. If you do 12 | not agree with these terms, please do not use, install, modify or 13 | redistribute this Apple software. 14 | 15 | In consideration of your agreement to abide by the following terms, and 16 | subject to these terms, Apple grants you a personal, non-exclusive 17 | license, under Apple's copyrights in this original Apple software (the 18 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 19 | Software, with or without modifications, in source and/or binary forms; 20 | provided that if you redistribute the Apple Software in its entirety and 21 | without modifications, you must retain this notice and the following 22 | text and disclaimers in all such redistributions of the Apple Software. 23 | Neither the name, trademarks, service marks or logos of Apple Inc. may 24 | be used to endorse or promote products derived from the Apple Software 25 | without specific prior written permission from Apple. Except as 26 | expressly stated in this notice, no other rights or licenses, express or 27 | implied, are granted by Apple herein, including but not limited to any 28 | patent rights that may be infringed by your derivative works or by other 29 | works in which the Apple Software may be incorporated. 30 | 31 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 32 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 33 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 34 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 35 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 36 | 37 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 38 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 39 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 40 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 41 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 42 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 43 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 44 | POSSIBILITY OF SUCH DAMAGE. 45 | 46 | Copyright (C) 2010 Apple Inc. All Rights Reserved. 47 | 48 | */ 49 | 50 | #import 51 | 52 | /* 53 | The KeychainItemWrapper class is an abstraction layer for the iPhone Keychain communication. It is merely a 54 | simple wrapper to provide a distinct barrier between all the idiosyncracies involved with the Keychain 55 | CF/NS container objects. 56 | */ 57 | @interface KeychainItemWrapper : NSObject 58 | { 59 | NSMutableDictionary *keychainItemData; // The actual keychain item data backing store. 60 | NSMutableDictionary *genericPasswordQuery; // A placeholder for the generic keychain item query used to locate the item. 61 | } 62 | 63 | @property (nonatomic, retain) NSMutableDictionary *keychainItemData; 64 | @property (nonatomic, retain) NSMutableDictionary *genericPasswordQuery; 65 | 66 | // Designated initializer. 67 | - (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup; 68 | - (void)setObject:(id)inObject forKey:(id)key; 69 | - (id)objectForKey:(id)key; 70 | 71 | // Initializes and resets the default generic keychain item data. 72 | - (void)resetKeychainItem; 73 | 74 | @end 75 | -------------------------------------------------------------------------------- /Zulip/LoginViewController.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface LoginViewController : UIViewController 4 | 5 | @property (strong, nonatomic) IBOutlet UITextField *email; 6 | @property (strong, nonatomic) IBOutlet UITextField *password; 7 | @property (strong, nonatomic) IBOutlet UIButton *loginButton; 8 | 9 | @property (nonatomic, retain) NSMutableArray *entryFields; 10 | 11 | - (IBAction) login: (id) sender; 12 | - (IBAction) about: (id)sender; 13 | @end 14 | -------------------------------------------------------------------------------- /Zulip/LoginViewController.m: -------------------------------------------------------------------------------- 1 | #import "LoginViewController.h" 2 | #import "ZulipAppDelegate.h" 3 | #import "ZulipAPIController.h" 4 | #import "ZulipAPIClient.h" 5 | #import "GoogleOAuthManager.h" 6 | 7 | #import "BrowserViewController.h" 8 | #import "UIView+Layout.h" 9 | #import "MBProgressHUD.h" 10 | 11 | @interface LoginViewController () 12 | @property (weak, nonatomic) IBOutlet UIScrollView *scrollView; 13 | @property (strong, nonatomic) ZulipAppDelegate *appDelegate; 14 | @property (strong, nonatomic) GoogleOAuthManager *googleManager; 15 | @end 16 | 17 | @implementation LoginViewController 18 | 19 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 20 | { 21 | if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { 22 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; 23 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; 24 | } 25 | return self; 26 | } 27 | 28 | - (void)viewDidLoad 29 | { 30 | [super viewDidLoad]; 31 | [self.navigationController setNavigationBarHidden:YES animated:YES]; 32 | 33 | self.password.secureTextEntry = YES; 34 | self.appDelegate = (ZulipAppDelegate *)[[UIApplication sharedApplication] delegate]; 35 | 36 | self.entryFields = [[NSMutableArray alloc] init]; 37 | NSInteger tag = 1; 38 | UIView *aView; 39 | while ((aView = [self.view viewWithTag:tag])) { 40 | if (aView && [[aView class] isSubclassOfClass:[UIResponder class]]) { 41 | [self.entryFields addObject:aView]; 42 | } 43 | tag++; 44 | } 45 | 46 | // Focus on email field. 47 | [self.email becomeFirstResponder]; 48 | } 49 | 50 | - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { 51 | self.scrollView.frame = self.view.bounds; 52 | } 53 | 54 | - (void)viewWillDisappear:(BOOL)animated 55 | { 56 | [super viewWillDisappear:animated]; 57 | [self.navigationController setNavigationBarHidden:NO animated:YES]; 58 | } 59 | 60 | - (void)didReceiveMemoryWarning 61 | { 62 | [super didReceiveMemoryWarning]; 63 | } 64 | 65 | #pragma mark - Event handlers 66 | - (IBAction)didTapGoogleButton:(id)sender { 67 | self.googleManager = [[GoogleOAuthManager alloc] init]; 68 | UIViewController *loginController = [self.googleManager showAuthScreenWithSuccess:^(NSDictionary *result) { 69 | [self.navigationController popViewControllerAnimated:NO]; 70 | [self loginWithUsername:@"google-oauth2-token" password:result[@"id_token"]]; 71 | } failure:^(NSError *error) { 72 | [self.navigationController popViewControllerAnimated:YES]; 73 | [self.appDelegate showErrorScreen:@"Unable to login with Google. Please try again."]; 74 | }]; 75 | 76 | [self.email resignFirstResponder]; 77 | [self.password resignFirstResponder]; 78 | [self.navigationController pushViewController:loginController animated:YES]; 79 | } 80 | 81 | - (void)loginWithUsername:(NSString *)email password:(NSString *)password { 82 | [[ZulipAPIController sharedInstance] logout]; 83 | [[ZulipAPIController sharedInstance] login:email password:password result:^(bool loggedIn) { 84 | if (loggedIn) { 85 | [self.appDelegate dismissLoginScreen]; 86 | } else { 87 | NSLog(@"Failed to login!"); 88 | [self.email resignFirstResponder]; 89 | [self.password resignFirstResponder]; 90 | [self.appDelegate showErrorScreen:@"Unable to login. Please try again."]; 91 | } 92 | }]; 93 | } 94 | 95 | - (IBAction) login: (id) sender { 96 | NSString *trimmedEmail = [self.email.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 97 | [self loginWithUsername:trimmedEmail password:self.password.text]; 98 | } 99 | 100 | - (IBAction) about:(id)sender 101 | { 102 | [self.appDelegate showAboutScreen]; 103 | } 104 | 105 | - (BOOL)textFieldShouldReturn:(UITextField *)textField { 106 | // Find the next entry field 107 | for (UIView *view in self.entryFields) { 108 | if (view.tag == (textField.tag + 1)) { 109 | [view becomeFirstResponder]; 110 | break; 111 | } 112 | } 113 | 114 | if (textField == self.password) { 115 | [textField resignFirstResponder]; 116 | [self login:nil]; 117 | } 118 | 119 | return NO; 120 | } 121 | 122 | #pragma mark - Keyboard functions 123 | - (void)keyboardWillShow:(NSNotification *)notification { 124 | NSDictionary *userInfo = notification.userInfo; 125 | CGRect keyboardFrame = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; 126 | CGRect keyboardFrameForTextField = [self.view convertRect:keyboardFrame fromView:nil]; 127 | 128 | NSLog(@"%f, %f", keyboardFrameForTextField.size.height, self.view.height); 129 | self.scrollView.contentSize = CGSizeMake(self.view.width, self.view.height); 130 | [self.scrollView resizeTo:CGSizeMake(self.view.width, self.view.height - keyboardFrameForTextField.size.height)]; 131 | } 132 | 133 | - (void)keyboardWillHide:(NSNotification *)notification { 134 | [self.scrollView resizeTo:self.view.size]; 135 | } 136 | 137 | @end 138 | -------------------------------------------------------------------------------- /Zulip/MessageCell.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "DTAttributedTextContentView.h" 4 | #import "DTCoreText.h" 5 | 6 | #import "RawMessage.h" 7 | 8 | @protocol MessageCellDelegate 9 | 10 | @optional 11 | - (void)openLink:(NSURL *)URL; 12 | 13 | @end 14 | 15 | 16 | @interface MessageCell : UITableViewCell 17 | 18 | - (void)willBeDisplayedWithPreviousMessage:(RawMessage *)previousMessage; 19 | 20 | + (NSString *)reuseIdentifier; 21 | + (CGFloat)heightForCellWithMessage:(RawMessage *)message 22 | previousMessage:(RawMessage *)previousMessage; 23 | 24 | @property (strong, nonatomic) IBOutlet UILabel *header; 25 | @property (strong, nonatomic) IBOutlet UILabel *headerBar; 26 | @property (strong, nonatomic) IBOutlet UILabel *sender; 27 | @property (strong, nonatomic) IBOutlet UILabel *timestamp; 28 | @property (strong, nonatomic) IBOutlet UIImageView *gravatar; 29 | @property (strong, nonatomic) IBOutlet DTAttributedTextContentView *attributedTextView; 30 | 31 | @property (strong, nonatomic) NSString *type; 32 | @property (strong, nonatomic) NSString *recipient; 33 | 34 | @property (strong, nonatomic) RawMessage *message; 35 | 36 | @property (nonatomic, weak) IBOutlet id delegate; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /Zulip/Models/RangePair.h: -------------------------------------------------------------------------------- 1 | // 2 | // RangePair.h 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 8/6/13. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface RangePair : NSObject 12 | 13 | @property NSUInteger left; 14 | @property NSUInteger right; 15 | 16 | - (id) initWithStart:(NSUInteger)theStart andEnd:(NSUInteger)theEnd; 17 | - (id) initForComparisonWith:(NSUInteger)messageID; 18 | 19 | + (void) extendRanges:(NSMutableArray *)rangePairs withRange:(RangePair*)newRange; 20 | + (RangePair*) getCurrentRangeOf:(NSUInteger)messageID inRangePairs:(NSArray *)rangePairs; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Zulip/Models/RangePair.m: -------------------------------------------------------------------------------- 1 | // 2 | // RangePair.m 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 8/6/13. 6 | // 7 | // 8 | 9 | #import "RangePair.h" 10 | 11 | @implementation RangePair 12 | 13 | - (id) initWithStart:(NSUInteger)theStart andEnd:(NSUInteger)theEnd 14 | { 15 | if (self = [super init]) 16 | { 17 | self.left = theStart; 18 | self.right = theEnd; 19 | if (self.left >= self.right) { 20 | NSLog(@"Invalid range given to RangePair: [%i, %i]", (int)self.left, (int)self.right); 21 | NSAssert2(self.left < self.right, @"invalid range given: [%i, %i]", (int)self.left, (int)self.right); 22 | } 23 | 24 | } 25 | return self; 26 | } 27 | 28 | - (id) initForComparisonWith:(NSUInteger)messageID 29 | { 30 | // omit checking for valid range 31 | if (self = [super init]) { 32 | self.left = messageID; 33 | self.right = messageID; 34 | } 35 | return self; 36 | } 37 | 38 | - (id) initWithCoder:(NSCoder *)aDecoder 39 | { 40 | self = [super init]; 41 | 42 | if (self) { 43 | self.left = [[aDecoder decodeObjectForKey:@"left"] unsignedIntegerValue]; 44 | self.right = [[aDecoder decodeObjectForKey:@"right"] unsignedIntegerValue]; 45 | } 46 | 47 | return self; 48 | } 49 | 50 | + (void) extendRanges:(NSMutableArray *)rangePairs withRange:(RangePair*)newRange; 51 | { 52 | 53 | // binary search for new.left in rangePairs.rights. Save that part of the array as "head" 54 | NSUInteger indexOfRangePairHead = [rangePairs indexOfObject:newRange inSortedRange:(NSRange){0, [rangePairs count]} options:NSBinarySearchingInsertionIndex|NSBinarySearchingFirstEqual usingComparator:^(RangePair * left, RangePair * right) { 55 | if (left.right < right.left) { 56 | return NSOrderedAscending; 57 | } 58 | if (left.left > right.right) { 59 | return NSOrderedDescending; 60 | } 61 | return NSOrderedSame; 62 | }]; 63 | 64 | // binary search for new.right in rangePairs.lefts. Save that part of the array as "tail" 65 | NSUInteger indexOfRangePairTail = [rangePairs indexOfObject:newRange inSortedRange:(NSRange){0, [rangePairs count]} options:NSBinarySearchingInsertionIndex|NSBinarySearchingLastEqual usingComparator:^(RangePair *left, RangePair *right) { 66 | if (left.right < right.left) { 67 | return NSOrderedAscending; 68 | } 69 | if (left.left > right.right) { 70 | return NSOrderedDescending; 71 | } 72 | return NSOrderedSame; 73 | }]; 74 | 75 | // We now partition the rangePairs like so: 76 | // [0..indexHead-1|indexHead..indexTail-1|indexTail..last] 77 | NSArray* head = [rangePairs subarrayWithRange:NSMakeRange(0, indexOfRangePairHead)]; 78 | NSArray* middle = [rangePairs subarrayWithRange:NSMakeRange(indexOfRangePairHead, indexOfRangePairTail-indexOfRangePairHead)]; 79 | NSArray* tail = [rangePairs subarrayWithRange:NSMakeRange(indexOfRangePairTail, [rangePairs count]-indexOfRangePairTail)]; 80 | 81 | [rangePairs removeAllObjects]; 82 | 83 | if(indexOfRangePairTail != indexOfRangePairHead) { 84 | for (NSUInteger i=0; i<[middle count]; ++i) { 85 | newRange.left = MIN([middle[i] left], [newRange left]); 86 | newRange.right = MAX([middle[i] right], [newRange right]); 87 | } 88 | } 89 | 90 | [rangePairs addObjectsFromArray:head]; 91 | [rangePairs addObject:newRange]; 92 | [rangePairs addObjectsFromArray:tail]; 93 | } 94 | 95 | 96 | + (RangePair*) getCurrentRangeOf:(NSUInteger)messageID inRangePairs:(NSArray *)rangePairs 97 | { 98 | RangePair* messageRange = [[RangePair alloc] initForComparisonWith:messageID]; 99 | NSUInteger indexOfPossibleCurrentRange = [rangePairs indexOfObject:messageRange inSortedRange:(NSRange){0, [rangePairs count]} options:NSBinarySearchingInsertionIndex|NSBinarySearchingFirstEqual usingComparator:^(RangePair * left, RangePair * right) { 100 | if (left.right < right.left) { 101 | return NSOrderedAscending; 102 | } 103 | if (left.left > right.right) { 104 | return NSOrderedDescending; 105 | } 106 | return NSOrderedSame; 107 | }]; 108 | if (indexOfPossibleCurrentRange == [rangePairs count]) { 109 | return nil; 110 | } 111 | 112 | RangePair* possibleCurrentRange = [rangePairs objectAtIndex:indexOfPossibleCurrentRange]; 113 | if (messageID >= possibleCurrentRange.left && messageID <= possibleCurrentRange.right) { 114 | return possibleCurrentRange; 115 | } 116 | 117 | return nil; 118 | } 119 | 120 | - (NSString *)description 121 | { 122 | return [NSString stringWithFormat:@"", (int)self.left, (int)self.right]; 123 | } 124 | 125 | - (void)encodeWithCoder:(NSCoder *)coder 126 | { 127 | [coder encodeObject:[NSNumber numberWithUnsignedInteger:self.left] forKey:@"left"]; 128 | [coder encodeObject:[NSNumber numberWithUnsignedInteger:self.right] forKey:@"right"]; 129 | } 130 | 131 | 132 | @end 133 | -------------------------------------------------------------------------------- /Zulip/Models/RawMessage.h: -------------------------------------------------------------------------------- 1 | // 2 | // RawMessage.h 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 7/31/13. 6 | // 7 | // 8 | 9 | #import 10 | 11 | #import "ZMessage.h" 12 | #import "ZUser.h" 13 | #import "ZSubscription.h" 14 | 15 | typedef void(^RawMessageChangeHandler)(RawMessage *rawMsg); 16 | 17 | /** 18 | This is a "non-NSManagedObject" ZMessage, for use in the message list. 19 | 20 | We can't use ZMessage objects directly in the message list, as we only want to save 21 | messages to Core Data in some cases (when loading into the Home View) and we can't 22 | generate NSManagedObject subclasses **without** loading them into core data 23 | 24 | Any changes made to this raw message need to be explicitly saved back to 25 | Core Data---use the - (void)save method for that. 26 | */ 27 | @interface RawMessage : NSObject 28 | 29 | @property (nonatomic, retain) NSString * content; 30 | @property (nonatomic, retain) NSString * avatar_url; 31 | @property (nonatomic, retain) NSNumber * messageID; 32 | @property (nonatomic, retain) NSString * stream_recipient; 33 | @property (nonatomic, retain) NSString * subject; 34 | @property (nonatomic, retain) NSDate * timestamp; 35 | @property (nonatomic, retain) NSString * type; 36 | @property (nonatomic, retain) NSMutableSet *pm_recipients; 37 | @property (nonatomic, retain) ZUser *sender; 38 | @property (nonatomic, retain) ZSubscription *subscription; 39 | @property (nonatomic, retain) NSSet *messageFlags; 40 | @property (nonatomic, retain) NSAttributedString *attributedString; 41 | 42 | @property (nonatomic, assign) BOOL munged; 43 | 44 | // TODO (is this really needed?) 45 | - (void)save; 46 | 47 | // Flag specific 48 | @property (nonatomic) BOOL read; 49 | @property (nonatomic) BOOL starred; 50 | 51 | - (void)addMessageFlag:(NSString *)flag; 52 | - (void)removeMessageFlag:(NSString *)flag; 53 | 54 | // Update handler 55 | - (void)registerForChanges:(RawMessageChangeHandler)handler; 56 | 57 | // Set to YES when updating from Zulip API, disables writeback 58 | // of changes 59 | @property (nonatomic, assign) BOOL disableUpdates; 60 | 61 | + (RawMessage *)allocFromZMessage:(ZMessage *)message; 62 | @property (nonatomic, weak) ZMessage *linkedZMessage; 63 | 64 | - (BOOL)isSameTopicAsMessage:(RawMessage *)otherMessage; 65 | - (BOOL)isSameSenderAsMessage:(RawMessage *)otherMessage; 66 | @end 67 | -------------------------------------------------------------------------------- /Zulip/Models/RawMessage.m: -------------------------------------------------------------------------------- 1 | // 2 | // RawMessage.m 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 7/31/13. 6 | // 7 | // 8 | 9 | #import "RawMessage.h" 10 | 11 | #import "ZulipAppDelegate.h" 12 | #import "ZulipAPIController.h" 13 | #import "UnreadManager.h" 14 | 15 | static NSString * const MessageFlagRead = @"read"; 16 | static NSString * const MessageFlagStarred = @"starred"; 17 | 18 | @interface RawMessage () 19 | 20 | @property (nonatomic, retain) NSMutableSet *changeHandlers; 21 | @end 22 | 23 | @implementation RawMessage 24 | 25 | - (id)init 26 | { 27 | self = [super init]; 28 | 29 | if (self) { 30 | self.changeHandlers = [[NSMutableSet alloc] init]; 31 | self.pm_recipients = [[NSMutableSet alloc] init]; 32 | self.linkedZMessage = nil; 33 | self.messageFlags = [[NSSet alloc] init]; 34 | self.disableUpdates = NO; 35 | } 36 | 37 | return self; 38 | } 39 | 40 | - (BOOL)read 41 | { 42 | return [self.messageFlags containsObject:MessageFlagRead]; 43 | } 44 | 45 | - (void)setRead:(BOOL)read 46 | { 47 | if ([self read] == read) { 48 | return; 49 | } 50 | 51 | if (read) { 52 | [self addMessageFlag:MessageFlagRead]; 53 | } else { 54 | [self removeMessageFlag:MessageFlagRead]; 55 | } 56 | 57 | if (read) { 58 | [[[ZulipAPIController sharedInstance] unreadManager] markMessageRead:self]; 59 | } 60 | } 61 | 62 | - (BOOL)starred { 63 | return [self.messageFlags containsObject:MessageFlagStarred]; 64 | } 65 | 66 | - (void)setStarred:(BOOL)starred 67 | { 68 | if (starred) { 69 | [self addMessageFlag:MessageFlagStarred]; 70 | } else { 71 | [self removeMessageFlag:MessageFlagStarred]; 72 | } 73 | } 74 | 75 | - (void)addMessageFlag:(NSString *)flag 76 | { 77 | NSMutableSet *newFlags = [[NSMutableSet alloc] initWithSet:self.messageFlags]; 78 | [newFlags addObject:flag]; 79 | 80 | // Save back to the server if there is a change 81 | if (![newFlags isEqualToSet:self.messageFlags] && !self.disableUpdates) { 82 | [[ZulipAPIController sharedInstance] sendMessageFlagsUpdated:self withOperation:@"add" andFlag:flag]; 83 | } 84 | 85 | self.messageFlags = newFlags; 86 | } 87 | 88 | - (void)removeMessageFlag:(NSString *)flag 89 | { 90 | NSMutableSet *newFlags = [[NSMutableSet alloc] initWithSet:self.messageFlags]; 91 | [newFlags removeObject:flag]; 92 | 93 | // Save back to the server if there is a change 94 | if (![newFlags isEqualToSet:self.messageFlags] && !self.disableUpdates) { 95 | [[ZulipAPIController sharedInstance] sendMessageFlagsUpdated:self withOperation:@"remove" andFlag:flag]; 96 | } 97 | 98 | self.messageFlags = newFlags; 99 | } 100 | 101 | - (void)registerForChanges:(RawMessageChangeHandler)handler 102 | { 103 | [self.changeHandlers addObject:[handler copy]]; 104 | } 105 | 106 | - (void)setMessageFlags:(NSSet *)messageFlags 107 | { 108 | if ([_messageFlags isEqualToSet:messageFlags]) { 109 | return; 110 | } 111 | 112 | _messageFlags = messageFlags; 113 | 114 | [self notifyOfChanges]; 115 | 116 | // Save to Core Data if necessary 117 | if (self.linkedZMessage) { 118 | if (![self.messageFlags isEqualToSet:self.linkedZMessage.messageFlags]) { 119 | [self.linkedZMessage setMessageFlags:self.messageFlags]; 120 | ZulipAppDelegate *appDelegate = (ZulipAppDelegate *)[[UIApplication sharedApplication] delegate]; 121 | 122 | NSError *error = nil; 123 | [[appDelegate managedObjectContext] save:&error]; 124 | if (error) { 125 | NSLog(@"Error saving flags from RawMessage to ZMessage: %@ %@", [error localizedDescription], [error userInfo]); 126 | } 127 | } 128 | } 129 | } 130 | 131 | - (void)notifyOfChanges 132 | { 133 | for (RawMessageChangeHandler handler in self.changeHandlers) { 134 | handler(self); 135 | } 136 | } 137 | 138 | + (RawMessage *)allocFromZMessage:(ZMessage *)message 139 | { 140 | RawMessage *raw = [[RawMessage alloc] init]; 141 | 142 | NSArray *string_props = @[@"content", @"avatar_url", @"stream_recipient", @"subject", @"type"]; 143 | for (NSString *prop in string_props) { 144 | [raw setValue:[message valueForKey:prop] forKey:prop]; 145 | } 146 | raw.messageID = message.messageID; 147 | raw.timestamp = message.timestamp; 148 | raw.pm_recipients = [NSMutableSet setWithSet:message.pm_recipients]; 149 | raw.sender = message.sender; 150 | 151 | raw.messageFlags = [message messageFlags]; 152 | 153 | raw.linkedZMessage = message; 154 | 155 | return raw; 156 | } 157 | 158 | - (void)save 159 | { 160 | // TODO 161 | } 162 | 163 | - (NSString *)description 164 | { 165 | return [NSString stringWithFormat:@"", self.sender.full_name, [self.messageID longValue]]; 166 | } 167 | 168 | - (BOOL)isSameTopicAsMessage:(RawMessage *)otherMessage { 169 | if ([self.type isEqualToString:@"stream"]) { 170 | return [self.stream_recipient isEqualToString:otherMessage.stream_recipient] && [self.subject isEqualToString:otherMessage.subject]; 171 | } else if ([self.type isEqualToString:@"private"]) { 172 | return [self.pm_recipients isEqualToSet:otherMessage.pm_recipients]; 173 | } 174 | 175 | return NO; 176 | 177 | } 178 | 179 | - (BOOL)isSameSenderAsMessage:(RawMessage *)otherMessage { 180 | return [self.sender isEqual:otherMessage.sender]; 181 | } 182 | 183 | 184 | @end 185 | -------------------------------------------------------------------------------- /Zulip/Models/ZMessage.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZMessage.h 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 8/2/13. 6 | // 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @class ZSubscription, ZUser, RawMessage; 13 | 14 | @interface ZMessage : NSManagedObject 15 | 16 | @property (nonatomic, retain) NSString * avatar_url; 17 | @property (nonatomic, retain) NSString * content; 18 | @property (nonatomic, retain) NSNumber * messageID; 19 | @property (nonatomic, retain) NSString * stream_recipient; 20 | @property (nonatomic, retain) NSString * subject; 21 | @property (nonatomic, retain) NSDate * timestamp; 22 | @property (nonatomic, retain) NSString * type; 23 | @property (nonatomic, retain) NSData * flagData; 24 | @property (nonatomic, retain) NSSet *pm_recipients; 25 | @property (nonatomic, retain) ZUser *sender; 26 | @property (nonatomic, retain) ZSubscription *subscription; 27 | 28 | // NOTE: These two methods below have been manually added, 29 | // when regenerating this file make sure to keep them! 30 | // Do not use these directly. Use RawMessage objects 31 | // to manipulate messages 32 | - (NSSet *)messageFlags; 33 | - (void)setMessageFlags:(NSSet *)flags; 34 | 35 | // NOTE added manually, retain when regenerating! 36 | @property (nonatomic, retain) RawMessage *linkedRawMessage; 37 | 38 | @end 39 | 40 | @interface ZMessage (CoreDataGeneratedAccessors) 41 | 42 | - (void)addPm_recipientsObject:(ZUser *)value; 43 | - (void)removePm_recipientsObject:(ZUser *)value; 44 | - (void)addPm_recipients:(NSSet *)values; 45 | - (void)removePm_recipients:(NSSet *)values; 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /Zulip/Models/ZMessage.m: -------------------------------------------------------------------------------- 1 | // 2 | // ZMessage.m 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 8/2/13. 6 | // 7 | // 8 | 9 | #import "ZMessage.h" 10 | #import "ZSubscription.h" 11 | #import "ZUser.h" 12 | 13 | 14 | @implementation ZMessage 15 | 16 | @dynamic avatar_url; 17 | @dynamic content; 18 | @dynamic messageID; 19 | @dynamic stream_recipient; 20 | @dynamic subject; 21 | @dynamic timestamp; 22 | @dynamic type; 23 | @dynamic flagData; 24 | @dynamic pm_recipients; 25 | @dynamic sender; 26 | @dynamic subscription; 27 | 28 | // MANUALLY ADDED BELOW THIS LINE 29 | @synthesize linkedRawMessage; 30 | 31 | - (NSSet *)messageFlags 32 | { 33 | if (!self.flagData) { 34 | return [[NSSet alloc] init]; 35 | } 36 | 37 | return [NSKeyedUnarchiver unarchiveObjectWithData:self.flagData]; 38 | } 39 | 40 | - (void)setMessageFlags:(NSSet *)flags 41 | { 42 | NSData *data = [NSKeyedArchiver archivedDataWithRootObject:flags]; 43 | self.flagData = data; 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /Zulip/Models/ZSubscription.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZSubscription.h 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 7/25/13. 6 | // 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @class ZMessage, ZUser; 13 | 14 | @interface ZSubscription : NSManagedObject 15 | 16 | @property (nonatomic, retain) NSString * color; 17 | @property (nonatomic, retain) NSNumber * in_home_view; 18 | @property (nonatomic, retain) NSNumber * invite_only; 19 | @property (nonatomic, retain) NSString * name; 20 | @property (nonatomic, retain) NSNumber * notifications; 21 | @property (nonatomic, retain) ZUser *subscribers; 22 | @property (nonatomic, retain) NSSet *messages; 23 | @end 24 | 25 | @interface ZSubscription (CoreDataGeneratedAccessors) 26 | 27 | - (id)initWithDictionary:(NSDictionary *)dict; 28 | 29 | - (void)addMessagesObject:(ZMessage *)value; 30 | - (void)removeMessagesObject:(ZMessage *)value; 31 | - (void)addMessages:(NSSet *)values; 32 | - (void)removeMessages:(NSSet *)values; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Zulip/Models/ZSubscription.m: -------------------------------------------------------------------------------- 1 | // 2 | // ZSubscription.m 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 7/25/13. 6 | // 7 | // 8 | 9 | #import "ZSubscription.h" 10 | #import "ZMessage.h" 11 | #import "ZUser.h" 12 | #import "ZulipAppDelegate.h" 13 | 14 | @implementation ZSubscription 15 | 16 | @dynamic color; 17 | @dynamic in_home_view; 18 | @dynamic invite_only; 19 | @dynamic name; 20 | @dynamic notifications; 21 | @dynamic subscribers; 22 | @dynamic messages; 23 | 24 | - (id)initWithDictionary:(NSDictionary *)dict { 25 | ZulipAppDelegate *appDelegate = (ZulipAppDelegate *)[[UIApplication sharedApplication] delegate]; 26 | self = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass(self.class) inManagedObjectContext:appDelegate.managedObjectContext]; 27 | 28 | if (self) { 29 | self.color = dict[@"color"]; 30 | self.in_home_view = dict[@"in_home_view"]; 31 | self.invite_only = dict[@"invite_only"]; 32 | self.name = dict[@"name"]; 33 | self.notifications = dict[@"notifications"]; 34 | } 35 | 36 | return self; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /Zulip/Models/ZUser.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZUser.h 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 7/29/13. 6 | // 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @class ZMessage, ZSubscription; 13 | 14 | @interface ZUser : NSManagedObject 15 | 16 | @property (nonatomic, retain) NSString * avatar_url; 17 | @property (nonatomic, retain) NSString * email; 18 | @property (nonatomic, retain) NSString * full_name; 19 | @property (nonatomic, retain) NSNumber * userID; 20 | @property (nonatomic, retain) NSSet *received_pms; 21 | @property (nonatomic, retain) NSSet *sent_messages; 22 | @property (nonatomic, retain) NSSet *subscriptions; 23 | @end 24 | 25 | @interface ZUser (CoreDataGeneratedAccessors) 26 | 27 | - (void)addReceived_pmsObject:(ZMessage *)value; 28 | - (void)removeReceived_pmsObject:(ZMessage *)value; 29 | - (void)addReceived_pms:(NSSet *)values; 30 | - (void)removeReceived_pms:(NSSet *)values; 31 | 32 | - (void)addSent_messagesObject:(ZMessage *)value; 33 | - (void)removeSent_messagesObject:(ZMessage *)value; 34 | - (void)addSent_messages:(NSSet *)values; 35 | - (void)removeSent_messages:(NSSet *)values; 36 | 37 | - (void)addSubscriptionsObject:(ZSubscription *)value; 38 | - (void)removeSubscriptionsObject:(ZSubscription *)value; 39 | - (void)addSubscriptions:(NSSet *)values; 40 | - (void)removeSubscriptions:(NSSet *)values; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /Zulip/Models/ZUser.m: -------------------------------------------------------------------------------- 1 | // 2 | // ZUser.m 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 7/29/13. 6 | // 7 | // 8 | 9 | #import "ZUser.h" 10 | #import "ZMessage.h" 11 | #import "ZSubscription.h" 12 | 13 | 14 | @implementation ZUser 15 | 16 | @dynamic avatar_url; 17 | @dynamic email; 18 | @dynamic full_name; 19 | @dynamic userID; 20 | @dynamic received_pms; 21 | @dynamic sent_messages; 22 | @dynamic subscriptions; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Zulip/Models/ZUserPresence.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZUserPresence.h 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 12/31/13. 6 | // 7 | // 8 | 9 | #import 10 | #import 11 | 12 | extern NSString *const ZUserPresenceStatusActive; 13 | extern NSString *const ZUserPresenceStatusIdle; 14 | extern NSString *const ZUserPresenceStatusOffline; 15 | extern NSString *const ZUserPresenceStatusIgnore; 16 | 17 | @class ZUser; 18 | 19 | @interface ZUserPresence : NSManagedObject 20 | 21 | @property (readonly) NSString * currentStatus; 22 | @property (nonatomic, retain) NSString * status; 23 | @property (nonatomic, retain) NSNumber * timestamp; 24 | @property (nonatomic, retain) NSString * client; 25 | @property (nonatomic, retain) ZUser *user; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Zulip/Models/ZUserPresence.m: -------------------------------------------------------------------------------- 1 | // 2 | // ZUserPresence.m 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 12/31/13. 6 | // 7 | // 8 | 9 | #import "ZUserPresence.h" 10 | #import "ZUser.h" 11 | 12 | static const NSTimeInterval ZUserPresenceTimeout = 140; // In seconds 13 | 14 | NSString *const ZUserPresenceStatusActive = @"active"; 15 | NSString *const ZUserPresenceStatusIdle = @"idle"; 16 | NSString *const ZUserPresenceStatusOffline = @"offline"; 17 | NSString *const ZUserPresenceStatusIgnore = @"ignore"; 18 | 19 | static NSString *const ZUserPresenceClientWebsite = @"website"; 20 | 21 | @implementation ZUserPresence 22 | 23 | @dynamic status; 24 | @dynamic timestamp; 25 | @dynamic client; 26 | @dynamic user; 27 | 28 | - (NSString *)currentStatus { 29 | if (![self.client isEqualToString:ZUserPresenceClientWebsite]) { 30 | return ZUserPresenceStatusIgnore; 31 | } else if (self.isCurrent) { 32 | return self.status; 33 | } else { 34 | return ZUserPresenceStatusOffline; 35 | } 36 | } 37 | 38 | - (BOOL)isCurrent { 39 | NSDate *timestamp = [NSDate dateWithTimeIntervalSince1970:[self.timestamp doubleValue]]; 40 | return [[NSDate date] timeIntervalSinceDate:timestamp] < ZUserPresenceTimeout; 41 | } 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /Zulip/Models/Zulip.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | Zulip2.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /Zulip/Models/Zulip.xcdatamodeld/Zulip 1.0.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Zulip/Models/Zulip.xcdatamodeld/Zulip.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Zulip/Models/Zulip.xcdatamodeld/Zulip2.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Zulip/NSString+Encode.h: -------------------------------------------------------------------------------- 1 | // NSString+Encode.h 2 | 3 | @interface NSString (encode) 4 | - (NSString *)encodeString:(NSStringEncoding)encoding; 5 | @end 6 | -------------------------------------------------------------------------------- /Zulip/NSString+Encode.m: -------------------------------------------------------------------------------- 1 | // NSString+Encode.m 2 | 3 | @implementation NSString (encode) 4 | - (NSString *)encodeString:(NSStringEncoding)encoding 5 | { 6 | return [(NSString *) CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)self, 7 | NULL, (CFStringRef)@";/?:@&=$+{}<>,", 8 | CFStringConvertNSStringEncodingToEncoding(encoding)) 9 | autorelease]; 10 | } 11 | @end 12 | -------------------------------------------------------------------------------- /Zulip/NarrowOperators.h: -------------------------------------------------------------------------------- 1 | // 2 | // NarrowOperators.h 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 8/1/13. 6 | // 7 | // 8 | 9 | #import 10 | #import "RawMessage.h" 11 | 12 | /** 13 | This represents a bunch of narrow operators, and can be converted into either an 14 | NSCompoundPredicate* for Core Data lookups or a Zulip-compatible JSON narrow blob 15 | */ 16 | @interface NarrowOperators : NSObject 17 | 18 | + (NarrowOperators *) operatorsFromMessage:(RawMessage *)msg; 19 | 20 | @property (assign, nonatomic) BOOL isServerOnly; 21 | 22 | - (void)setInHomeView; 23 | - (void)setPrivateMessages; 24 | - (void)setMentions; 25 | - (void)setStarred; 26 | - (void)searchFor:(NSString *)query; 27 | - (void)addStreamNarrow:(NSString *)streamName; 28 | - (void)addUserNarrow:(NSString *)email; 29 | 30 | - (BOOL)isHomeView; 31 | - (BOOL)isPrivateMessages; 32 | 33 | - (NSString *)title; 34 | 35 | - (NSPredicate *)allocAsPredicate; 36 | - (NSString *)allocAsJSONPayload; 37 | - (BOOL)acceptsMessage:(RawMessage *)msg; 38 | 39 | - (void)clear; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /Zulip/PreferencesWrapper.h: -------------------------------------------------------------------------------- 1 | // 2 | // PreferencesWrapper.h 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 8/9/13. 6 | // 7 | // 8 | 9 | #import 10 | 11 | /** 12 | Wraps NSUserDefaults to be user-specific, similar to how we have 13 | specific sqlite files for each user/domain. Since NSUserDefaults 14 | doesn't have any built-in sandboxing, we fake it by prepending each 15 | key 16 | */ 17 | @interface PreferencesWrapper : NSObject 18 | 19 | + (PreferencesWrapper *)sharedInstance; 20 | 21 | - (void)removeKey:(NSString *)key; 22 | 23 | - (void)setPointer:(long)pointer; 24 | - (long)pointer; 25 | 26 | - (void)setPersistentQueue:(NSDictionary *)queue forName:(NSString *)queueName; 27 | - (NSDictionary *)persistentQueueWithName:(NSString *)name; 28 | 29 | - (void)setFullName:(NSString *)name; 30 | - (NSString *)fullName; 31 | 32 | @property (nonatomic, retain) NSString *domain; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Zulip/PreferencesWrapper.m: -------------------------------------------------------------------------------- 1 | // 2 | // PreferencesWrapper.m 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 8/9/13. 6 | // 7 | // 8 | 9 | #import "PreferencesWrapper.h" 10 | 11 | #import "ZulipAPIController.h" 12 | 13 | @implementation PreferencesWrapper 14 | 15 | 16 | static dispatch_once_t *onceTokenPointer; 17 | 18 | - (id)init 19 | { 20 | self = [super init]; 21 | if (self) { 22 | self.domain = @""; 23 | } 24 | return self; 25 | } 26 | 27 | - (void)removeKey:(NSString *)key 28 | { 29 | [[NSUserDefaults standardUserDefaults] removeObjectForKey:[self keyWithPrefix:key]]; 30 | [[NSUserDefaults standardUserDefaults] synchronize]; 31 | } 32 | 33 | - (NSString *)fullName 34 | { 35 | return [[NSUserDefaults standardUserDefaults] stringForKey:[self keyWithPrefix:@"fullName"]]; 36 | } 37 | 38 | - (void)setFullName:(NSString *)fullName 39 | { 40 | [[NSUserDefaults standardUserDefaults] setObject:fullName forKey:[self keyWithPrefix:@"fullName"]]; 41 | [[NSUserDefaults standardUserDefaults] synchronize]; 42 | } 43 | 44 | - (long)pointer 45 | { 46 | return [[[NSUserDefaults standardUserDefaults] objectForKey:[self keyWithPrefix:@"pointer"]] longValue]; 47 | } 48 | 49 | - (void)setPointer:(long)pointer 50 | { 51 | [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithLong:pointer] forKey:[self keyWithPrefix:@"pointer"]]; 52 | [[NSUserDefaults standardUserDefaults] synchronize]; 53 | } 54 | 55 | - (NSDictionary *)persistentQueueWithName:(NSString *)name 56 | { 57 | return [[NSUserDefaults standardUserDefaults] objectForKey:[self keyWithPrefix:name]]; 58 | } 59 | 60 | - (void)setPersistentQueue:(NSDictionary *)queue forName:(NSString *)queueName 61 | { 62 | [[NSUserDefaults standardUserDefaults] setObject:queue forKey:[self keyWithPrefix:queueName]]; 63 | [[NSUserDefaults standardUserDefaults] synchronize]; 64 | } 65 | 66 | - (NSString *)keyWithPrefix:(NSString *)key 67 | { 68 | return [NSString stringWithFormat:@"%@-%@", self.domain, key]; 69 | } 70 | 71 | // Singleton 72 | + (PreferencesWrapper *)sharedInstance { 73 | static PreferencesWrapper *_sharedInstance = nil; 74 | static dispatch_once_t onceToken; 75 | onceTokenPointer = &onceToken; 76 | dispatch_once(&onceToken, ^{ 77 | _sharedInstance = [[PreferencesWrapper alloc] init]; 78 | }); 79 | 80 | return _sharedInstance; 81 | } 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /Zulip/PresenceManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // PresenceManager.h 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 12/31/13. 6 | // 7 | // 8 | 9 | #import 10 | 11 | // PresenceManager updates the Zulip server periodially when in the foreground, with presence information 12 | // It also receives and handles the realm's presence information and inserts it into Core Data 13 | @interface PresenceManager : NSObject 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Zulip/Protocols/MessageComposing.h: -------------------------------------------------------------------------------- 1 | // 2 | // MessageComposing.h 3 | // Zulip 4 | // 5 | // Created by Michael Walker on 1/21/14. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @protocol MessageComposing 12 | 13 | - (void)showComposeViewForUser:(ZUser *)user; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Zulip/RenderedMarkdownMunger.h: -------------------------------------------------------------------------------- 1 | // 2 | // RenderedMarkdownMunger.h 3 | // Zulip 4 | // 5 | // Created by Humbug on 8/8/13. 6 | // 7 | // 8 | 9 | #import 10 | #import "RawMessage.h" 11 | 12 | @interface RenderedMarkdownMunger : NSObject 13 | 14 | + (void)mungeThis:(RawMessage*)message; 15 | + (NSString *)emojiShortNameFromUnicode:(NSString *)unicode; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Zulip/SidebarUserCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // SidebarUserCell.h 3 | // Zulip 4 | // 5 | // Created by Michael Walker on 12/29/13. 6 | // 7 | // 8 | 9 | @class ZUser; 10 | 11 | #import 12 | 13 | @interface SidebarUserCell : UITableViewCell 14 | 15 | @property (weak, nonatomic) IBOutlet UIView *statusIcon; 16 | @property (weak, nonatomic) IBOutlet UILabel *name; 17 | @property (weak, nonatomic) IBOutlet UILabel *unreadCount; 18 | 19 | @property (strong, nonatomic) ZUser *user; 20 | @property (strong, nonatomic) NSString *status; 21 | 22 | - (void)calculateUnreadCount; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Zulip/SidebarUserCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // SidebarUserCell.m 3 | // Zulip 4 | // 5 | // Created by Michael Walker on 12/29/13. 6 | // 7 | // 8 | 9 | #import "SidebarUserCell.h" 10 | #import "ZUser.h" 11 | #import "ZulipAPIController.h" 12 | #import "ZUserPresence.h" 13 | #import "UIColor+HexColor.h" 14 | 15 | @implementation SidebarUserCell 16 | 17 | - (void)layoutSubviews { 18 | self.statusIcon.layer.cornerRadius = CGRectGetWidth(self.statusIcon.frame)/2; 19 | } 20 | 21 | - (void)setUser:(ZUser *)user { 22 | _user = user; 23 | self.name.text = user.full_name; 24 | [self calculateUnreadCount]; 25 | } 26 | 27 | - (void)calculateUnreadCount { 28 | NSDictionary *unread_counts = [[[ZulipAPIController sharedInstance] unreadManager] unreadCounts]; 29 | 30 | NSUInteger unread = [unread_counts[self.user.email] integerValue]; 31 | [self setCount:unread]; 32 | } 33 | 34 | - (void)setStatus:(NSString *)status { 35 | self.statusIcon.layer.borderWidth = 0; 36 | 37 | if ([status isEqualToString:ZUserPresenceStatusActive]) { 38 | self.statusIcon.backgroundColor = [UIColor colorWithHexString:@"#44c21d" defaultColor:UIColor.clearColor]; 39 | } else if ([status isEqualToString:ZUserPresenceStatusIdle]) { 40 | self.statusIcon.backgroundColor = [UIColor colorWithHexString:@"#ec7e18" defaultColor:UIColor.clearColor]; 41 | } else { // ZUserPresenceStatusOffline 42 | self.statusIcon.backgroundColor = UIColor.clearColor; 43 | self.statusIcon.layer.borderWidth = 1.f; 44 | self.statusIcon.layer.borderColor = UIColor.grayColor.CGColor; 45 | } 46 | } 47 | 48 | - (void)setCount:(NSInteger)count 49 | { 50 | if (count > 0) { 51 | self.unreadCount.text = [NSString stringWithFormat:@"%i", (int)count]; 52 | } else { 53 | self.unreadCount.text = @""; 54 | } 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /Zulip/StreamComposeView.h: -------------------------------------------------------------------------------- 1 | // 2 | // StreamComposeView.h 3 | // Zulip 4 | // 5 | // Created by Michael Walker on 1/3/14. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @class RawMessage; 12 | @class ComposeAutocompleteView; 13 | @class ZUser; 14 | 15 | @protocol StreamComposeViewDelegate 16 | - (void)willShowComposeView; 17 | @end 18 | 19 | @interface StreamComposeView : UIView 20 | 21 | @property (readonly) CGFloat visibleHeight; 22 | 23 | @property (weak, nonatomic) id delegate; 24 | 25 | @property (strong, nonatomic) NSString *defaultRecipient; 26 | @property (assign, nonatomic) BOOL isPrivate; 27 | @property (strong, nonatomic) ComposeAutocompleteView *autocompleteView; 28 | 29 | - (id)initWithAutocompleteView:(ComposeAutocompleteView *)autocompleteView; 30 | 31 | - (void)showComposeViewForMessage:(RawMessage *)message; 32 | - (void)showComposeViewForUser:(ZUser *)message; 33 | 34 | // Shows the compose view as if the user tapped on the compose box 35 | - (void)showOneTimePrivateCompose; 36 | 37 | - (void)showSubjectBar; 38 | - (void)hideSubjectBar; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Zulip/StreamViewController.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "MessageCell.h" 3 | #import "NarrowOperators.h" 4 | #import "MessageComposing.h" 5 | #import "StreamComposeView.h" 6 | 7 | @class ComposeAutocompleteView; 8 | @class ZUser; 9 | 10 | //@protocol StreamViewControllerDelegate 11 | 12 | //- (NSPredicate *)filterPrediate; 13 | //- () 14 | 15 | //@end 16 | 17 | @interface StreamViewController : UIViewController 18 | 19 | @property (nonatomic, retain) NSMutableArray *messages; 20 | @property (nonatomic, retain) NSMutableSet *msgIds; 21 | @property (nonatomic, retain) NarrowOperators *operators; 22 | 23 | @property (strong, nonatomic) UITableView *tableView; 24 | @property (strong, nonatomic) UIRefreshControl *refreshControl; 25 | 26 | @property (strong, nonatomic) StreamComposeView *composeView; 27 | @property (strong, nonatomic) ComposeAutocompleteView *autocompleteView; 28 | 29 | // Generic message list implementations 30 | - (int)rowWithId:(int)messageId; 31 | 32 | - (void)initialPopulate; 33 | - (void)resumePopulate; 34 | - (void)clearMessages; 35 | 36 | - (void)loadMessages:(NSArray *)messages; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /Zulip/StreamViewController.xib: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /Zulip/UIColor+HexColor.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 Micah Hainline 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #import 18 | 19 | @interface UIColor (HexColor) 20 | 21 | + (UIColor *) colorWithHexString: (NSString *) hexString defaultColor:(UIColor *)defaultColor; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /Zulip/UIColor+HexColor.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 Micah Hainline 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #import "UIColor+HexColor.h" 18 | 19 | @implementation UIColor (HexColor) 20 | 21 | + (UIColor *) colorWithHexString: (NSString *) hexString defaultColor: (UIColor *)defaultColor { 22 | NSString *colorString = [[hexString stringByReplacingOccurrencesOfString: @"#" withString: @""] uppercaseString]; 23 | CGFloat alpha, red, blue, green; 24 | switch ([colorString length]) { 25 | case 3: // #RGB 26 | alpha = 1.0f; 27 | red = [self colorComponentFrom: colorString start: 0 length: 1]; 28 | green = [self colorComponentFrom: colorString start: 1 length: 1]; 29 | blue = [self colorComponentFrom: colorString start: 2 length: 1]; 30 | break; 31 | case 4: // #ARGB 32 | alpha = [self colorComponentFrom: colorString start: 0 length: 1]; 33 | red = [self colorComponentFrom: colorString start: 1 length: 1]; 34 | green = [self colorComponentFrom: colorString start: 2 length: 1]; 35 | blue = [self colorComponentFrom: colorString start: 3 length: 1]; 36 | break; 37 | case 6: // #RRGGBB 38 | alpha = 1.0f; 39 | red = [self colorComponentFrom: colorString start: 0 length: 2]; 40 | green = [self colorComponentFrom: colorString start: 2 length: 2]; 41 | blue = [self colorComponentFrom: colorString start: 4 length: 2]; 42 | break; 43 | case 8: // #AARRGGBB 44 | alpha = [self colorComponentFrom: colorString start: 0 length: 2]; 45 | red = [self colorComponentFrom: colorString start: 2 length: 2]; 46 | green = [self colorComponentFrom: colorString start: 4 length: 2]; 47 | blue = [self colorComponentFrom: colorString start: 6 length: 2]; 48 | break; 49 | default: 50 | NSLog(@"Invalid color value format: Color value %@ is invalid. It should be a hex value of the form #RBG, #ARGB, #RRGGBB, or #AARRGGBB", hexString); 51 | return defaultColor; 52 | } 53 | return [UIColor colorWithRed: red green: green blue: blue alpha: alpha]; 54 | } 55 | 56 | + (CGFloat) colorComponentFrom: (NSString *) string start: (NSUInteger) start length: (NSUInteger) length { 57 | NSString *substring = [string substringWithRange: NSMakeRange(start, length)]; 58 | NSString *fullHex = length == 2 ? substring : [NSString stringWithFormat: @"%@%@", substring, substring]; 59 | unsigned hexComponent; 60 | [[NSScanner scannerWithString: fullHex] scanHexInt: &hexComponent]; 61 | return hexComponent / 255.0f; 62 | } 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /Zulip/UIView+Layout.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 Pivotal Labs (www.pivotallabs.com) Contact email: adam@pivotallabs.com 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #import 22 | 23 | typedef enum { 24 | ViewCornerTopLeft, 25 | ViewCornerBottomLeft, 26 | ViewCornerTopRight, 27 | ViewCornerBottomRight 28 | } ViewCorner; 29 | 30 | @interface UIView (Layout) 31 | 32 | - (CGFloat) top; 33 | - (CGFloat) bottom; 34 | - (CGFloat) left; 35 | - (CGFloat) right; 36 | 37 | - (CGFloat) width; 38 | - (CGFloat) height; 39 | 40 | - (CGSize) size; 41 | - (CGPoint) origin; 42 | 43 | - (CGFloat)bottomOfSuperview; 44 | - (CGFloat)rightOfSuperview; 45 | 46 | //------------------------- 47 | // PivotalCoreKit code here 48 | //------------------------- 49 | 50 | // Intended for the idiom view.center = superview.centerBounds; 51 | - (CGPoint)centerBounds; 52 | 53 | // Move/resize as separate operations, working off of all four corners 54 | - (void)moveCorner:(ViewCorner)corner toPoint:(CGPoint)point; 55 | - (void)moveToPoint:(CGPoint)point; 56 | - (void)moveBy:(CGPoint)pointDelta; 57 | - (void)resizeTo:(CGSize)size keepingCorner:(ViewCorner)corner; 58 | - (void)resizeTo:(CGSize)size; 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /Zulip/UIView+Layout.m: -------------------------------------------------------------------------------- 1 | #import "UIView+Layout.h" 2 | 3 | @implementation UIView (Layout) 4 | 5 | #pragma mark - Accessors 6 | - (CGFloat)top { 7 | return self.frame.origin.y; 8 | } 9 | 10 | - (CGFloat)bottom { 11 | return self.frame.origin.y + self.frame.size.height; 12 | } 13 | 14 | - (CGFloat)left { 15 | return self.frame.origin.x; 16 | } 17 | 18 | - (CGFloat)right { 19 | return self.frame.origin.x + self.frame.size.width; 20 | } 21 | 22 | - (CGFloat)width { 23 | return self.frame.size.width; 24 | } 25 | 26 | - (CGFloat)height { 27 | return self.frame.size.height; 28 | } 29 | 30 | - (CGSize)size { 31 | return self.frame.size; 32 | } 33 | 34 | - (CGPoint)origin { 35 | return self.frame.origin; 36 | } 37 | 38 | - (CGFloat)bottomOfSuperview { 39 | return self.superview.bottom - self.frame.size.height; 40 | } 41 | 42 | - (CGFloat)rightOfSuperview { 43 | return self.superview.right - self.frame.size.width; 44 | } 45 | 46 | # pragma mark - PivotalCoreKit 47 | - (CGPoint)centerBounds { 48 | return CGPointMake(self.bounds.size.width / 2.f, self.bounds.size.height / 2.f); 49 | } 50 | 51 | - (void)moveCorner:(ViewCorner)corner toPoint:(CGPoint)point { 52 | CGRect frame = self.frame; 53 | switch (corner) { 54 | case ViewCornerTopLeft: 55 | frame.origin = point; 56 | break; 57 | case ViewCornerBottomLeft: 58 | frame.origin = CGPointMake(point.x, point.y - frame.size.height); 59 | break; 60 | case ViewCornerBottomRight: 61 | frame.origin = CGPointMake(point.x - frame.size.width, point.y - frame.size.height); 62 | break; 63 | case ViewCornerTopRight: 64 | frame.origin = CGPointMake(point.x - frame.size.width, point.y); 65 | break; 66 | } 67 | self.frame = frame; 68 | } 69 | 70 | - (void)moveToPoint:(CGPoint)point { 71 | [self moveCorner:ViewCornerTopLeft toPoint:point]; 72 | } 73 | 74 | - (void)moveBy:(CGPoint)pointDelta { 75 | CGRect frame = self.frame; 76 | frame.origin = CGPointMake(frame.origin.x + pointDelta.x, 77 | frame.origin.y + pointDelta.y); 78 | self.frame = frame; 79 | } 80 | 81 | - (void)resizeTo:(CGSize)size keepingCorner:(ViewCorner)corner { 82 | CGRect frame = self.frame; 83 | self.frame = CGRectMake(frame.origin.x, frame.origin.y, size.width, size.height); 84 | switch (corner) { 85 | case ViewCornerTopLeft: 86 | break; 87 | case ViewCornerBottomLeft: 88 | [self moveCorner:corner toPoint:CGPointMake(CGRectGetMinX(frame), CGRectGetMaxY(frame))]; 89 | break; 90 | case ViewCornerBottomRight: 91 | [self moveCorner:corner toPoint:CGPointMake(CGRectGetMaxX(frame), CGRectGetMaxY(frame))]; 92 | break; 93 | case ViewCornerTopRight: 94 | [self moveCorner:corner toPoint:CGPointMake(CGRectGetMaxX(frame), CGRectGetMinY(frame))]; 95 | break; 96 | } 97 | } 98 | 99 | - (void)resizeTo:(CGSize)size { 100 | [self resizeTo:size keepingCorner:ViewCornerTopLeft]; 101 | } 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /Zulip/Views/ComposeAutocompleteView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ComposeAutocompleteView.h 3 | // Zulip 4 | // 5 | // Created by Michael Walker on 1/21/14. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @class StreamViewController; 12 | 13 | typedef NS_ENUM(NSUInteger, ComposeAutocompleteType) { 14 | ComposeAutocompleteTypeUser, 15 | ComposeAutocompleteTypeStream, 16 | ComposeAutocompleteTypeTopic 17 | }; 18 | 19 | @interface ComposeAutocompleteView : UITableView 20 | 21 | @property (weak, nonatomic) UIView *messageBody; 22 | @property (weak, nonatomic) StreamViewController *messageDelegate; 23 | 24 | - (void)registerTextField:(UITextField *)textField 25 | forType:(ComposeAutocompleteType)type; 26 | - (void)resetRegisteredTextFields; 27 | @end 28 | -------------------------------------------------------------------------------- /Zulip/Views/SidebarSectionHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // SidebarStreamsHeader.h 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 8/2/13. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface SidebarSectionHeader : UIViewController 12 | @property (nonatomic, retain) IBOutlet UILabel *label; 13 | 14 | @property (nonatomic, strong) NSString *sectionTitle; 15 | 16 | - (instancetype)initWithTitle:(NSString *)title; 17 | @end 18 | -------------------------------------------------------------------------------- /Zulip/Views/SidebarSectionHeader.m: -------------------------------------------------------------------------------- 1 | // 2 | // SidebarSectionHeader.m 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 8/2/13. 6 | // 7 | // 8 | 9 | #import "SidebarSectionHeader.h" 10 | 11 | #import 12 | 13 | @implementation SidebarSectionHeader 14 | 15 | - (instancetype)initWithTitle:(NSString *)title { 16 | if (self = [self init]) { 17 | self.sectionTitle = title; 18 | } 19 | 20 | return self; 21 | } 22 | 23 | - (id)init 24 | { 25 | self = [super initWithNibName:@"SidebarSectionHeader" bundle:[NSBundle mainBundle]]; 26 | return self; 27 | } 28 | 29 | - (void)viewDidLoad 30 | { 31 | [super viewDidLoad]; 32 | // Draw a nice bottom border for the section header 33 | UIView *bottomBorder = [[UIView alloc] initWithFrame:CGRectMake(0, CGRectGetHeight(self.view.bounds) - 2.0f, CGRectGetWidth(self.view.bounds), 0.5f)]; 34 | bottomBorder.backgroundColor = [UIColor colorWithRed:(200.0f/255.0f) green:(200.0f/255.0f) blue:(200.0f/255.0f) alpha:1.0f]; 35 | 36 | bottomBorder.layer.shadowColor = [UIColor darkGrayColor].CGColor; 37 | bottomBorder.layer.shadowOffset = CGSizeMake(0.0f, 1.0f); 38 | bottomBorder.layer.shadowOpacity = 0.1f; 39 | 40 | [self.view addSubview:bottomBorder]; 41 | 42 | // On iOS 7 we keep the background translucent 43 | if ([[[UIDevice currentDevice] systemVersion] compare:@"7.0" options:NSNumericSearch] != NSOrderedAscending) { 44 | self.view.backgroundColor = [UIColor clearColor]; 45 | } 46 | 47 | self.label.text = self.sectionTitle; 48 | } 49 | 50 | - (void)setSectionTitle:(NSString *)sectionTitle { 51 | _sectionTitle = sectionTitle; 52 | self.label.text = _sectionTitle; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /Zulip/Views/SidebarStreamCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // SidebarStreamCell.h 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 7/30/13. 6 | // 7 | // 8 | 9 | #import 10 | #import "ZSubscription.h" 11 | #import "NarrowOperators.h" 12 | 13 | typedef enum { 14 | HOME, 15 | PRIVATE_MESSAGES, 16 | STREAM, 17 | STARRED, 18 | AT_MENTIONS 19 | } SIDEBAR_SHORTCUTS; 20 | 21 | @interface SidebarStreamCell : UITableViewCell 22 | 23 | @property (nonatomic, retain) IBOutlet UILabel *name; 24 | @property (nonatomic, retain) IBOutlet UILabel *unread; 25 | @property (strong, nonatomic) IBOutlet UIImageView *gravatar; 26 | 27 | @property (nonatomic, assign) SIDEBAR_SHORTCUTS shortcut; 28 | @property (nonatomic, retain) ZSubscription *stream; 29 | @property (nonatomic, retain, readonly) NarrowOperators *narrow; 30 | 31 | - (void)setBackgroundIfCurrent; 32 | - (void)setUnreadCount:(NSDictionary *)unreadCounts; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Zulip/Views/SidebarStreamCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // SidebarStreamCell.m 3 | // Zulip 4 | // 5 | // Created by Leonardo Franchi on 7/30/13. 6 | // 7 | // 8 | 9 | #import "SidebarStreamCell.h" 10 | #import "ZulipAppDelegate.h" 11 | #import "ZulipAPIController.h" 12 | #import "UnreadManager.h" 13 | 14 | #import "UIColor+HexColor.h" 15 | #import "UIView+Layout.h" 16 | #import 17 | 18 | @interface SidebarStreamCell () 19 | @end 20 | 21 | @implementation SidebarStreamCell 22 | 23 | - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 24 | { 25 | self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; 26 | if (self) { 27 | self.stream = nil; 28 | } 29 | return self; 30 | } 31 | 32 | - (void)setShortcut:(SIDEBAR_SHORTCUTS)shortcut 33 | { 34 | _shortcut = shortcut; 35 | NarrowOperators *op = [[NarrowOperators alloc] init]; 36 | 37 | switch (shortcut) { 38 | case HOME: 39 | { 40 | [op setInHomeView]; 41 | FAKFontAwesome *homeIcon = [FAKFontAwesome homeIconWithSize:self.gravatar.height]; 42 | self.gravatar.image = [homeIcon imageWithSize:self.gravatar.size]; 43 | break; 44 | } 45 | case PRIVATE_MESSAGES: 46 | { 47 | [op setPrivateMessages]; 48 | FAKFontAwesome *userIcon = [FAKFontAwesome userIconWithSize:self.gravatar.height]; 49 | self.gravatar.image = [userIcon imageWithSize:self.gravatar.size]; 50 | break; 51 | } 52 | case STARRED: 53 | { 54 | [op setStarred]; 55 | FAKFontAwesome *starIcon = [FAKFontAwesome starIconWithSize:self.gravatar.height]; 56 | self.gravatar.image = [starIcon imageWithSize:self.gravatar.size]; 57 | break; 58 | } 59 | case AT_MENTIONS: 60 | { 61 | [op setMentions]; 62 | FAKFontAwesome *tagIcon = [FAKFontAwesome tagIconWithSize:self.gravatar.height]; 63 | self.gravatar.image = [tagIcon imageWithSize:self.gravatar.size]; 64 | break; 65 | } 66 | default: 67 | break; 68 | } 69 | 70 | self.name.text = [op title]; 71 | 72 | _narrow = op; 73 | 74 | NSDictionary *unread_counts = [[[ZulipAPIController sharedInstance] unreadManager] unreadCounts]; 75 | [self setUnreadCount:unread_counts]; 76 | } 77 | 78 | - (void)setStream:(ZSubscription *)subscription 79 | { 80 | _shortcut = STREAM; 81 | _stream = subscription; 82 | 83 | NarrowOperators *op = [[NarrowOperators alloc] init]; 84 | [op addStreamNarrow:subscription.name]; 85 | _narrow = op; 86 | 87 | CGFloat size = CGRectGetHeight(self.gravatar.bounds); 88 | self.gravatar.image = [self streamColorSwatchWithSize:(int)size andColor:subscription.color]; 89 | 90 | NSDictionary *unread_counts = [[[ZulipAPIController sharedInstance] unreadManager] unreadCounts]; 91 | [self setUnreadCount:unread_counts]; 92 | 93 | self.name.text = [op title]; 94 | [self setBackgroundIfCurrent]; 95 | 96 | if ([self.stream.invite_only boolValue]) { 97 | [self addPrivateIcon]; 98 | } 99 | 100 | if (![self.stream.in_home_view boolValue]) { 101 | self.name.textColor = [UIColor lightGrayColor]; 102 | self.gravatar.alpha = 0.4; 103 | } 104 | } 105 | 106 | - (void)setCount:(int)count 107 | { 108 | if (count > 0) { 109 | self.unread.text = [NSString stringWithFormat:@"%i", count]; 110 | } else { 111 | self.unread.text = @""; 112 | } 113 | } 114 | 115 | - (void)setBackgroundIfCurrent 116 | { 117 | ZulipAppDelegate *delegate = (ZulipAppDelegate *)[[UIApplication sharedApplication] delegate]; 118 | 119 | if ([[delegate currentNarrow] isEqual:self.narrow]) { 120 | // This is the current narrow, highlight it 121 | self.backgroundColor = [UIColor colorWithHexString:@"#CCD6CC" defaultColor:[UIColor grayColor]]; 122 | } else { 123 | self.backgroundColor = [UIColor clearColor]; 124 | } 125 | } 126 | 127 | - (void)setUnreadCount:(NSDictionary *)unreadCounts 128 | { 129 | int count = 0; 130 | if (self.narrow.isHomeView) { 131 | if ([unreadCounts objectForKey:@"home"]) { 132 | count = [[unreadCounts objectForKey:@"home"] intValue]; 133 | } 134 | } else if (self.narrow.isPrivateMessages) { 135 | if ([unreadCounts objectForKey:@"pms"]) { 136 | count = [[unreadCounts objectForKey:@"pms"] intValue]; 137 | } 138 | } else { 139 | if ([unreadCounts objectForKey:@"streams"]) { 140 | NSDictionary *streams = [unreadCounts objectForKey:@"streams"]; 141 | if ([streams objectForKey:self.stream.name]) { 142 | count = [[streams objectForKey:self.stream.name] intValue]; 143 | } 144 | } 145 | } 146 | [self setCount:count]; 147 | } 148 | 149 | #pragma mark - Private 150 | - (void)addPrivateIcon { 151 | FAKFontAwesome *lockIcon = [FAKFontAwesome lockIconWithSize:self.gravatar.height]; 152 | UIImageView *lock = [[UIImageView alloc] initWithImage:[lockIcon imageWithSize:self.gravatar.size]]; 153 | lock.frame = self.gravatar.frame; 154 | [self addSubview:lock]; 155 | } 156 | 157 | #pragma mark - Drawing Methods 158 | 159 | - (UIImage *)streamColorSwatchWithSize:(int)height andColor:(NSString *)colorRGB 160 | { 161 | UIGraphicsBeginImageContextWithOptions(CGSizeMake(height, height), NO, 0.0f); 162 | CGContextRef context = UIGraphicsGetCurrentContext(); 163 | UIGraphicsPushContext(context); 164 | 165 | CGRect bounds = CGRectMake(0, 0, height, height); 166 | CGContextAddEllipseInRect(context, bounds); 167 | CGContextClip(context); 168 | 169 | UIColor *color = [UIColor colorWithHexString:colorRGB defaultColor:[UIColor grayColor]]; 170 | CGContextSetFillColorWithColor(context, [color CGColor]); 171 | CGContextFillRect(context, bounds); 172 | 173 | UIGraphicsPopContext(); 174 | UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); 175 | UIGraphicsEndImageContext(); 176 | 177 | return result; 178 | } 179 | 180 | @end 181 | -------------------------------------------------------------------------------- /Zulip/Views/SidebarStreamCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 28 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Zulip/Views/SidebarUserCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 24 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Zulip/Views/StreamProgressView.h: -------------------------------------------------------------------------------- 1 | // 2 | // StreamProgressView.h 3 | // Zulip 4 | // 5 | // Created by Michael Walker on 2/25/14. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface StreamProgressView : UIView 12 | 13 | + (CGFloat)height; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Zulip/Views/StreamProgressView.m: -------------------------------------------------------------------------------- 1 | // 2 | // StreamProgressView.m 3 | // Zulip 4 | // 5 | // Created by Michael Walker on 2/25/14. 6 | // 7 | // 8 | 9 | #import "StreamProgressView.h" 10 | 11 | @implementation StreamProgressView 12 | 13 | + (CGFloat)height { 14 | return 80.0; 15 | } 16 | 17 | - (id)initWithFrame:(CGRect)frame 18 | { 19 | self = [super initWithFrame:frame]; 20 | if (self) { 21 | self.autoresizingMask = UIViewAutoresizingFlexibleWidth; 22 | 23 | UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; 24 | [self addSubview:spinner]; 25 | spinner.center = self.center; 26 | spinner.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; 27 | spinner.color = [UIColor grayColor]; 28 | [spinner startAnimating]; 29 | } 30 | return self; 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /Zulip/Views/UserCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // UserCell.h 3 | // Zulip 4 | // 5 | // Created by Humbug on 10/21/13. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface UserCell : UITableViewCell 12 | 13 | 14 | // the name if it's available, otherwise the email address 15 | @property (strong, nonatomic) IBOutlet UILabel *title; 16 | // the email address if the name is available, otherwise nothing 17 | @property (strong, nonatomic) IBOutlet UILabel *subtitle; 18 | @property (strong, nonatomic) IBOutlet UIImageView *gravatar; 19 | 20 | 21 | @property (strong, nonatomic) NSString *name; 22 | @property (strong, nonatomic) NSString *email; 23 | @property (strong, nonatomic) NSString *gravatarUrl; 24 | 25 | + (NSString *)reuseIdentifier; 26 | - (void)setUserWithEmail:(NSString *)email; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Zulip/Views/UserCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // UserCell.m 3 | // Zulip 4 | // 5 | // Created by Humbug on 10/21/13. 6 | // 7 | // 8 | 9 | #import "UserCell.h" 10 | #import "UIImageView+AFNetworking.h" 11 | #import "ZulipAPIController.h" 12 | #import "ZulipAPIClient.h" 13 | #import "ZUser.h" 14 | 15 | @implementation UserCell 16 | 17 | - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 18 | { 19 | self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; 20 | if (self) { 21 | // Initialization code 22 | } 23 | return self; 24 | } 25 | 26 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated 27 | { 28 | [super setSelected:selected animated:animated]; 29 | 30 | // Configure the view for the selected state 31 | } 32 | 33 | - (void)setUserWithEmail:(NSString *)email 34 | { 35 | 36 | ZUser *user = [[ZulipAPIController sharedInstance] getPersonFromCoreDataWithEmail:email]; 37 | if (user) 38 | { 39 | self.gravatarUrl = user.avatar_url; 40 | [self.gravatar setImageWithURL:[NSURL URLWithString:self.gravatarUrl]]; 41 | self.title.text = self.name = user.full_name; 42 | self.subtitle.text = self.email = user.email; 43 | } 44 | else 45 | { 46 | self.gravatarUrl = nil; 47 | self.gravatar.image = nil; 48 | self.title.text = self.email = email; 49 | self.subtitle.text = self.name = nil; 50 | } 51 | } 52 | 53 | 54 | + (NSString *)reuseIdentifier { 55 | return @"UserCellIdentifier"; 56 | } 57 | 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /Zulip/Views/UserCellView.xib: -------------------------------------------------------------------------------- 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 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /Zulip/Zulip-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIconFile 12 | 13 | CFBundleIcons 14 | 15 | CFBundleIcons~ipad 16 | 17 | CFBundleIdentifier 18 | $(PRODUCT_BUNDLE_IDENTIFIER) 19 | CFBundleInfoDictionaryVersion 20 | 6.0 21 | CFBundleName 22 | ${PRODUCT_NAME} 23 | CFBundlePackageType 24 | APPL 25 | CFBundleShortVersionString 26 | 2.4.0 27 | CFBundleSignature 28 | ???? 29 | CFBundleVersion 30 | 2.4.0 31 | LSRequiresIPhoneOS 32 | 33 | NSMainNibFile 34 | MainWindow~iphone 35 | NSMainNibFile~ipad 36 | MainWindow~ipad 37 | UIAppFonts 38 | 39 | SourceSansPro-Black-webfont.ttf 40 | SourceSansPro-BlackIt-webfont.ttf 41 | SourceSansPro-Bold-webfont.ttf 42 | SourceSansPro-BoldIt-webfont.ttf 43 | SourceSansPro-ExtraLight-webfont.ttf 44 | SourceSansPro-ExtraLightIt-webfont.ttf 45 | SourceSansPro-It-webfont.ttf 46 | SourceSansPro-Light-webfont.ttf 47 | SourceSansPro-LightIt-webfont.ttf 48 | SourceSansPro-Regular-webfont.ttf 49 | SourceSansPro-Semibold-webfont.ttf 50 | SourceSansPro-SemiboldIt-webfont.ttf 51 | 52 | UILaunchImages~ipad 53 | 54 | 55 | UILaunchImageMinimumOSVersion 56 | 7.0 57 | UILaunchImageName 58 | Default-iOS7-Landscape 59 | UILaunchImageOrientation 60 | Landscape 61 | UILaunchImageSize 62 | {768, 1024} 63 | 64 | 65 | UILaunchImageMinimumOSVersion 66 | 7.0 67 | UILaunchImageName 68 | Default-iOS7-Portrait 69 | UILaunchImageOrientation 70 | Portrait 71 | UILaunchImageSize 72 | {768, 1024} 73 | 74 | 75 | UISupportedInterfaceOrientations 76 | 77 | UIInterfaceOrientationPortrait 78 | UIInterfaceOrientationLandscapeLeft 79 | UIInterfaceOrientationLandscapeRight 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /Zulip/Zulip-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'Zulip' target in the 'Zulip' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_3_0 8 | #warning "This project uses features only available in iPhone SDK 3.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /Zulip/ZulipAPIClient.h: -------------------------------------------------------------------------------- 1 | #import "AFHTTPClient.h" 2 | /* 3 | Main API entry point for calling the Zulip API 4 | */ 5 | @interface ZulipAPIClient : AFHTTPClient 6 | 7 | // Set the credentials before using the ZulipAPIClient via [ZulipAPIClient +sharedClient] 8 | + (void) setEmailForDomain:(NSString *)userEmail; 9 | + (void) setCredentials:(NSString *)userEmail withAPIKey:(NSString *)key; 10 | 11 | + (ZulipAPIClient *) sharedClient; 12 | 13 | - (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest success:(void ( ^ ) ( AFHTTPRequestOperation *operation , id responseObject ))success failure:(void ( ^ ) ( AFHTTPRequestOperation *operation , NSError *error ))failure; 14 | 15 | - (void) logout; 16 | 17 | @property NSURL *apiURL; 18 | @property (nonatomic, retain) NSString *client; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Zulip/ZulipAPIClient.m: -------------------------------------------------------------------------------- 1 | #import "ZulipAPIClient.h" 2 | 3 | #import "AFJSONRequestOperation.h" 4 | 5 | @implementation ZulipAPIClient 6 | 7 | static NSString *email = nil; 8 | static BOOL debug = NO; 9 | 10 | + (void)setCredentials:(NSString *)userEmail withAPIKey:(NSString *)key { 11 | email = userEmail; 12 | 13 | [[ZulipAPIClient sharedClient] setAuthorizationHeaderWithUsername:email password:key]; 14 | 15 | } 16 | 17 | + (void)setEmailForDomain:(NSString *)userEmail 18 | { 19 | email = userEmail; 20 | } 21 | 22 | static dispatch_once_t *onceTokenPointer; 23 | 24 | // Singleton 25 | + (ZulipAPIClient *)sharedClient { 26 | static ZulipAPIClient *_sharedClient = nil; 27 | static dispatch_once_t onceToken; 28 | onceTokenPointer = &onceToken; 29 | dispatch_once(&onceToken, ^{ 30 | NSString *apiURLString; 31 | 32 | if (debug == YES) { 33 | apiURLString = @"http://localhost:9991/api/v1"; 34 | } else { 35 | apiURLString = @"https://api.zulip.com/v1/"; 36 | } 37 | 38 | NSLog(@"Loading URL: %@", apiURLString); 39 | NSURL *apiURL = [NSURL URLWithString:apiURLString]; 40 | _sharedClient = [[ZulipAPIClient alloc] initWithBaseURL:apiURL]; 41 | 42 | NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; 43 | NSString *userAgent = [NSString stringWithFormat:@"ZulipiOS/%@ (%@; %@)", version, [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion]]; 44 | 45 | [_sharedClient setDefaultHeader:@"User-Agent" value:userAgent]; 46 | 47 | _sharedClient.apiURL = apiURL; 48 | }); 49 | 50 | return _sharedClient; 51 | } 52 | 53 | - (void)logout { 54 | *onceTokenPointer = 0; 55 | } 56 | 57 | - (id)initWithBaseURL:(NSURL *)url { 58 | self = [super initWithBaseURL:url]; 59 | if (!self) { 60 | return nil; 61 | } 62 | 63 | self.client = @"ios"; 64 | 65 | [self registerHTTPOperationClass:[AFJSONRequestOperation class]]; 66 | 67 | // Accept HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 68 | [self setDefaultHeader:@"Accept" value:@"application/json"]; 69 | 70 | return self; 71 | } 72 | 73 | #pragma mark - AFHTTPClient reimplementations 74 | 75 | - (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest success:(void ( ^ ) ( AFHTTPRequestOperation *operation , id responseObject ))success failure:(void ( ^ ) ( AFHTTPRequestOperation *operation , NSError *error ))failure { 76 | // Reimplement to print out error messages from JSON content 77 | id my_failure = ^( AFHTTPRequestOperation *operation , NSError *error ) { 78 | if (failure) { 79 | failure(operation, error); 80 | } 81 | 82 | // Log 'msg' key from JSON payload if it exists 83 | if ([operation isKindOfClass:[AFJSONRequestOperation class]]) { 84 | NSDictionary *json = (NSDictionary *)[(AFJSONRequestOperation *)operation responseJSON]; 85 | NSString *errorMsg = [json objectForKey:@"msg"]; 86 | if (errorMsg) { 87 | NSLog(@"Zulip API Error Message: %@", errorMsg); 88 | } 89 | } 90 | }; 91 | 92 | if (debug) 93 | NSLog(@"Sending API request for: %@", [[urlRequest URL] path]); 94 | 95 | return [super HTTPRequestOperationWithRequest:urlRequest success:success failure:my_failure]; 96 | } 97 | 98 | // Send client string with all requests 99 | 100 | - (NSDictionary *)addClientToParams:(NSDictionary *)parameters { 101 | if (!parameters) { 102 | parameters = @{}; 103 | } 104 | NSMutableDictionary *mutatedParams = [NSMutableDictionary dictionaryWithDictionary:parameters]; 105 | [mutatedParams setObject:self.client forKey:@"client"]; 106 | return mutatedParams; 107 | } 108 | 109 | - (void)getPath:(NSString *)path parameters:(NSDictionary *)parameters success:(void ( ^ ) ( AFHTTPRequestOperation *operation , id responseObject ))success failure:(void ( ^ ) ( AFHTTPRequestOperation *operation , NSError *error ))failure 110 | { 111 | return [super getPath:path parameters:[self addClientToParams:parameters] success:success failure:failure]; 112 | } 113 | 114 | - (void)patchPath:(NSString *)path parameters:(NSDictionary *)parameters success:(void ( ^ ) ( AFHTTPRequestOperation *operation , id responseObject ))success failure:(void ( ^ ) ( AFHTTPRequestOperation *operation , NSError *error ))failure 115 | { 116 | return [super patchPath:path parameters:[self addClientToParams:parameters] success:success failure:failure]; 117 | } 118 | 119 | - (void)postPath:(NSString *)path parameters:(NSDictionary *)parameters success:(void ( ^ ) ( AFHTTPRequestOperation *operation , id responseObject ))success failure:(void ( ^ ) ( AFHTTPRequestOperation *operation , NSError *error ))failure 120 | { 121 | return [super postPath:path parameters:[self addClientToParams:parameters] success:success failure:failure]; 122 | } 123 | 124 | - (void)putPath:(NSString *)path parameters:(NSDictionary *)parameters success:(void ( ^ ) ( AFHTTPRequestOperation *operation , id responseObject ))success failure:(void ( ^ ) ( AFHTTPRequestOperation *operation , NSError *error ))failure 125 | { 126 | return [super putPath:path parameters:[self addClientToParams:parameters] success:success failure:failure]; 127 | } 128 | 129 | @end 130 | -------------------------------------------------------------------------------- /Zulip/ZulipAppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LoginViewController.h" 3 | #import "HomeViewController.h" 4 | #import "ErrorViewController.h" 5 | #import "AFHTTPRequestOperation.h" 6 | #import "JASidePanelController.h" 7 | #import "NarrowOperators.h" 8 | 9 | @interface ZulipAppDelegate : NSObject 10 | 11 | @property (nonatomic, retain) UIWindow *window; 12 | @property (nonatomic, retain) UITabBarController *tabBarController; 13 | @property (nonatomic, retain) UINavigationController *navController; 14 | @property (nonatomic, retain) JASidePanelController *sidePanelController; 15 | @property (nonatomic, retain) LoginViewController *loginViewController; 16 | @property (nonatomic, retain) HomeViewController *homeViewController; 17 | @property (nonatomic, retain) ErrorViewController *errorViewController; 18 | 19 | // Push notifications 20 | @property (nonatomic, assign) BOOL wakingFromBackground; 21 | // List of NSNumber * Message IDs that were included in the push notification update 22 | @property (nonatomic, retain) NSArray *notifiedWithMessages; 23 | 24 | // Core Data bits 25 | @property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext; 26 | @property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel; 27 | @property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator; 28 | 29 | - (void) showErrorScreen:(NSString *)errorMessage; 30 | - (void) dismissErrorScreen; 31 | - (void) dismissLoginScreen; 32 | - (void) showAboutScreen; 33 | 34 | // Narrowing 35 | - (void) narrowWithOperators:(NarrowOperators *)narrow; 36 | - (void) narrowWithOperators:(NarrowOperators *)narrow thenDisplayId:(long)messageId; 37 | - (BOOL) isNarrowed; 38 | - (NarrowOperators *)currentNarrow; 39 | - (void) clearNarrowWithAnimation:(BOOL)animation; 40 | 41 | - (void) reloadCoreData; 42 | @end 43 | -------------------------------------------------------------------------------- /Zulip/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Zulip/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | int main(int argc, char *argv[]) 4 | { 5 | @autoreleasepool { 6 | int retVal = UIApplicationMain(argc, argv, nil, nil); 7 | return retVal; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ZulipTests/ZulipTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /ZulipTests/ZulipTests.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZulipTests.h 3 | // ZulipTests 4 | // 5 | 6 | #import 7 | 8 | @interface ZulipTests : SenTestCase 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /ZulipTests/ZulipTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // ZulipTests.m 3 | // ZulipTests 4 | // 5 | 6 | #import "ZulipTests.h" 7 | 8 | @implementation ZulipTests 9 | 10 | - (void)setUp 11 | { 12 | [super setUp]; 13 | 14 | // Set-up code here. 15 | } 16 | 17 | - (void)tearDown 18 | { 19 | // Tear-down code here. 20 | 21 | [super tearDown]; 22 | } 23 | 24 | - (void)testExample 25 | { 26 | STFail(@"Unit tests are not implemented yet in ZulipTests"); 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /ZulipTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /delete-trailing-whitespace.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | while (<>) { 4 | s/\s+$//; 5 | print "$_\n"; 6 | } 7 | -------------------------------------------------------------------------------- /docs/event_queues.md: -------------------------------------------------------------------------------- 1 | # Event Queue overview 2 | 3 | We have 2 event queues that are connected to our Zulip servers. 4 | 5 | ## Resuming 6 | 7 | Basically there are a couple of things that can happen on resume: 8 | 9 | * Your message event queue is still valid (which means you were active in the last 10min) 10 | -> Then we simply drain the queue and show the new messages 11 | 12 | * Your message event queue is invalid (garbage collected on server) 13 | -> We basically reload around your pointer, calling initialPopulate 14 | 15 | * Your metadata queue is invalid (idle for >1week) 16 | -> We do a clean reset and load from an empty database 17 | 18 | If your connectivity really sucks… a lot of things might break. E.g. the initial populate might not succeed, or loading the event queue might fail, etc. In all those cases we’ll show the “Error screen” once enough failures have been encountered. 19 | 20 | -------------------------------------------------------------------------------- /images/launch-1024x768.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/images/launch-1024x768.png -------------------------------------------------------------------------------- /images/launch-1536x2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/images/launch-1536x2048.png -------------------------------------------------------------------------------- /images/launch-2048x1536.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/images/launch-2048x1536.png -------------------------------------------------------------------------------- /images/launch-320x480.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/images/launch-320x480.png -------------------------------------------------------------------------------- /images/launch-640x1136.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/images/launch-640x1136.png -------------------------------------------------------------------------------- /images/launch-640x960.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/images/launch-640x960.png -------------------------------------------------------------------------------- /images/launch-768x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-ios-legacy/06a8540ab51817b3080a86b62ad22b62be001792/images/launch-768x1024.png -------------------------------------------------------------------------------- /tools/emoji_dict_generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import glob 5 | import json 6 | 7 | mapping = {} 8 | rev_mapping = {} 9 | 10 | for f in glob.glob('/Users/leo/humbug/static/third/gemoji/images/emoji/*.png'): 11 | if os.path.islink(f): 12 | name = os.path.basename(f).split('.')[0] 13 | target = os.readlink(f) 14 | codepoint = os.path.basename(target).split('.')[0] 15 | if '-' in codepoint: 16 | a, b = codepoint.split('-') 17 | uc = (r'\U' + a.zfill(8)) + (r'\U' + b.zfill(8)) 18 | else: 19 | uc = r'\U' + codepoint.zfill(8) 20 | decoded = (uc).decode('unicode-escape') 21 | mapping[name] = decoded 22 | rev_mapping[decoded]=name 23 | 24 | print json.dumps(mapping) 25 | print json.dumps(rev_mapping) 26 | --------------------------------------------------------------------------------