├── .gitignore ├── .travis.yml ├── LICENSE ├── Podfile ├── Podfile.lock ├── README.md ├── twiliochat.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── twiliochat.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── twiliochat.xcscheme ├── twiliochat ├── AlertDialogController.swift ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-29.png │ │ ├── Icon-29@2x-1.png │ │ ├── Icon-29@2x.png │ │ ├── Icon-29@3x.png │ │ ├── Icon-40.png │ │ ├── Icon-40@2x-1.png │ │ ├── Icon-40@2x.png │ │ ├── Icon-40@3x.png │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── Icon-76.png │ │ ├── Icon-76@2x.png │ │ └── Icon-83_5@2x.png │ ├── Contents.json │ ├── add-channel-button.imageset │ │ ├── Contents.json │ │ ├── add-channel-button.png │ │ ├── add-channel-button@2x.png │ │ └── add-channel-button@3x.png │ ├── btn-login.imageset │ │ ├── Contents.json │ │ ├── btn-login.png │ │ ├── btn-login@2x.png │ │ └── btn-login@3x.png │ ├── channel-list-bg.imageset │ │ ├── Contents.json │ │ ├── channel-list-bg.png │ │ ├── channel-list-bg@2x.png │ │ └── channel-list-bg@3x.png │ ├── chat-field-bg.imageset │ │ ├── Contents.json │ │ ├── chat-field-bg.png │ │ ├── chat-field-bg@2x.png │ │ └── chat-field-bg@3x.png │ ├── header-bg.imageset │ │ ├── Contents.json │ │ ├── header-bg.png │ │ ├── header-bg@2x.png │ │ └── header-bg@3x.png │ ├── home-bg.imageset │ │ ├── Contents.json │ │ ├── home-bg.png │ │ ├── home-bg@2x.png │ │ └── home-bg@3x.png │ ├── icon-list.imageset │ │ ├── Contents.json │ │ ├── icon-list.png │ │ ├── icon-list@2x.png │ │ └── icon-list@3x.png │ ├── icon-user.imageset │ │ ├── Contents.json │ │ ├── icon-user.png │ │ ├── icon-user@2x.png │ │ └── icon-user@3x.png │ ├── landing-logo.imageset │ │ ├── Contents.json │ │ ├── landing-logo.png │ │ ├── landing-logo@2x.png │ │ └── landing-logo@3x.png │ ├── login-bg.imageset │ │ ├── Contents.json │ │ ├── login-bg.png │ │ ├── login-bg@2x.png │ │ └── login-bg@3x.png │ ├── password-field-bg.imageset │ │ ├── Contents.json │ │ ├── password-field-bg.png │ │ ├── password-field-bg@2x.png │ │ └── password-field-bg@3x.png │ ├── user-panel-line.imageset │ │ ├── Contents.json │ │ ├── user-panel-line.png │ │ ├── user-panel-line@2x.png │ │ └── user-panel-line@3x.png │ └── username-field-bg.imageset │ │ ├── Contents.json │ │ ├── username-field-bg.png │ │ ├── username-field-bg@2x.png │ │ └── username-field-bg@3x.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── ChannelManager.swift ├── ChatStatusTableCell.xib ├── ChatTableCell.swift ├── ChatTableCell.xib ├── DateTodayFormatter.swift ├── Info.plist ├── InputDialogController.swift ├── Keys.plist ├── LoginViewController.swift ├── MainChatViewController.swift ├── MenuTableCell.swift ├── MenuViewController.swift ├── MessagingManager.swift ├── SessionManager.swift ├── StatusMessage.swift ├── TextFieldFormHandler.swift ├── TokenRequestHandler.swift └── twiliochat-Bridging-Header.h └── twiliochatTests ├── DateTodayFormatterTests.swift ├── Info.plist ├── LoginViewControllerTests.swift ├── MockAlertDialogController.swift └── MockMessagingManager.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | *.DS_Store 4 | build/ 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | xcuserdata 14 | *.xccheckout 15 | *.moved-aside 16 | DerivedData 17 | *.hmap 18 | *.ipa 19 | *.xcuserstate 20 | twiliochat/Keys.plist 21 | 22 | # CocoaPods 23 | # 24 | # We recommend against adding the Pods directory to your .gitignore. However 25 | # you should judge for yourself, the pros and cons are mentioned at: 26 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 27 | # 28 | Pods/ 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode10 3 | cache: cocoapods 4 | xcode_workspace: twiliochat.xcworkspace 5 | xcode_scheme: twiliochat 6 | 7 | before_install: 8 | - travis_wait pod repo update >/dev/null 2>&1 9 | 10 | install: 11 | - travis_wait pod install 12 | 13 | script: 14 | - xcodebuild -workspace twiliochat.xcworkspace -scheme twiliochat CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO clean build-for-testing | xcpretty 15 | - xcodebuild -workspace twiliochat.xcworkspace -scheme twiliochat -destination 'platform=iOS Simulator,name=iPhone 6' CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO clean test | xcpretty 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 TwilioDevEd 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 all 13 | 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 THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs' 2 | 3 | # Uncomment this line to define a global platform for your project 4 | platform :ios, '11.0' 5 | # Uncomment this line if you're using Swift 6 | use_frameworks! 7 | 8 | target 'twiliochat' do 9 | pod 'SWRevealViewController', '~> 2.3' 10 | pod 'SlackTextViewController', '1.9.6' 11 | pod 'TwilioChatClient', '~> 4.0' 12 | end 13 | 14 | target 'twiliochatTests' do 15 | pod 'SWRevealViewController', '~> 2.3' 16 | pod 'SlackTextViewController', '1.9.6' 17 | pod 'TwilioChatClient', '~> 4.0' 18 | end 19 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - SlackTextViewController (1.9.6) 3 | - SWRevealViewController (2.3.0) 4 | - TwilioChatClient (4.0.0) 5 | 6 | DEPENDENCIES: 7 | - SlackTextViewController (= 1.9.6) 8 | - SWRevealViewController (~> 2.3) 9 | - TwilioChatClient (~> 4.0) 10 | 11 | SPEC REPOS: 12 | https://github.com/CocoaPods/Specs.git: 13 | - SlackTextViewController 14 | - SWRevealViewController 15 | - TwilioChatClient 16 | 17 | SPEC CHECKSUMS: 18 | SlackTextViewController: b854e62c1c156336bc4fd409c6ca79b5773e8f9d 19 | SWRevealViewController: 6d3fd97f70112fd7cef9de14df4260eacce4c63a 20 | TwilioChatClient: 72fad6bd1fcff3bb13d90a5a5c873509072d859a 21 | 22 | PODFILE CHECKSUM: 9fb530d9294dcf8e8f7f735b87bea8ccde0b39f3 23 | 24 | COCOAPODS: 1.8.4 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Important Notice 2 | 3 | We intend to sunset the Programmable Chat API on July 25, 2022 to focus on the next generation of chat: the [Twilio Conversations API](https://www.twilio.com/docs/conversations). Find out about the [EOL process](https://www.twilio.com/changelog/programmable-chat-end-of-life). We have also prepared [this Migration Guide](https://www.twilio.com/docs/conversations/migrating-chat-conversations) to assist in the transition from Chat to Conversations. 4 | 5 | # Twilio Chat Tutorial for Swift 6 | [![Build Status](https://travis-ci.org/TwilioDevEd/twiliochat-swift.svg?branch=master)](https://travis-ci.org/TwilioDevEd/twiliochat-swift) 7 | 8 | Swift implementation of Twilio Chat 9 | 10 | ### Running the Application 11 | 12 | 1. Clone the repository and `cd` into it 13 | 1. Install the application's dependencies with [CocoaPods](https://cocoapods.org/) 14 | 15 | ```bash 16 | $ pod install 17 | ``` 18 | 1. Open the project with `Xcode` but don't use `twiliochat.xcodeproj` file. For 19 | CocoaPods dependencies to work you must use `twiliochat.xcworkspace`. 20 | 1. [Twilio Chat](https://www.twilio.com/docs/chat) requires an 21 | [access token](https://www.twilio.com/docs/chat/identity) generated using your 22 | Twilio credentials in order to connect. First we need to setup a server that will generate this token 23 | for the mobile application to use. We have created web versions of Twilio Chat, you can use any of these 24 | applications to generate the token that this mobile app requires. Just pick your favorite flavor: 25 | 26 | * [PHP - Laravel](https://github.com/TwilioDevEd/twiliochat-laravel) 27 | * [C# - .NET MVC](https://github.com/TwilioDevEd/twiliochat-csharp) 28 | * [Java - Servlets](https://github.com/TwilioDevEd/twiliochat-servlets) 29 | * [JS - Node](https://github.com/TwilioDevEd/twiliochat-node) 30 | 31 | Look for instructions on how to setup these servers in any of the links above. 32 | 33 | 1. Once you have the server running (from the previous step), you need to edit one 34 | file in the Xcode project. 35 | 36 | ``` 37 | ProjectRoot -> twiliochat -> resources -> Keys.plist 38 | ``` 39 | This file contains the `TokenRequestUrl` key. The default value is `http://localhost:8000/token`. This 40 | address refers to the host machine loopback interface when running this application 41 | in the iOS simulator. You must change this value to match the address of your server running 42 | the token generation application. We are using the [PHP - Laravel](https://github.com/TwilioDevEd/twiliochat-laravel) 43 | version in this case, that's why we use port 8000. 44 | 45 | ***Note:*** In some operating systems you need to specify the address for the development server 46 | when you run the Laravel application, here's an example: 47 | 48 | ``` 49 | $ php artisan serve --host=127.0.0.1 50 | ``` 51 | 52 | 1. Now Twilio Chat is ready to go. Run the application on the simulator or your own device, just 53 | make sure that you have properly set up the token generation server and the `TokenRequestUrl` key. 54 | To run the application in a real device you'll need to expose your local token generation server 55 | by manually forwarding ports, or using a tool like [ngrok](https://ngrok.com/). 56 | If you decide to work with ngrok, your Keys.plist file should hold a key like this one: 57 | 58 | ``` 59 | TokenRequestUrl -> http://.ngrok.io/token 60 | ``` 61 | No need to specify the port in this url, as ngrok will forward the request to the specified port. 62 | -------------------------------------------------------------------------------- /twiliochat.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 59A866DD1C1B5E8100525F07 /* MockAlertDialogController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59A866DC1C1B5E8100525F07 /* MockAlertDialogController.swift */; }; 11 | 59A866DF1C1B6C0900525F07 /* LoginViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59A866DE1C1B6C0900525F07 /* LoginViewControllerTests.swift */; }; 12 | 59AE1CCE1C0C8C280008B10B /* TextFieldFormHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59AE1CCD1C0C8C280008B10B /* TextFieldFormHandler.swift */; }; 13 | 59AE1CD21C0D03820008B10B /* MessagingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59AE1CD11C0D03820008B10B /* MessagingManager.swift */; }; 14 | 59F208FE1C04C06800CC2392 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59F208FD1C04C06800CC2392 /* AppDelegate.swift */; }; 15 | 59F209031C04C06800CC2392 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 59F209011C04C06800CC2392 /* Main.storyboard */; }; 16 | 59F209051C04C06800CC2392 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 59F209041C04C06800CC2392 /* Assets.xcassets */; }; 17 | 59F209081C04C06800CC2392 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 59F209061C04C06800CC2392 /* LaunchScreen.storyboard */; }; 18 | 59F209131C04C06800CC2392 /* DateTodayFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59F209121C04C06800CC2392 /* DateTodayFormatterTests.swift */; }; 19 | 59F2092F1C04C35E00CC2392 /* Keys.plist in Resources */ = {isa = PBXBuildFile; fileRef = 59F2092D1C04C35E00CC2392 /* Keys.plist */; }; 20 | 59F209321C050EF700CC2392 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59F209311C050EF700CC2392 /* LoginViewController.swift */; }; 21 | 59F5EDB01C6E925500CE7D32 /* TokenRequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59F5EDAF1C6E925500CE7D32 /* TokenRequestHandler.swift */; }; 22 | 59F5EDB31C720E1200CE7D32 /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59F5EDB21C720E1200CE7D32 /* SessionManager.swift */; }; 23 | 877109151C10EF4F004C57D3 /* ChannelManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877109141C10EF4F004C57D3 /* ChannelManager.swift */; }; 24 | 877109171C15E55E004C57D3 /* MenuTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877109161C15E55E004C57D3 /* MenuTableCell.swift */; }; 25 | 8771091A1C162FC8004C57D3 /* ChatStatusTableCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 877109181C162FC8004C57D3 /* ChatStatusTableCell.xib */; }; 26 | 8771091B1C162FC8004C57D3 /* ChatTableCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 877109191C162FC8004C57D3 /* ChatTableCell.xib */; }; 27 | 8771091D1C1638E9004C57D3 /* InputDialogController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8771091C1C1638E9004C57D3 /* InputDialogController.swift */; }; 28 | 8771091F1C175AC2004C57D3 /* ChatTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8771091E1C175AC2004C57D3 /* ChatTableCell.swift */; }; 29 | 877109211C1772C5004C57D3 /* StatusMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877109201C1772C5004C57D3 /* StatusMessage.swift */; }; 30 | 877109231C1776FA004C57D3 /* DateTodayFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877109221C1776FA004C57D3 /* DateTodayFormatter.swift */; }; 31 | 87F895F21C0CF3CE00754007 /* AlertDialogController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87F895F11C0CF3CE00754007 /* AlertDialogController.swift */; }; 32 | 87F895F61C0E044300754007 /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87F895F51C0E044300754007 /* MenuViewController.swift */; }; 33 | 87F895F81C0E045F00754007 /* MainChatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87F895F71C0E045F00754007 /* MainChatViewController.swift */; }; 34 | 8A07B2CB1F224CAD009F0836 /* MockMessagingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A07B2CA1F224CAD009F0836 /* MockMessagingManager.swift */; }; 35 | /* End PBXBuildFile section */ 36 | 37 | /* Begin PBXContainerItemProxy section */ 38 | 59F2090F1C04C06800CC2392 /* PBXContainerItemProxy */ = { 39 | isa = PBXContainerItemProxy; 40 | containerPortal = 59F208F21C04C06800CC2392 /* Project object */; 41 | proxyType = 1; 42 | remoteGlobalIDString = 59F208F91C04C06800CC2392; 43 | remoteInfo = twiliochat; 44 | }; 45 | /* End PBXContainerItemProxy section */ 46 | 47 | /* Begin PBXFileReference section */ 48 | 59A866DC1C1B5E8100525F07 /* MockAlertDialogController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockAlertDialogController.swift; sourceTree = ""; }; 49 | 59A866DE1C1B6C0900525F07 /* LoginViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewControllerTests.swift; sourceTree = ""; }; 50 | 59AE1CCD1C0C8C280008B10B /* TextFieldFormHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldFormHandler.swift; sourceTree = ""; }; 51 | 59AE1CD11C0D03820008B10B /* MessagingManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagingManager.swift; sourceTree = ""; }; 52 | 59F208FA1C04C06800CC2392 /* twiliochat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = twiliochat.app; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | 59F208FD1C04C06800CC2392 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 54 | 59F209021C04C06800CC2392 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 55 | 59F209041C04C06800CC2392 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 56 | 59F209071C04C06800CC2392 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 57 | 59F209091C04C06800CC2392 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 58 | 59F2090E1C04C06800CC2392 /* twiliochatTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = twiliochatTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | 59F209121C04C06800CC2392 /* DateTodayFormatterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTodayFormatterTests.swift; sourceTree = ""; }; 60 | 59F209141C04C06800CC2392 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 61 | 59F2092D1C04C35E00CC2392 /* Keys.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Keys.plist; sourceTree = ""; }; 62 | 59F209311C050EF700CC2392 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 63 | 59F5EDAF1C6E925500CE7D32 /* TokenRequestHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenRequestHandler.swift; sourceTree = ""; }; 64 | 59F5EDB21C720E1200CE7D32 /* SessionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionManager.swift; sourceTree = ""; }; 65 | 825276F51DFB48EC0029E3CA /* twiliochat-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "twiliochat-Bridging-Header.h"; sourceTree = ""; }; 66 | 877109141C10EF4F004C57D3 /* ChannelManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelManager.swift; sourceTree = ""; }; 67 | 877109161C15E55E004C57D3 /* MenuTableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuTableCell.swift; sourceTree = ""; }; 68 | 877109181C162FC8004C57D3 /* ChatStatusTableCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ChatStatusTableCell.xib; sourceTree = ""; }; 69 | 877109191C162FC8004C57D3 /* ChatTableCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ChatTableCell.xib; sourceTree = ""; }; 70 | 8771091C1C1638E9004C57D3 /* InputDialogController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputDialogController.swift; sourceTree = ""; }; 71 | 8771091E1C175AC2004C57D3 /* ChatTableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatTableCell.swift; sourceTree = ""; }; 72 | 877109201C1772C5004C57D3 /* StatusMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusMessage.swift; sourceTree = ""; }; 73 | 877109221C1776FA004C57D3 /* DateTodayFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateTodayFormatter.swift; sourceTree = ""; }; 74 | 87F895F11C0CF3CE00754007 /* AlertDialogController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertDialogController.swift; sourceTree = ""; }; 75 | 87F895F51C0E044300754007 /* MenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuViewController.swift; sourceTree = ""; }; 76 | 87F895F71C0E045F00754007 /* MainChatViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainChatViewController.swift; sourceTree = ""; }; 77 | 8A07B2CA1F224CAD009F0836 /* MockMessagingManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockMessagingManager.swift; sourceTree = ""; }; 78 | /* End PBXFileReference section */ 79 | 80 | /* Begin PBXFrameworksBuildPhase section */ 81 | 59F208F71C04C06800CC2392 /* Frameworks */ = { 82 | isa = PBXFrameworksBuildPhase; 83 | buildActionMask = 2147483647; 84 | files = ( 85 | ); 86 | runOnlyForDeploymentPostprocessing = 0; 87 | }; 88 | 59F2090B1C04C06800CC2392 /* Frameworks */ = { 89 | isa = PBXFrameworksBuildPhase; 90 | buildActionMask = 2147483647; 91 | files = ( 92 | ); 93 | runOnlyForDeploymentPostprocessing = 0; 94 | }; 95 | /* End PBXFrameworksBuildPhase section */ 96 | 97 | /* Begin PBXGroup section */ 98 | 59A866D91C1B5D3700525F07 /* Mocks */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | 8A07B2CA1F224CAD009F0836 /* MockMessagingManager.swift */, 102 | 59A866DC1C1B5E8100525F07 /* MockAlertDialogController.swift */, 103 | ); 104 | name = Mocks; 105 | sourceTree = ""; 106 | }; 107 | 59AE1CCF1C0C8C6D0008B10B /* util */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 59AE1CCD1C0C8C280008B10B /* TextFieldFormHandler.swift */, 111 | 87F895F11C0CF3CE00754007 /* AlertDialogController.swift */, 112 | 8771091C1C1638E9004C57D3 /* InputDialogController.swift */, 113 | 877109221C1776FA004C57D3 /* DateTodayFormatter.swift */, 114 | 59F5EDAF1C6E925500CE7D32 /* TokenRequestHandler.swift */, 115 | ); 116 | name = util; 117 | sourceTree = ""; 118 | }; 119 | 59AE1CD01C0D034D0008B10B /* messaging */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 59AE1CD11C0D03820008B10B /* MessagingManager.swift */, 123 | ); 124 | name = messaging; 125 | sourceTree = ""; 126 | }; 127 | 59F208F11C04C06800CC2392 = { 128 | isa = PBXGroup; 129 | children = ( 130 | 59F208FC1C04C06800CC2392 /* twiliochat */, 131 | 59F209111C04C06800CC2392 /* twiliochatTests */, 132 | 59F208FB1C04C06800CC2392 /* Products */, 133 | 89D67A5C1B1630989E04019D /* Pods */, 134 | ); 135 | sourceTree = ""; 136 | }; 137 | 59F208FB1C04C06800CC2392 /* Products */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | 59F208FA1C04C06800CC2392 /* twiliochat.app */, 141 | 59F2090E1C04C06800CC2392 /* twiliochatTests.xctest */, 142 | ); 143 | name = Products; 144 | sourceTree = ""; 145 | }; 146 | 59F208FC1C04C06800CC2392 /* twiliochat */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | 59F5EDB11C720DD700CE7D32 /* session */, 150 | 877109101C10EEF0004C57D3 /* channels */, 151 | 59AE1CCF1C0C8C6D0008B10B /* util */, 152 | 59AE1CD01C0D034D0008B10B /* messaging */, 153 | 87F895F41C0E040E00754007 /* menu */, 154 | 87F895F31C0E033A00754007 /* chat */, 155 | 59F209301C050ED800CC2392 /* login */, 156 | 59F2092B1C04C33C00CC2392 /* resources */, 157 | 59F208FD1C04C06800CC2392 /* AppDelegate.swift */, 158 | 59F209011C04C06800CC2392 /* Main.storyboard */, 159 | 59F209041C04C06800CC2392 /* Assets.xcassets */, 160 | 59F209061C04C06800CC2392 /* LaunchScreen.storyboard */, 161 | 877109181C162FC8004C57D3 /* ChatStatusTableCell.xib */, 162 | 877109191C162FC8004C57D3 /* ChatTableCell.xib */, 163 | 825276F51DFB48EC0029E3CA /* twiliochat-Bridging-Header.h */, 164 | 59F209091C04C06800CC2392 /* Info.plist */, 165 | ); 166 | path = twiliochat; 167 | sourceTree = ""; 168 | }; 169 | 59F209111C04C06800CC2392 /* twiliochatTests */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 59A866D91C1B5D3700525F07 /* Mocks */, 173 | 59F209121C04C06800CC2392 /* DateTodayFormatterTests.swift */, 174 | 59F209141C04C06800CC2392 /* Info.plist */, 175 | 59A866DE1C1B6C0900525F07 /* LoginViewControllerTests.swift */, 176 | ); 177 | path = twiliochatTests; 178 | sourceTree = ""; 179 | }; 180 | 59F2092B1C04C33C00CC2392 /* resources */ = { 181 | isa = PBXGroup; 182 | children = ( 183 | 59F2092D1C04C35E00CC2392 /* Keys.plist */, 184 | ); 185 | name = resources; 186 | sourceTree = ""; 187 | }; 188 | 59F209301C050ED800CC2392 /* login */ = { 189 | isa = PBXGroup; 190 | children = ( 191 | 59F209311C050EF700CC2392 /* LoginViewController.swift */, 192 | ); 193 | name = login; 194 | sourceTree = ""; 195 | }; 196 | 59F5EDB11C720DD700CE7D32 /* session */ = { 197 | isa = PBXGroup; 198 | children = ( 199 | 59F5EDB21C720E1200CE7D32 /* SessionManager.swift */, 200 | ); 201 | name = session; 202 | sourceTree = ""; 203 | }; 204 | 877109101C10EEF0004C57D3 /* channels */ = { 205 | isa = PBXGroup; 206 | children = ( 207 | 877109141C10EF4F004C57D3 /* ChannelManager.swift */, 208 | ); 209 | name = channels; 210 | sourceTree = ""; 211 | }; 212 | 87F895F31C0E033A00754007 /* chat */ = { 213 | isa = PBXGroup; 214 | children = ( 215 | 87F895F71C0E045F00754007 /* MainChatViewController.swift */, 216 | 8771091E1C175AC2004C57D3 /* ChatTableCell.swift */, 217 | 877109201C1772C5004C57D3 /* StatusMessage.swift */, 218 | ); 219 | name = chat; 220 | sourceTree = ""; 221 | }; 222 | 87F895F41C0E040E00754007 /* menu */ = { 223 | isa = PBXGroup; 224 | children = ( 225 | 87F895F51C0E044300754007 /* MenuViewController.swift */, 226 | 877109161C15E55E004C57D3 /* MenuTableCell.swift */, 227 | ); 228 | name = menu; 229 | sourceTree = ""; 230 | }; 231 | 89D67A5C1B1630989E04019D /* Pods */ = { 232 | isa = PBXGroup; 233 | children = ( 234 | ); 235 | path = Pods; 236 | sourceTree = ""; 237 | }; 238 | /* End PBXGroup section */ 239 | 240 | /* Begin PBXNativeTarget section */ 241 | 59F208F91C04C06800CC2392 /* twiliochat */ = { 242 | isa = PBXNativeTarget; 243 | buildConfigurationList = 59F209221C04C06800CC2392 /* Build configuration list for PBXNativeTarget "twiliochat" */; 244 | buildPhases = ( 245 | 59F208F61C04C06800CC2392 /* Sources */, 246 | 59F208F71C04C06800CC2392 /* Frameworks */, 247 | 59F208F81C04C06800CC2392 /* Resources */, 248 | ); 249 | buildRules = ( 250 | ); 251 | dependencies = ( 252 | ); 253 | name = twiliochat; 254 | productName = twiliochat; 255 | productReference = 59F208FA1C04C06800CC2392 /* twiliochat.app */; 256 | productType = "com.apple.product-type.application"; 257 | }; 258 | 59F2090D1C04C06800CC2392 /* twiliochatTests */ = { 259 | isa = PBXNativeTarget; 260 | buildConfigurationList = 59F209251C04C06800CC2392 /* Build configuration list for PBXNativeTarget "twiliochatTests" */; 261 | buildPhases = ( 262 | 59F2090A1C04C06800CC2392 /* Sources */, 263 | 59F2090B1C04C06800CC2392 /* Frameworks */, 264 | 59F2090C1C04C06800CC2392 /* Resources */, 265 | ); 266 | buildRules = ( 267 | ); 268 | dependencies = ( 269 | 59F209101C04C06800CC2392 /* PBXTargetDependency */, 270 | ); 271 | name = twiliochatTests; 272 | productName = twiliochatTests; 273 | productReference = 59F2090E1C04C06800CC2392 /* twiliochatTests.xctest */; 274 | productType = "com.apple.product-type.bundle.unit-test"; 275 | }; 276 | /* End PBXNativeTarget section */ 277 | 278 | /* Begin PBXProject section */ 279 | 59F208F21C04C06800CC2392 /* Project object */ = { 280 | isa = PBXProject; 281 | attributes = { 282 | LastSwiftUpdateCheck = 0710; 283 | LastUpgradeCheck = 1150; 284 | ORGANIZATIONNAME = Twilio; 285 | TargetAttributes = { 286 | 59F208F91C04C06800CC2392 = { 287 | CreatedOnToolsVersion = 7.1.1; 288 | LastSwiftMigration = 1150; 289 | }; 290 | 59F2090D1C04C06800CC2392 = { 291 | CreatedOnToolsVersion = 7.1.1; 292 | LastSwiftMigration = 1150; 293 | TestTargetID = 59F208F91C04C06800CC2392; 294 | }; 295 | }; 296 | }; 297 | buildConfigurationList = 59F208F51C04C06800CC2392 /* Build configuration list for PBXProject "twiliochat" */; 298 | compatibilityVersion = "Xcode 3.2"; 299 | developmentRegion = English; 300 | hasScannedForEncodings = 0; 301 | knownRegions = ( 302 | English, 303 | en, 304 | Base, 305 | ); 306 | mainGroup = 59F208F11C04C06800CC2392; 307 | productRefGroup = 59F208FB1C04C06800CC2392 /* Products */; 308 | projectDirPath = ""; 309 | projectRoot = ""; 310 | targets = ( 311 | 59F208F91C04C06800CC2392 /* twiliochat */, 312 | 59F2090D1C04C06800CC2392 /* twiliochatTests */, 313 | ); 314 | }; 315 | /* End PBXProject section */ 316 | 317 | /* Begin PBXResourcesBuildPhase section */ 318 | 59F208F81C04C06800CC2392 /* Resources */ = { 319 | isa = PBXResourcesBuildPhase; 320 | buildActionMask = 2147483647; 321 | files = ( 322 | 8771091B1C162FC8004C57D3 /* ChatTableCell.xib in Resources */, 323 | 59F209081C04C06800CC2392 /* LaunchScreen.storyboard in Resources */, 324 | 59F209051C04C06800CC2392 /* Assets.xcassets in Resources */, 325 | 8771091A1C162FC8004C57D3 /* ChatStatusTableCell.xib in Resources */, 326 | 59F209031C04C06800CC2392 /* Main.storyboard in Resources */, 327 | 59F2092F1C04C35E00CC2392 /* Keys.plist in Resources */, 328 | ); 329 | runOnlyForDeploymentPostprocessing = 0; 330 | }; 331 | 59F2090C1C04C06800CC2392 /* Resources */ = { 332 | isa = PBXResourcesBuildPhase; 333 | buildActionMask = 2147483647; 334 | files = ( 335 | ); 336 | runOnlyForDeploymentPostprocessing = 0; 337 | }; 338 | /* End PBXResourcesBuildPhase section */ 339 | 340 | /* Begin PBXSourcesBuildPhase section */ 341 | 59F208F61C04C06800CC2392 /* Sources */ = { 342 | isa = PBXSourcesBuildPhase; 343 | buildActionMask = 2147483647; 344 | files = ( 345 | 877109151C10EF4F004C57D3 /* ChannelManager.swift in Sources */, 346 | 87F895F81C0E045F00754007 /* MainChatViewController.swift in Sources */, 347 | 877109171C15E55E004C57D3 /* MenuTableCell.swift in Sources */, 348 | 877109231C1776FA004C57D3 /* DateTodayFormatter.swift in Sources */, 349 | 877109211C1772C5004C57D3 /* StatusMessage.swift in Sources */, 350 | 8771091D1C1638E9004C57D3 /* InputDialogController.swift in Sources */, 351 | 59F209321C050EF700CC2392 /* LoginViewController.swift in Sources */, 352 | 8771091F1C175AC2004C57D3 /* ChatTableCell.swift in Sources */, 353 | 87F895F21C0CF3CE00754007 /* AlertDialogController.swift in Sources */, 354 | 59AE1CCE1C0C8C280008B10B /* TextFieldFormHandler.swift in Sources */, 355 | 59F208FE1C04C06800CC2392 /* AppDelegate.swift in Sources */, 356 | 59AE1CD21C0D03820008B10B /* MessagingManager.swift in Sources */, 357 | 59F5EDB01C6E925500CE7D32 /* TokenRequestHandler.swift in Sources */, 358 | 59F5EDB31C720E1200CE7D32 /* SessionManager.swift in Sources */, 359 | 87F895F61C0E044300754007 /* MenuViewController.swift in Sources */, 360 | ); 361 | runOnlyForDeploymentPostprocessing = 0; 362 | }; 363 | 59F2090A1C04C06800CC2392 /* Sources */ = { 364 | isa = PBXSourcesBuildPhase; 365 | buildActionMask = 2147483647; 366 | files = ( 367 | 59A866DD1C1B5E8100525F07 /* MockAlertDialogController.swift in Sources */, 368 | 59F209131C04C06800CC2392 /* DateTodayFormatterTests.swift in Sources */, 369 | 59A866DF1C1B6C0900525F07 /* LoginViewControllerTests.swift in Sources */, 370 | 8A07B2CB1F224CAD009F0836 /* MockMessagingManager.swift in Sources */, 371 | ); 372 | runOnlyForDeploymentPostprocessing = 0; 373 | }; 374 | /* End PBXSourcesBuildPhase section */ 375 | 376 | /* Begin PBXTargetDependency section */ 377 | 59F209101C04C06800CC2392 /* PBXTargetDependency */ = { 378 | isa = PBXTargetDependency; 379 | target = 59F208F91C04C06800CC2392 /* twiliochat */; 380 | targetProxy = 59F2090F1C04C06800CC2392 /* PBXContainerItemProxy */; 381 | }; 382 | /* End PBXTargetDependency section */ 383 | 384 | /* Begin PBXVariantGroup section */ 385 | 59F209011C04C06800CC2392 /* Main.storyboard */ = { 386 | isa = PBXVariantGroup; 387 | children = ( 388 | 59F209021C04C06800CC2392 /* Base */, 389 | ); 390 | name = Main.storyboard; 391 | sourceTree = ""; 392 | }; 393 | 59F209061C04C06800CC2392 /* LaunchScreen.storyboard */ = { 394 | isa = PBXVariantGroup; 395 | children = ( 396 | 59F209071C04C06800CC2392 /* Base */, 397 | ); 398 | name = LaunchScreen.storyboard; 399 | sourceTree = ""; 400 | }; 401 | /* End PBXVariantGroup section */ 402 | 403 | /* Begin XCBuildConfiguration section */ 404 | 59F209201C04C06800CC2392 /* Debug */ = { 405 | isa = XCBuildConfiguration; 406 | buildSettings = { 407 | ALWAYS_SEARCH_USER_PATHS = NO; 408 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 409 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 410 | CLANG_CXX_LIBRARY = "libc++"; 411 | CLANG_ENABLE_MODULES = YES; 412 | CLANG_ENABLE_OBJC_ARC = YES; 413 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 414 | CLANG_WARN_BOOL_CONVERSION = YES; 415 | CLANG_WARN_COMMA = YES; 416 | CLANG_WARN_CONSTANT_CONVERSION = YES; 417 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 418 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 419 | CLANG_WARN_EMPTY_BODY = YES; 420 | CLANG_WARN_ENUM_CONVERSION = YES; 421 | CLANG_WARN_INFINITE_RECURSION = YES; 422 | CLANG_WARN_INT_CONVERSION = YES; 423 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 424 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 425 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 426 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 427 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 428 | CLANG_WARN_STRICT_PROTOTYPES = YES; 429 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 430 | CLANG_WARN_UNREACHABLE_CODE = YES; 431 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 432 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 433 | COPY_PHASE_STRIP = NO; 434 | DEBUG_INFORMATION_FORMAT = dwarf; 435 | ENABLE_BITCODE = NO; 436 | ENABLE_STRICT_OBJC_MSGSEND = YES; 437 | ENABLE_TESTABILITY = YES; 438 | GCC_C_LANGUAGE_STANDARD = gnu99; 439 | GCC_DYNAMIC_NO_PIC = NO; 440 | GCC_NO_COMMON_BLOCKS = YES; 441 | GCC_OPTIMIZATION_LEVEL = 0; 442 | GCC_PREPROCESSOR_DEFINITIONS = ( 443 | "DEBUG=1", 444 | "$(inherited)", 445 | ); 446 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 447 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 448 | GCC_WARN_UNDECLARED_SELECTOR = YES; 449 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 450 | GCC_WARN_UNUSED_FUNCTION = YES; 451 | GCC_WARN_UNUSED_VARIABLE = YES; 452 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 453 | MTL_ENABLE_DEBUG_INFO = YES; 454 | ONLY_ACTIVE_ARCH = YES; 455 | SDKROOT = iphoneos; 456 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 457 | TARGETED_DEVICE_FAMILY = "1,2"; 458 | }; 459 | name = Debug; 460 | }; 461 | 59F209211C04C06800CC2392 /* Release */ = { 462 | isa = XCBuildConfiguration; 463 | buildSettings = { 464 | ALWAYS_SEARCH_USER_PATHS = NO; 465 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 466 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 467 | CLANG_CXX_LIBRARY = "libc++"; 468 | CLANG_ENABLE_MODULES = YES; 469 | CLANG_ENABLE_OBJC_ARC = YES; 470 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 471 | CLANG_WARN_BOOL_CONVERSION = YES; 472 | CLANG_WARN_COMMA = YES; 473 | CLANG_WARN_CONSTANT_CONVERSION = YES; 474 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 475 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 476 | CLANG_WARN_EMPTY_BODY = YES; 477 | CLANG_WARN_ENUM_CONVERSION = YES; 478 | CLANG_WARN_INFINITE_RECURSION = YES; 479 | CLANG_WARN_INT_CONVERSION = YES; 480 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 481 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 482 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 483 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 484 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 485 | CLANG_WARN_STRICT_PROTOTYPES = YES; 486 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 487 | CLANG_WARN_UNREACHABLE_CODE = YES; 488 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 489 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 490 | COPY_PHASE_STRIP = NO; 491 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 492 | ENABLE_BITCODE = NO; 493 | ENABLE_NS_ASSERTIONS = NO; 494 | ENABLE_STRICT_OBJC_MSGSEND = YES; 495 | GCC_C_LANGUAGE_STANDARD = gnu99; 496 | GCC_NO_COMMON_BLOCKS = YES; 497 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 498 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 499 | GCC_WARN_UNDECLARED_SELECTOR = YES; 500 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 501 | GCC_WARN_UNUSED_FUNCTION = YES; 502 | GCC_WARN_UNUSED_VARIABLE = YES; 503 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 504 | MTL_ENABLE_DEBUG_INFO = NO; 505 | SDKROOT = iphoneos; 506 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 507 | TARGETED_DEVICE_FAMILY = "1,2"; 508 | VALIDATE_PRODUCT = YES; 509 | }; 510 | name = Release; 511 | }; 512 | 59F209231C04C06800CC2392 /* Debug */ = { 513 | isa = XCBuildConfiguration; 514 | buildSettings = { 515 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 516 | CODE_SIGN_IDENTITY = "iPhone Developer"; 517 | ENABLE_BITCODE = NO; 518 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 519 | HEADER_SEARCH_PATHS = ( 520 | "$(inherited)", 521 | "\"${PODS_ROOT}/Headers/Public\"", 522 | "\"${PODS_ROOT}\"/**", 523 | ); 524 | INFOPLIST_FILE = twiliochat/Info.plist; 525 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 526 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 527 | PRODUCT_BUNDLE_IDENTIFIER = twilio.twiliochat; 528 | PRODUCT_NAME = "$(TARGET_NAME)"; 529 | SWIFT_OBJC_BRIDGING_HEADER = "twiliochat/twiliochat-Bridging-Header.h"; 530 | SWIFT_VERSION = 5.0; 531 | }; 532 | name = Debug; 533 | }; 534 | 59F209241C04C06800CC2392 /* Release */ = { 535 | isa = XCBuildConfiguration; 536 | buildSettings = { 537 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 538 | CODE_SIGN_IDENTITY = "iPhone Developer"; 539 | ENABLE_BITCODE = NO; 540 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 541 | HEADER_SEARCH_PATHS = ( 542 | "$(inherited)", 543 | "\"${PODS_ROOT}/Headers/Public\"", 544 | "\"${PODS_ROOT}\"/**", 545 | ); 546 | INFOPLIST_FILE = twiliochat/Info.plist; 547 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 548 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 549 | PRODUCT_BUNDLE_IDENTIFIER = twilio.twiliochat; 550 | PRODUCT_NAME = "$(TARGET_NAME)"; 551 | SWIFT_OBJC_BRIDGING_HEADER = "twiliochat/twiliochat-Bridging-Header.h"; 552 | SWIFT_VERSION = 5.0; 553 | }; 554 | name = Release; 555 | }; 556 | 59F209261C04C06800CC2392 /* Debug */ = { 557 | isa = XCBuildConfiguration; 558 | buildSettings = { 559 | BUNDLE_LOADER = "$(TEST_HOST)"; 560 | DEVELOPMENT_TEAM = ""; 561 | INFOPLIST_FILE = twiliochatTests/Info.plist; 562 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 563 | PRODUCT_BUNDLE_IDENTIFIER = twilio.twiliochatTests; 564 | PRODUCT_NAME = "$(TARGET_NAME)"; 565 | SWIFT_OBJC_BRIDGING_HEADER = ""; 566 | SWIFT_VERSION = 5.0; 567 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/twiliochat.app/twiliochat"; 568 | }; 569 | name = Debug; 570 | }; 571 | 59F209271C04C06800CC2392 /* Release */ = { 572 | isa = XCBuildConfiguration; 573 | buildSettings = { 574 | BUNDLE_LOADER = "$(TEST_HOST)"; 575 | DEVELOPMENT_TEAM = ""; 576 | INFOPLIST_FILE = twiliochatTests/Info.plist; 577 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 578 | PRODUCT_BUNDLE_IDENTIFIER = twilio.twiliochatTests; 579 | PRODUCT_NAME = "$(TARGET_NAME)"; 580 | SWIFT_OBJC_BRIDGING_HEADER = ""; 581 | SWIFT_VERSION = 5.0; 582 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/twiliochat.app/twiliochat"; 583 | }; 584 | name = Release; 585 | }; 586 | /* End XCBuildConfiguration section */ 587 | 588 | /* Begin XCConfigurationList section */ 589 | 59F208F51C04C06800CC2392 /* Build configuration list for PBXProject "twiliochat" */ = { 590 | isa = XCConfigurationList; 591 | buildConfigurations = ( 592 | 59F209201C04C06800CC2392 /* Debug */, 593 | 59F209211C04C06800CC2392 /* Release */, 594 | ); 595 | defaultConfigurationIsVisible = 0; 596 | defaultConfigurationName = Release; 597 | }; 598 | 59F209221C04C06800CC2392 /* Build configuration list for PBXNativeTarget "twiliochat" */ = { 599 | isa = XCConfigurationList; 600 | buildConfigurations = ( 601 | 59F209231C04C06800CC2392 /* Debug */, 602 | 59F209241C04C06800CC2392 /* Release */, 603 | ); 604 | defaultConfigurationIsVisible = 0; 605 | defaultConfigurationName = Release; 606 | }; 607 | 59F209251C04C06800CC2392 /* Build configuration list for PBXNativeTarget "twiliochatTests" */ = { 608 | isa = XCConfigurationList; 609 | buildConfigurations = ( 610 | 59F209261C04C06800CC2392 /* Debug */, 611 | 59F209271C04C06800CC2392 /* Release */, 612 | ); 613 | defaultConfigurationIsVisible = 0; 614 | defaultConfigurationName = Release; 615 | }; 616 | /* End XCConfigurationList section */ 617 | }; 618 | rootObject = 59F208F21C04C06800CC2392 /* Project object */; 619 | } 620 | -------------------------------------------------------------------------------- /twiliochat.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /twiliochat.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /twiliochat.xcworkspace/xcshareddata/xcschemes/twiliochat.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /twiliochat/AlertDialogController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class AlertDialogController: NSObject { 4 | 5 | class func showAlertWithMessage(message:String?, title:String?, presenter:(UIViewController)) { 6 | showAlertWithMessage(message: message, title: title, presenter: presenter, completion: nil) 7 | } 8 | 9 | class func showAlertWithMessage(message:String?, title:String?, 10 | presenter:(UIViewController), completion:(() -> Void)?) { 11 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) 12 | 13 | let defaultAction = UIAlertAction(title: "OK", style: .cancel) { (_) -> Void in 14 | if let block = completion { 15 | block() 16 | } 17 | } 18 | 19 | alert.addAction(defaultAction) 20 | presenter.present(alert, animated: true, completion: nil) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /twiliochat/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @UIApplicationMain 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | var window: UIWindow? 6 | 7 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 8 | MessagingManager.sharedManager().presentLaunchScreen() 9 | MessagingManager.sharedManager().presentRootViewController() 10 | return true 11 | } 12 | 13 | func applicationWillResignActive(_ application: UIApplication) { 14 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 15 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 16 | } 17 | 18 | func applicationDidEnterBackground(_ application: UIApplication) { 19 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 20 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 21 | } 22 | 23 | func applicationWillEnterForeground(_ application: UIApplication) { 24 | // Called as part of the transition from the background to the inactive state here you can undo many of the changes made on entering the background. 25 | } 26 | 27 | func applicationDidBecomeActive(_ application: UIApplication) { 28 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 29 | } 30 | 31 | func applicationWillTerminate(_ application: UIApplication) { 32 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "size" : "29x29", 15 | "idiom" : "iphone", 16 | "filename" : "Icon-29@2x.png", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "Icon-29@3x.png", 23 | "scale" : "3x" 24 | }, 25 | { 26 | "size" : "40x40", 27 | "idiom" : "iphone", 28 | "filename" : "Icon-40@2x-1.png", 29 | "scale" : "2x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "Icon-40@3x.png", 35 | "scale" : "3x" 36 | }, 37 | { 38 | "size" : "60x60", 39 | "idiom" : "iphone", 40 | "filename" : "Icon-60@2x.png", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "size" : "60x60", 45 | "idiom" : "iphone", 46 | "filename" : "Icon-60@3x.png", 47 | "scale" : "3x" 48 | }, 49 | { 50 | "idiom" : "ipad", 51 | "size" : "20x20", 52 | "scale" : "1x" 53 | }, 54 | { 55 | "idiom" : "ipad", 56 | "size" : "20x20", 57 | "scale" : "2x" 58 | }, 59 | { 60 | "size" : "29x29", 61 | "idiom" : "ipad", 62 | "filename" : "Icon-29.png", 63 | "scale" : "1x" 64 | }, 65 | { 66 | "size" : "29x29", 67 | "idiom" : "ipad", 68 | "filename" : "Icon-29@2x-1.png", 69 | "scale" : "2x" 70 | }, 71 | { 72 | "size" : "40x40", 73 | "idiom" : "ipad", 74 | "filename" : "Icon-40.png", 75 | "scale" : "1x" 76 | }, 77 | { 78 | "size" : "40x40", 79 | "idiom" : "ipad", 80 | "filename" : "Icon-40@2x.png", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "size" : "76x76", 85 | "idiom" : "ipad", 86 | "filename" : "Icon-76.png", 87 | "scale" : "1x" 88 | }, 89 | { 90 | "size" : "76x76", 91 | "idiom" : "ipad", 92 | "filename" : "Icon-76@2x.png", 93 | "scale" : "2x" 94 | }, 95 | { 96 | "size" : "83.5x83.5", 97 | "idiom" : "ipad", 98 | "filename" : "Icon-83_5@2x.png", 99 | "scale" : "2x" 100 | } 101 | ], 102 | "info" : { 103 | "version" : 1, 104 | "author" : "xcode" 105 | } 106 | } -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-29.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-29@2x-1.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-40.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-40@2x-1.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-83_5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/AppIcon.appiconset/Icon-83_5@2x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/add-channel-button.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "add-channel-button.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "add-channel-button@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "add-channel-button@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/add-channel-button.imageset/add-channel-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/add-channel-button.imageset/add-channel-button.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/add-channel-button.imageset/add-channel-button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/add-channel-button.imageset/add-channel-button@2x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/add-channel-button.imageset/add-channel-button@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/add-channel-button.imageset/add-channel-button@3x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/btn-login.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "btn-login.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "btn-login@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "btn-login@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/btn-login.imageset/btn-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/btn-login.imageset/btn-login.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/btn-login.imageset/btn-login@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/btn-login.imageset/btn-login@2x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/btn-login.imageset/btn-login@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/btn-login.imageset/btn-login@3x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/channel-list-bg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "channel-list-bg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "channel-list-bg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "channel-list-bg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/channel-list-bg.imageset/channel-list-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/channel-list-bg.imageset/channel-list-bg.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/channel-list-bg.imageset/channel-list-bg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/channel-list-bg.imageset/channel-list-bg@2x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/channel-list-bg.imageset/channel-list-bg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/channel-list-bg.imageset/channel-list-bg@3x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/chat-field-bg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "chat-field-bg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "chat-field-bg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "chat-field-bg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/chat-field-bg.imageset/chat-field-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/chat-field-bg.imageset/chat-field-bg.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/chat-field-bg.imageset/chat-field-bg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/chat-field-bg.imageset/chat-field-bg@2x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/chat-field-bg.imageset/chat-field-bg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/chat-field-bg.imageset/chat-field-bg@3x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/header-bg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "header-bg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "header-bg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "header-bg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/header-bg.imageset/header-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/header-bg.imageset/header-bg.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/header-bg.imageset/header-bg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/header-bg.imageset/header-bg@2x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/header-bg.imageset/header-bg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/header-bg.imageset/header-bg@3x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/home-bg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "home-bg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "home-bg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "home-bg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/home-bg.imageset/home-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/home-bg.imageset/home-bg.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/home-bg.imageset/home-bg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/home-bg.imageset/home-bg@2x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/home-bg.imageset/home-bg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/home-bg.imageset/home-bg@3x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/icon-list.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-list.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-list@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icon-list@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/icon-list.imageset/icon-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/icon-list.imageset/icon-list.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/icon-list.imageset/icon-list@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/icon-list.imageset/icon-list@2x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/icon-list.imageset/icon-list@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/icon-list.imageset/icon-list@3x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/icon-user.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-user.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-user@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icon-user@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/icon-user.imageset/icon-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/icon-user.imageset/icon-user.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/icon-user.imageset/icon-user@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/icon-user.imageset/icon-user@2x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/icon-user.imageset/icon-user@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/icon-user.imageset/icon-user@3x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/landing-logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "landing-logo.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "landing-logo@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "landing-logo@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/landing-logo.imageset/landing-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/landing-logo.imageset/landing-logo.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/landing-logo.imageset/landing-logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/landing-logo.imageset/landing-logo@2x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/landing-logo.imageset/landing-logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/landing-logo.imageset/landing-logo@3x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/login-bg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "login-bg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "login-bg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "login-bg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/login-bg.imageset/login-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/login-bg.imageset/login-bg.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/login-bg.imageset/login-bg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/login-bg.imageset/login-bg@2x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/login-bg.imageset/login-bg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/login-bg.imageset/login-bg@3x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/password-field-bg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "password-field-bg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "password-field-bg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "password-field-bg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/password-field-bg.imageset/password-field-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/password-field-bg.imageset/password-field-bg.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/password-field-bg.imageset/password-field-bg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/password-field-bg.imageset/password-field-bg@2x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/password-field-bg.imageset/password-field-bg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/password-field-bg.imageset/password-field-bg@3x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/user-panel-line.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "user-panel-line.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "user-panel-line@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "user-panel-line@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/user-panel-line.imageset/user-panel-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/user-panel-line.imageset/user-panel-line.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/user-panel-line.imageset/user-panel-line@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/user-panel-line.imageset/user-panel-line@2x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/user-panel-line.imageset/user-panel-line@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/user-panel-line.imageset/user-panel-line@3x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/username-field-bg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "username-field-bg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "username-field-bg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "username-field-bg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/username-field-bg.imageset/username-field-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/username-field-bg.imageset/username-field-bg.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/username-field-bg.imageset/username-field-bg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/username-field-bg.imageset/username-field-bg@2x.png -------------------------------------------------------------------------------- /twiliochat/Assets.xcassets/username-field-bg.imageset/username-field-bg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/twiliochat-swift/adccfd292062c20c26f43bd950a4c278dd88da95/twiliochat/Assets.xcassets/username-field-bg.imageset/username-field-bg@3x.png -------------------------------------------------------------------------------- /twiliochat/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /twiliochat/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 321 | 327 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | -------------------------------------------------------------------------------- /twiliochat/ChannelManager.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | protocol ChannelManagerDelegate { 4 | func reloadChannelDescriptorList() 5 | } 6 | 7 | class ChannelManager: NSObject { 8 | static let sharedManager = ChannelManager() 9 | 10 | static let defaultChannelUniqueName = "general" 11 | static let defaultChannelName = "General Channel" 12 | 13 | var delegate:ChannelManagerDelegate? 14 | 15 | var channelsList:TCHChannels? 16 | var channelDescriptors:NSOrderedSet? 17 | var generalChannel:TCHChannel! 18 | 19 | override init() { 20 | super.init() 21 | channelDescriptors = NSMutableOrderedSet() 22 | } 23 | 24 | // MARK: - General channel 25 | 26 | func joinGeneralChatRoomWithCompletion(completion: @escaping (Bool) -> Void) { 27 | 28 | let uniqueName = ChannelManager.defaultChannelUniqueName 29 | if let channelsList = self.channelsList { 30 | channelsList.channel(withSidOrUniqueName: uniqueName) { result, channel in 31 | self.generalChannel = channel 32 | 33 | if self.generalChannel != nil { 34 | self.joinGeneralChatRoomWithUniqueName(name: nil, completion: completion) 35 | } else { 36 | self.createGeneralChatRoomWithCompletion { succeeded in 37 | if (succeeded) { 38 | self.joinGeneralChatRoomWithUniqueName(name: uniqueName, completion: completion) 39 | return 40 | } 41 | 42 | completion(false) 43 | } 44 | } 45 | } 46 | } 47 | } 48 | 49 | func joinGeneralChatRoomWithUniqueName(name: String?, completion: @escaping (Bool) -> Void) { 50 | generalChannel.join { result in 51 | if ((result.isSuccessful()) && name != nil) { 52 | self.setGeneralChatRoomUniqueNameWithCompletion(completion: completion) 53 | return 54 | } 55 | completion((result.isSuccessful())) 56 | } 57 | } 58 | 59 | func createGeneralChatRoomWithCompletion(completion: @escaping (Bool) -> Void) { 60 | let channelName = ChannelManager.defaultChannelName 61 | let options = [ 62 | TCHChannelOptionFriendlyName: channelName, 63 | TCHChannelOptionType: TCHChannelType.public.rawValue 64 | ] as [String : Any] 65 | channelsList!.createChannel(options: options) { result, channel in 66 | if (result.isSuccessful()) { 67 | self.generalChannel = channel 68 | } 69 | completion((result.isSuccessful())) 70 | } 71 | } 72 | 73 | func setGeneralChatRoomUniqueNameWithCompletion(completion:@escaping (Bool) -> Void) { 74 | generalChannel.setUniqueName(ChannelManager.defaultChannelUniqueName) { result in 75 | completion((result.isSuccessful())) 76 | } 77 | } 78 | 79 | // MARK: - Populate channel Descriptors 80 | 81 | func populateChannelDescriptors() { 82 | 83 | channelsList?.userChannelDescriptors { result, paginator in 84 | guard let paginator = paginator else { 85 | return 86 | } 87 | 88 | let newChannelDescriptors = NSMutableOrderedSet() 89 | newChannelDescriptors.addObjects(from: paginator.items()) 90 | self.channelsList?.publicChannelDescriptors { result, paginator in 91 | guard let paginator = paginator else { 92 | return 93 | } 94 | 95 | // de-dupe channel list 96 | let channelIds = NSMutableSet() 97 | for descriptor in newChannelDescriptors { 98 | if let descriptor = descriptor as? TCHChannelDescriptor { 99 | if let sid = descriptor.sid { 100 | channelIds.add(sid) 101 | } 102 | } 103 | } 104 | for descriptor in paginator.items() { 105 | if let sid = descriptor.sid { 106 | if !channelIds.contains(sid) { 107 | channelIds.add(sid) 108 | newChannelDescriptors.add(descriptor) 109 | } 110 | } 111 | } 112 | 113 | 114 | // sort the descriptors 115 | let sortSelector = #selector(NSString.localizedCaseInsensitiveCompare(_:)) 116 | let descriptor = NSSortDescriptor(key: "friendlyName", ascending: true, selector: sortSelector) 117 | newChannelDescriptors.sort(using: [descriptor]) 118 | 119 | self.channelDescriptors = newChannelDescriptors 120 | 121 | if let delegate = self.delegate { 122 | delegate.reloadChannelDescriptorList() 123 | } 124 | } 125 | } 126 | } 127 | 128 | 129 | // MARK: - Create channel 130 | 131 | func createChannelWithName(name: String, completion: @escaping (Bool, TCHChannel?) -> Void) { 132 | if (name == ChannelManager.defaultChannelName) { 133 | completion(false, nil) 134 | return 135 | } 136 | 137 | let channelOptions = [ 138 | TCHChannelOptionFriendlyName: name, 139 | TCHChannelOptionType: TCHChannelType.public.rawValue 140 | ] as [String : Any] 141 | UIApplication.shared.isNetworkActivityIndicatorVisible = true; 142 | self.channelsList?.createChannel(options: channelOptions) { result, channel in 143 | UIApplication.shared.isNetworkActivityIndicatorVisible = false 144 | completion((result.isSuccessful()), channel) 145 | } 146 | } 147 | } 148 | 149 | // MARK: - TwilioChatClientDelegate 150 | extension ChannelManager : TwilioChatClientDelegate { 151 | func chatClient(_ client: TwilioChatClient, channelAdded channel: TCHChannel) { 152 | DispatchQueue.main.async { 153 | self.populateChannelDescriptors() 154 | } 155 | } 156 | 157 | func chatClient(_ client: TwilioChatClient, channel: TCHChannel, updated: TCHChannelUpdate) { 158 | DispatchQueue.main.async { 159 | self.delegate?.reloadChannelDescriptorList() 160 | } 161 | } 162 | 163 | func chatClient(_ client: TwilioChatClient, channelDeleted channel: TCHChannel) { 164 | DispatchQueue.main.async { 165 | self.populateChannelDescriptors() 166 | } 167 | 168 | } 169 | 170 | func chatClient(_ client: TwilioChatClient, synchronizationStatusUpdated status: TCHClientSynchronizationStatus) { 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /twiliochat/ChatStatusTableCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /twiliochat/ChatTableCell.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ChatTableCell: UITableViewCell { 4 | static let TWCUserLabelTag = 200 5 | static let TWCDateLabelTag = 201 6 | static let TWCMessageLabelTag = 202 7 | 8 | var userLabel: UILabel! 9 | var messageLabel: UILabel! 10 | var dateLabel: UILabel! 11 | 12 | func setUser(user:String!, message:String!, date:String!) { 13 | userLabel.text = user 14 | messageLabel.text = message 15 | dateLabel.text = date 16 | } 17 | 18 | override func awakeFromNib() { 19 | userLabel = viewWithTag(ChatTableCell.TWCUserLabelTag) as? UILabel 20 | messageLabel = viewWithTag(ChatTableCell.TWCMessageLabelTag) as? UILabel 21 | dateLabel = viewWithTag(ChatTableCell.TWCDateLabelTag) as? UILabel 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /twiliochat/ChatTableCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 30 | 36 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /twiliochat/DateTodayFormatter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class DateTodayFormatter { 4 | func stringFromDate(date: NSDate?) -> String? { 5 | guard let date = date else { 6 | return nil 7 | } 8 | 9 | let messageDate = roundDateToDay(date: date) 10 | let todayDate = roundDateToDay(date: NSDate()) 11 | 12 | let formatter = DateFormatter() 13 | 14 | if messageDate == todayDate { 15 | formatter.dateFormat = "'Today' - hh:mma" 16 | } 17 | else { 18 | formatter.dateFormat = "MMM. dd - hh:mma" 19 | } 20 | 21 | return formatter.string(from: date as Date) 22 | } 23 | 24 | func roundDateToDay(date: NSDate) -> NSDate { 25 | let calendar = Calendar.current 26 | let flags = Set([.day, .month, .year]) 27 | let components = calendar.dateComponents(flags, from: date as Date) 28 | 29 | return calendar.date(from:components)! as NSDate 30 | } 31 | } 32 | 33 | extension NSDate { 34 | class func dateWithISO8601String(dateString: String) -> NSDate? { 35 | var formattedDateString = dateString 36 | 37 | if dateString.hasSuffix("Z") { 38 | let lastIndex = dateString.indices.last! 39 | formattedDateString = dateString.substring(to: lastIndex) + "-000" 40 | } 41 | return dateFromString(str: formattedDateString, withFormat:"yyyy-MM-dd'T'HH:mm:ss.SSSZ") 42 | } 43 | 44 | class func dateFromString(str: String, withFormat dateFormat: String) -> NSDate? { 45 | let formatter = DateFormatter() 46 | formatter.dateFormat = dateFormat 47 | formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX") as Locale? 48 | return formatter.date(from: str) as NSDate? 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /twiliochat/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | CFBundleDevelopmentRegion 11 | en 12 | CFBundleExecutable 13 | $(EXECUTABLE_NAME) 14 | CFBundleIdentifier 15 | $(PRODUCT_BUNDLE_IDENTIFIER) 16 | CFBundleInfoDictionaryVersion 17 | 6.0 18 | CFBundleName 19 | $(PRODUCT_NAME) 20 | CFBundlePackageType 21 | APPL 22 | CFBundleShortVersionString 23 | 1.0 24 | CFBundleSignature 25 | ???? 26 | CFBundleVersion 27 | 1 28 | LSRequiresIPhoneOS 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /twiliochat/InputDialogController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class InputDialogController: NSObject { 4 | 5 | var saveAction: UIAlertAction! 6 | 7 | class func showWithTitle(title: String, message: String, 8 | placeholder: String, presenter: UIViewController, handler: @escaping (String) -> Void) { 9 | InputDialogController().showWithTitle(title: title, message: message, 10 | placeholder: placeholder, presenter: presenter, handler: handler) 11 | } 12 | 13 | func showWithTitle(title: String, message: String, placeholder: String, 14 | presenter: UIViewController, handler: @escaping (String) -> Void) { 15 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) 16 | 17 | let defaultAction = UIAlertAction(title: "Cancel", style: .cancel) { action in 18 | self.removeTextFieldObserver() 19 | } 20 | 21 | saveAction = UIAlertAction(title: "Save", style: .default) { action in 22 | self.removeTextFieldObserver() 23 | let textFieldText = alert.textFields![0].text ?? String() 24 | handler(textFieldText) 25 | } 26 | 27 | saveAction.isEnabled = false 28 | 29 | alert.addTextField { textField in 30 | textField.placeholder = placeholder 31 | NotificationCenter.default.addObserver(self, 32 | selector: #selector(InputDialogController.handleTextFieldTextDidChangeNotification(notification:)), 33 | name: UITextField.textDidChangeNotification, 34 | object: nil) 35 | } 36 | 37 | alert.addAction(defaultAction) 38 | alert.addAction(saveAction) 39 | presenter.present(alert, animated: true, completion: nil) 40 | } 41 | 42 | @objc func handleTextFieldTextDidChangeNotification(notification: NSNotification) { 43 | let textField = notification.object as? UITextField 44 | saveAction.isEnabled = !(textField!.text?.isEmpty ?? false) 45 | } 46 | 47 | func removeTextFieldObserver() { 48 | NotificationCenter.default.removeObserver(self) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /twiliochat/Keys.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TokenRequestUrl 6 | http://localhost:8000/token 7 | 8 | 9 | -------------------------------------------------------------------------------- /twiliochat/LoginViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class LoginViewController: UIViewController { 4 | @IBOutlet weak var loginButton: UIButton! 5 | @IBOutlet weak var usernameTextField: UITextField! 6 | @IBOutlet weak var activityIndicator: UIActivityIndicatorView! 7 | 8 | // MARK: - Injectable Properties 9 | 10 | var alertDialogControllerClass = AlertDialogController.self 11 | var MessagingClientClass = MessagingManager.self 12 | 13 | // MARK: - Initialization 14 | 15 | var textFieldFormHandler: TextFieldFormHandler! 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | initializeTextFields() 20 | } 21 | 22 | func initializeTextFields() { 23 | let textFields: [UITextField] = [usernameTextField] 24 | textFieldFormHandler = TextFieldFormHandler(withTextFields: textFields, topContainer: view) 25 | textFieldFormHandler.delegate = self 26 | } 27 | 28 | func resetFirstResponderOnSignUpModeChange() { 29 | self.view.layoutSubviews() 30 | 31 | if let index = self.textFieldFormHandler.firstResponderIndex { 32 | if (index > 1) { 33 | textFieldFormHandler.setTextFieldAtIndexAsFirstResponder(index: 1) 34 | } 35 | else { 36 | textFieldFormHandler.resetScroll() 37 | } 38 | } 39 | } 40 | 41 | override func viewWillDisappear(_ animated: Bool) { 42 | super.viewWillDisappear(animated) 43 | textFieldFormHandler.cleanUp() 44 | } 45 | 46 | override func didReceiveMemoryWarning() { 47 | super.didReceiveMemoryWarning() 48 | } 49 | 50 | // MARK: - Actions 51 | 52 | @IBAction func loginButtonTouched(_ sender: UIButton) { 53 | loginUser() 54 | } 55 | 56 | // MARK: - Login 57 | 58 | func loginUser() { 59 | if (validUserData()) { 60 | view.isUserInteractionEnabled = false 61 | activityIndicator.startAnimating() 62 | 63 | let MessagingManager = MessagingClientClass.sharedManager() 64 | if let username = usernameTextField.text { 65 | MessagingManager.loginWithUsername(username: username, completion: handleResponse) 66 | } 67 | } 68 | } 69 | 70 | func validUserData() -> Bool { 71 | if let usernameEmpty = usernameTextField.text?.isEmpty, !usernameEmpty { 72 | return true 73 | } 74 | showError(message: "All fields are required") 75 | return false 76 | } 77 | 78 | func showError(message:String) { 79 | alertDialogControllerClass.showAlertWithMessage(message: message, title: nil, presenter: self) 80 | } 81 | 82 | func handleResponse(succeeded: Bool, error: NSError?) { 83 | DispatchQueue.main.async { 84 | self.activityIndicator.stopAnimating() 85 | if let error = error, !succeeded { 86 | self.showError(message: error.localizedDescription) 87 | } 88 | self.view.isUserInteractionEnabled = true 89 | } 90 | } 91 | 92 | // MARK: - Style 93 | 94 | override var preferredStatusBarStyle: UIStatusBarStyle { 95 | return .lightContent 96 | } 97 | 98 | override var shouldAutorotate: Bool { 99 | return true 100 | } 101 | 102 | override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 103 | if (UI_USER_INTERFACE_IDIOM() == .pad) { 104 | return .all 105 | } 106 | return .portrait 107 | } 108 | 109 | override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { 110 | return .portrait 111 | } 112 | } 113 | 114 | // MARK: - TextFieldFormHandlerDelegate 115 | extension LoginViewController : TextFieldFormHandlerDelegate { 116 | func textFieldFormHandlerDoneEnteringData(handler: TextFieldFormHandler) { 117 | loginUser() 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /twiliochat/MainChatViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SlackTextViewController 3 | 4 | class MainChatViewController: SLKTextViewController { 5 | static let TWCChatCellIdentifier = "ChatTableCell" 6 | static let TWCChatStatusCellIdentifier = "ChatStatusTableCell" 7 | 8 | static let TWCOpenGeneralChannelSegue = "OpenGeneralChat" 9 | static let TWCLabelTag = 200 10 | 11 | var _channel:TCHChannel! 12 | var channel:TCHChannel! { 13 | get { 14 | return _channel 15 | } 16 | set(channel) { 17 | _channel = channel 18 | title = _channel.friendlyName 19 | _channel.delegate = self 20 | 21 | if _channel == ChannelManager.sharedManager.generalChannel { 22 | navigationItem.rightBarButtonItem = nil 23 | } 24 | 25 | joinChannel() 26 | } 27 | } 28 | 29 | var messages:Set = Set() 30 | var sortedMessages:[TCHMessage]! 31 | 32 | @IBOutlet weak var revealButtonItem: UIBarButtonItem! 33 | @IBOutlet weak var actionButtonItem: UIBarButtonItem! 34 | 35 | override func viewDidLoad() { 36 | super.viewDidLoad() 37 | 38 | if (revealViewController() != nil) { 39 | revealButtonItem.target = revealViewController() 40 | revealButtonItem.action = #selector(SWRevealViewController.revealToggle(_:)) 41 | navigationController?.navigationBar.addGestureRecognizer(revealViewController().panGestureRecognizer()) 42 | revealViewController().rearViewRevealOverdraw = 0 43 | } 44 | 45 | bounces = true 46 | shakeToClearEnabled = true 47 | isKeyboardPanningEnabled = true 48 | shouldScrollToBottomAfterKeyboardShows = false 49 | isInverted = true 50 | 51 | let cellNib = UINib(nibName: MainChatViewController.TWCChatCellIdentifier, bundle: nil) 52 | tableView!.register(cellNib, forCellReuseIdentifier:MainChatViewController.TWCChatCellIdentifier) 53 | 54 | let cellStatusNib = UINib(nibName: MainChatViewController.TWCChatStatusCellIdentifier, bundle: nil) 55 | tableView!.register(cellStatusNib, forCellReuseIdentifier:MainChatViewController.TWCChatStatusCellIdentifier) 56 | 57 | textInputbar.autoHideRightButton = true 58 | textInputbar.maxCharCount = 256 59 | textInputbar.counterStyle = .split 60 | textInputbar.counterPosition = .top 61 | 62 | let font = UIFont(name:"Avenir-Light", size:14) 63 | textView.font = font 64 | 65 | rightButton.setTitleColor(UIColor(red:0.973, green:0.557, blue:0.502, alpha:1), for: .normal) 66 | 67 | if let font = UIFont(name:"Avenir-Heavy", size:17) { 68 | navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.font: font] 69 | } 70 | 71 | tableView!.allowsSelection = false 72 | tableView!.estimatedRowHeight = 70 73 | tableView!.rowHeight = UITableView.automaticDimension 74 | tableView!.separatorStyle = .none 75 | 76 | if channel == nil { 77 | channel = ChannelManager.sharedManager.generalChannel 78 | } 79 | } 80 | 81 | override func viewDidLayoutSubviews() { 82 | super.viewDidLayoutSubviews() 83 | 84 | // required for iOS 11 85 | textInputbar.bringSubviewToFront(textInputbar.textView) 86 | textInputbar.bringSubviewToFront(textInputbar.leftButton) 87 | textInputbar.bringSubviewToFront(textInputbar.rightButton) 88 | 89 | } 90 | 91 | override func viewDidAppear(_ animated: Bool) { 92 | super.viewDidAppear(animated) 93 | scrollToBottom() 94 | } 95 | 96 | override func numberOfSections(in tableView: UITableView) -> Int { 97 | return 1 98 | } 99 | 100 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: NSInteger) -> Int { 101 | return messages.count 102 | } 103 | 104 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 105 | var cell:UITableViewCell 106 | 107 | let message = sortedMessages[indexPath.row] 108 | 109 | if let statusMessage = message as? StatusMessage { 110 | cell = getStatusCellForTableView(tableView: tableView, forIndexPath:indexPath, message:statusMessage) 111 | } 112 | else { 113 | cell = getChatCellForTableView(tableView: tableView, forIndexPath:indexPath, message:message) 114 | } 115 | 116 | cell.transform = tableView.transform 117 | return cell 118 | } 119 | 120 | func getChatCellForTableView(tableView: UITableView, forIndexPath indexPath:IndexPath, message: TCHMessage) -> UITableViewCell { 121 | let cell = tableView.dequeueReusableCell(withIdentifier: MainChatViewController.TWCChatCellIdentifier, for:indexPath as IndexPath) 122 | 123 | let chatCell: ChatTableCell = cell as! ChatTableCell 124 | let date = NSDate.dateWithISO8601String(dateString: message.dateCreated ?? "") 125 | let timestamp = DateTodayFormatter().stringFromDate(date: date) 126 | 127 | chatCell.setUser(user: message.author ?? "[Unknown author]", message: message.body, date: timestamp ?? "[Unknown date]") 128 | 129 | return chatCell 130 | } 131 | 132 | func getStatusCellForTableView(tableView: UITableView, forIndexPath indexPath:IndexPath, message: StatusMessage) -> UITableViewCell { 133 | let cell = tableView.dequeueReusableCell(withIdentifier: MainChatViewController.TWCChatStatusCellIdentifier, for:indexPath as IndexPath) 134 | 135 | let label = cell.viewWithTag(MainChatViewController.TWCLabelTag) as! UILabel 136 | let memberStatus = (message.status! == .Joined) ? "joined" : "left" 137 | label.text = "User \(message.statusMember.identity ?? "[Unknown user]") has \(memberStatus)" 138 | return cell 139 | } 140 | 141 | func joinChannel() { 142 | setViewOnHold(onHold: true) 143 | 144 | if channel.status != .joined { 145 | channel.join { result in 146 | print("Channel Joined") 147 | } 148 | return 149 | } 150 | 151 | loadMessages() 152 | setViewOnHold(onHold: false) 153 | } 154 | 155 | // Disable user input and show activity indicator 156 | func setViewOnHold(onHold: Bool) { 157 | self.isTextInputbarHidden = onHold; 158 | UIApplication.shared.isNetworkActivityIndicatorVisible = onHold; 159 | } 160 | 161 | override func didPressRightButton(_ sender: Any!) { 162 | textView.refreshFirstResponder() 163 | sendMessage(inputMessage: textView.text) 164 | super.didPressRightButton(sender) 165 | } 166 | 167 | // MARK: - Chat Service 168 | 169 | func sendMessage(inputMessage: String) { 170 | let messageOptions = TCHMessageOptions().withBody(inputMessage) 171 | channel.messages?.sendMessage(with: messageOptions, completion: nil) 172 | } 173 | 174 | func addMessages(newMessages:Set) { 175 | messages = messages.union(newMessages) 176 | sortMessages() 177 | DispatchQueue.main.async { 178 | self.tableView!.reloadData() 179 | if self.messages.count > 0 { 180 | self.scrollToBottom() 181 | } 182 | } 183 | } 184 | 185 | func sortMessages() { 186 | sortedMessages = messages.sorted { (a, b) -> Bool in 187 | (a.dateCreated ?? "") > (b.dateCreated ?? "") 188 | } 189 | } 190 | 191 | func loadMessages() { 192 | messages.removeAll() 193 | if channel.synchronizationStatus == .all { 194 | channel.messages?.getLastWithCount(100) { (result, items) in 195 | self.addMessages(newMessages: Set(items!)) 196 | } 197 | } 198 | } 199 | 200 | func scrollToBottom() { 201 | if messages.count > 0 { 202 | let indexPath = IndexPath(row: 0, section: 0) 203 | tableView!.scrollToRow(at: indexPath, at: .bottom, animated: true) 204 | } 205 | } 206 | 207 | func leaveChannel() { 208 | channel.leave { result in 209 | if (result.isSuccessful()) { 210 | let menuViewController = self.revealViewController().rearViewController as! MenuViewController 211 | menuViewController.deselectSelectedChannel() 212 | self.revealViewController().rearViewController.performSegue(withIdentifier: MainChatViewController.TWCOpenGeneralChannelSegue, sender: nil) 213 | } 214 | } 215 | } 216 | 217 | // MARK: - Actions 218 | 219 | @IBAction func actionButtonTouched(_ sender: UIBarButtonItem) { 220 | leaveChannel() 221 | } 222 | 223 | @IBAction func revealButtonTouched(_ sender: AnyObject) { 224 | revealViewController().revealToggle(animated: true) 225 | } 226 | } 227 | 228 | extension MainChatViewController : TCHChannelDelegate { 229 | func chatClient(_ client: TwilioChatClient, channel: TCHChannel, messageAdded message: TCHMessage) { 230 | if !messages.contains(message) { 231 | addMessages(newMessages: [message]) 232 | } 233 | } 234 | 235 | func chatClient(_ client: TwilioChatClient, channel: TCHChannel, memberJoined member: TCHMember) { 236 | addMessages(newMessages: [StatusMessage(statusMember:member, status:.Joined)]) 237 | } 238 | 239 | func chatClient(_ client: TwilioChatClient, channel: TCHChannel, memberLeft member: TCHMember) { 240 | addMessages(newMessages: [StatusMessage(statusMember:member, status:.Left)]) 241 | } 242 | 243 | func chatClient(_ client: TwilioChatClient, channelDeleted channel: TCHChannel) { 244 | DispatchQueue.main.async { 245 | if channel == self.channel { 246 | self.revealViewController().rearViewController 247 | .performSegue(withIdentifier: MainChatViewController.TWCOpenGeneralChannelSegue, sender: nil) 248 | } 249 | } 250 | } 251 | 252 | func chatClient(_ client: TwilioChatClient, 253 | channel: TCHChannel, 254 | synchronizationStatusUpdated status: TCHChannelSynchronizationStatus) { 255 | if status == .all { 256 | loadMessages() 257 | DispatchQueue.main.async { 258 | self.tableView?.reloadData() 259 | self.setViewOnHold(onHold: false) 260 | } 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /twiliochat/MenuTableCell.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class MenuTableCell: UITableViewCell { 4 | var label: UILabel! 5 | 6 | var channelName: String { 7 | get { 8 | return label?.text ?? String() 9 | } 10 | set(name) { 11 | label.text = name 12 | } 13 | } 14 | 15 | var selectedBackgroundColor: UIColor { 16 | return UIColor(red:0.969, green:0.902, blue:0.894, alpha:1) 17 | } 18 | 19 | var labelHighlightedTextColor: UIColor { 20 | return UIColor(red:0.22, green:0.024, blue:0.016, alpha:1) 21 | } 22 | 23 | var labelTextColor: UIColor { 24 | return UIColor(red:0.973, green:0.557, blue:0.502, alpha:1) 25 | } 26 | 27 | override func awakeFromNib() { 28 | label = viewWithTag(200) as? UILabel 29 | label.highlightedTextColor = labelHighlightedTextColor 30 | label.textColor = labelTextColor 31 | } 32 | 33 | override func setSelected(_ selected: Bool, animated: Bool) { 34 | super.setSelected(selected, animated: animated) 35 | if (selected) { 36 | contentView.backgroundColor = selectedBackgroundColor 37 | } else { 38 | contentView.backgroundColor = nil 39 | } 40 | } 41 | 42 | override func setHighlighted(_ highlighted: Bool, animated: Bool) { 43 | super.setHighlighted(highlighted, animated: animated) 44 | if (highlighted) { 45 | contentView.backgroundColor = selectedBackgroundColor 46 | } else { 47 | contentView.backgroundColor = nil 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /twiliochat/MenuViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class MenuViewController: UIViewController { 4 | static let TWCOpenChannelSegue = "OpenChat" 5 | static let TWCRefreshControlXOffset: CGFloat = 120 6 | 7 | @IBOutlet weak var tableView: UITableView! 8 | @IBOutlet weak var usernameLabel: UILabel! 9 | 10 | var refreshControl: UIRefreshControl! 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | let bgImage = UIImageView(image: UIImage(named:"home-bg")) 16 | bgImage.frame = self.tableView.frame 17 | tableView.backgroundView = bgImage 18 | 19 | usernameLabel.text = MessagingManager.sharedManager().userIdentity 20 | 21 | refreshControl = UIRefreshControl() 22 | tableView.addSubview(refreshControl) 23 | refreshControl.addTarget(self, action: #selector(MenuViewController.refreshChannels), for: .valueChanged) 24 | refreshControl.tintColor = UIColor.white 25 | 26 | self.refreshControl.frame.origin.x -= MenuViewController.TWCRefreshControlXOffset 27 | ChannelManager.sharedManager.delegate = self 28 | tableView.reloadData() 29 | } 30 | 31 | // MARK: - Internal methods 32 | 33 | func loadingCellForTableView(tableView: UITableView) -> UITableViewCell { 34 | return tableView.dequeueReusableCell(withIdentifier: "loadingCell")! 35 | } 36 | 37 | func channelCellForTableView(tableView: UITableView, atIndexPath indexPath: NSIndexPath) -> UITableViewCell { 38 | let menuCell = tableView.dequeueReusableCell(withIdentifier: "channelCell", for: indexPath as IndexPath) as! MenuTableCell 39 | 40 | if let channelDescriptor = ChannelManager.sharedManager.channelDescriptors![indexPath.row] as? TCHChannelDescriptor { 41 | menuCell.channelName = channelDescriptor.friendlyName ?? "[Unknown channel name]" 42 | } else { 43 | menuCell.channelName = "[Unknown channel name]" 44 | } 45 | 46 | return menuCell 47 | } 48 | 49 | @objc func refreshChannels() { 50 | refreshControl.beginRefreshing() 51 | tableView.reloadData() 52 | refreshControl.endRefreshing() 53 | } 54 | 55 | func deselectSelectedChannel() { 56 | let selectedRow = tableView.indexPathForSelectedRow 57 | if let row = selectedRow { 58 | tableView.deselectRow(at: row, animated: true) 59 | } 60 | } 61 | 62 | // MARK: - Channel 63 | 64 | func createNewChannelDialog() { 65 | InputDialogController.showWithTitle(title: "New Channel", 66 | message: "Enter a name for this channel", 67 | placeholder: "Name", 68 | presenter: self) { text in 69 | ChannelManager.sharedManager.createChannelWithName(name: text, completion: { _,_ in 70 | ChannelManager.sharedManager.populateChannelDescriptors() 71 | }) 72 | } 73 | } 74 | 75 | // MARK: Logout 76 | 77 | func promptLogout() { 78 | let alert = UIAlertController(title: nil, message: "You are about to Logout", preferredStyle: .alert) 79 | 80 | let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) 81 | let confirmAction = UIAlertAction(title: "Confirm", style: .default) { action in 82 | self.logOut() 83 | } 84 | 85 | alert.addAction(cancelAction) 86 | alert.addAction(confirmAction) 87 | present(alert, animated: true, completion: nil) 88 | } 89 | 90 | func logOut() { 91 | MessagingManager.sharedManager().logout() 92 | MessagingManager.sharedManager().presentRootViewController() 93 | } 94 | 95 | // MARK: - Actions 96 | 97 | @IBAction func logoutButtonTouched(_ sender: UIButton) { 98 | promptLogout() 99 | } 100 | 101 | @IBAction func newChannelButtonTouched(_ sender: UIButton) { 102 | createNewChannelDialog() 103 | } 104 | 105 | // MARK: - Navigation 106 | 107 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 108 | if segue.identifier == MenuViewController.TWCOpenChannelSegue { 109 | let indexPath = sender as! NSIndexPath 110 | 111 | let channelDescriptor = ChannelManager.sharedManager.channelDescriptors![indexPath.row] as! TCHChannelDescriptor 112 | let navigationController = segue.destination as! UINavigationController 113 | 114 | channelDescriptor.channel { (result, channel) in 115 | if let channel = channel { 116 | (navigationController.visibleViewController as! MainChatViewController).channel = channel 117 | } 118 | } 119 | 120 | } 121 | } 122 | 123 | // MARK: - Style 124 | 125 | override var preferredStatusBarStyle: UIStatusBarStyle { 126 | return .lightContent 127 | } 128 | } 129 | 130 | // MARK: - UITableViewDataSource 131 | extension MenuViewController : UITableViewDataSource { 132 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 133 | if let channelDescriptors = ChannelManager.sharedManager.channelDescriptors { 134 | print (channelDescriptors.count) 135 | return channelDescriptors.count 136 | } 137 | return 1 138 | } 139 | 140 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 141 | let cell: UITableViewCell 142 | 143 | if ChannelManager.sharedManager.channelDescriptors == nil { 144 | cell = loadingCellForTableView(tableView: tableView) 145 | } 146 | else { 147 | cell = channelCellForTableView(tableView: tableView, atIndexPath: indexPath as NSIndexPath) 148 | } 149 | 150 | cell.layoutIfNeeded() 151 | return cell 152 | } 153 | 154 | func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { 155 | if let channel = ChannelManager.sharedManager.channelDescriptors?.object(at: indexPath.row) as? TCHChannel { 156 | return channel != ChannelManager.sharedManager.generalChannel 157 | } 158 | return false 159 | } 160 | 161 | func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, 162 | forRowAt indexPath: IndexPath) { 163 | if editingStyle != .delete { 164 | return 165 | } 166 | if let channel = ChannelManager.sharedManager.channelDescriptors?.object(at: indexPath.row) as? TCHChannel { 167 | channel.destroy { result in 168 | if (result.isSuccessful()) { 169 | tableView.reloadData() 170 | } 171 | else { 172 | AlertDialogController.showAlertWithMessage(message: "You can not delete this channel", title: nil, presenter: self) 173 | } 174 | } 175 | } 176 | } 177 | } 178 | 179 | // MARK: - UITableViewDelegate 180 | extension MenuViewController : UITableViewDelegate { 181 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 182 | tableView.deselectRow(at: indexPath, animated: true) 183 | performSegue(withIdentifier: MenuViewController.TWCOpenChannelSegue, sender: indexPath) 184 | } 185 | } 186 | 187 | 188 | // MARK: - ChannelManagerDelegate 189 | extension MenuViewController : ChannelManagerDelegate { 190 | func reloadChannelDescriptorList() { 191 | tableView.reloadData() 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /twiliochat/MessagingManager.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class MessagingManager: NSObject { 4 | 5 | static let _sharedManager = MessagingManager() 6 | 7 | var client:TwilioChatClient? 8 | var delegate:ChannelManager? 9 | var connected = false 10 | 11 | var userIdentity:String { 12 | return SessionManager.getUsername() 13 | } 14 | 15 | var hasIdentity: Bool { 16 | return SessionManager.isLoggedIn() 17 | } 18 | 19 | override init() { 20 | super.init() 21 | delegate = ChannelManager.sharedManager 22 | } 23 | 24 | class func sharedManager() -> MessagingManager { 25 | return _sharedManager 26 | } 27 | 28 | func presentRootViewController() { 29 | if (!hasIdentity) { 30 | presentViewControllerByName(viewController: "LoginViewController") 31 | return 32 | } 33 | 34 | if (!connected) { 35 | connectClientWithCompletion { success, error in 36 | print("Delegate method will load views when sync is complete") 37 | if (!success || error != nil) { 38 | DispatchQueue.main.async { 39 | self.presentViewControllerByName(viewController: "LoginViewController") 40 | } 41 | } 42 | } 43 | return 44 | } 45 | 46 | presentViewControllerByName(viewController: "RevealViewController") 47 | } 48 | 49 | func presentViewControllerByName(viewController: String) { 50 | presentViewController(controller: storyBoardWithName(name: "Main").instantiateViewController(withIdentifier: viewController)) 51 | } 52 | 53 | func presentLaunchScreen() { 54 | presentViewController(controller: storyBoardWithName(name: "LaunchScreen").instantiateInitialViewController()!) 55 | } 56 | 57 | func presentViewController(controller: UIViewController) { 58 | let window = UIApplication.shared.delegate!.window!! 59 | window.rootViewController = controller 60 | } 61 | 62 | func storyBoardWithName(name:String) -> UIStoryboard { 63 | return UIStoryboard(name:name, bundle: Bundle.main) 64 | } 65 | 66 | // MARK: User and session management 67 | 68 | func loginWithUsername(username: String, 69 | completion: @escaping (Bool, NSError?) -> Void) { 70 | SessionManager.loginWithUsername(username: username) 71 | connectClientWithCompletion(completion: completion) 72 | } 73 | 74 | func logout() { 75 | SessionManager.logout() 76 | DispatchQueue.global(qos: .userInitiated).async { 77 | self.client?.shutdown() 78 | self.client = nil 79 | } 80 | self.connected = false 81 | } 82 | 83 | // MARK: Twilio Client 84 | 85 | func loadGeneralChatRoomWithCompletion(completion:@escaping (Bool, NSError?) -> Void) { 86 | ChannelManager.sharedManager.joinGeneralChatRoomWithCompletion { succeeded in 87 | if succeeded { 88 | completion(succeeded, nil) 89 | } 90 | else { 91 | let error = self.errorWithDescription(description: "Could not join General channel", code: 300) 92 | completion(succeeded, error) 93 | } 94 | } 95 | } 96 | 97 | func connectClientWithCompletion(completion: @escaping (Bool, NSError?) -> Void) { 98 | if (client != nil) { 99 | logout() 100 | } 101 | 102 | requestTokenWithCompletion { succeeded, token in 103 | if let token = token, succeeded { 104 | self.initializeClientWithToken(token: token) 105 | completion(succeeded, nil) 106 | } 107 | else { 108 | let error = self.errorWithDescription(description: "Could not get access token", code:301) 109 | completion(succeeded, error) 110 | } 111 | } 112 | } 113 | 114 | func initializeClientWithToken(token: String) { 115 | DispatchQueue.main.async { 116 | UIApplication.shared.isNetworkActivityIndicatorVisible = true 117 | } 118 | TwilioChatClient.chatClient(withToken: token, properties: nil, delegate: self) { [weak self] result, chatClient in 119 | guard (result.isSuccessful()) else { return } 120 | 121 | UIApplication.shared.isNetworkActivityIndicatorVisible = true 122 | self?.connected = true 123 | self?.client = chatClient 124 | } 125 | } 126 | 127 | func requestTokenWithCompletion(completion:@escaping (Bool, String?) -> Void) { 128 | if let device = UIDevice.current.identifierForVendor?.uuidString { 129 | TokenRequestHandler.fetchToken(params: ["device": device, "identity":SessionManager.getUsername()]) {response,error in 130 | var token: String? 131 | token = response["token"] as? String 132 | completion(token != nil, token) 133 | } 134 | } 135 | } 136 | 137 | func errorWithDescription(description: String, code: Int) -> NSError { 138 | let userInfo = [NSLocalizedDescriptionKey : description] 139 | return NSError(domain: "app", code: code, userInfo: userInfo) 140 | } 141 | } 142 | 143 | // MARK: - TwilioChatClientDelegate 144 | extension MessagingManager : TwilioChatClientDelegate { 145 | func chatClient(_ client: TwilioChatClient, channelAdded channel: TCHChannel) { 146 | self.delegate?.chatClient(client, channelAdded: channel) 147 | } 148 | 149 | func chatClient(_ client: TwilioChatClient, channel: TCHChannel, updated: TCHChannelUpdate) { 150 | self.delegate?.chatClient(client, channel: channel, updated: updated) 151 | } 152 | 153 | func chatClient(_ client: TwilioChatClient, channelDeleted channel: TCHChannel) { 154 | self.delegate?.chatClient(client, channelDeleted: channel) 155 | } 156 | 157 | func chatClient(_ client: TwilioChatClient, synchronizationStatusUpdated status: TCHClientSynchronizationStatus) { 158 | if status == TCHClientSynchronizationStatus.completed { 159 | UIApplication.shared.isNetworkActivityIndicatorVisible = false 160 | ChannelManager.sharedManager.channelsList = client.channelsList() 161 | ChannelManager.sharedManager.populateChannelDescriptors() 162 | loadGeneralChatRoomWithCompletion { success, error in 163 | if success { 164 | self.presentRootViewController() 165 | } 166 | } 167 | } 168 | self.delegate?.chatClient(client, synchronizationStatusUpdated: status) 169 | } 170 | 171 | func chatClientTokenWillExpire(_ client: TwilioChatClient) { 172 | requestTokenWithCompletion { succeeded, token in 173 | if (succeeded) { 174 | client.updateToken(token!) 175 | } 176 | else { 177 | print("Error while trying to get new access token") 178 | } 179 | } 180 | } 181 | 182 | func chatClientTokenExpired(_ client: TwilioChatClient) { 183 | requestTokenWithCompletion { succeeded, token in 184 | if (succeeded) { 185 | client.updateToken(token!) 186 | } 187 | else { 188 | print("Error while trying to get new access token") 189 | } 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /twiliochat/SessionManager.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class SessionManager { 4 | static let UsernameKey: String = "username" 5 | static let IsLoggedInKey: String = "loggedIn" 6 | static let defaults = UserDefaults.standard 7 | 8 | class func loginWithUsername(username:String) { 9 | defaults.set(username, forKey: UsernameKey) 10 | defaults.set(true, forKey: IsLoggedInKey) 11 | 12 | defaults.synchronize() 13 | } 14 | 15 | class func logout() { 16 | defaults.set("", forKey: UsernameKey) 17 | defaults.set(false, forKey: IsLoggedInKey) 18 | defaults.synchronize() 19 | } 20 | 21 | class func isLoggedIn() -> Bool { 22 | let isLoggedIn = defaults.bool(forKey: IsLoggedInKey) 23 | if (isLoggedIn) { 24 | return true 25 | } 26 | return false 27 | } 28 | 29 | class func getUsername() -> String { 30 | if let username = defaults.object(forKey: UsernameKey) as? String { 31 | return username 32 | } 33 | return "" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /twiliochat/StatusMessage.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import UIKit 3 | 4 | enum TWCMemberStatus { 5 | case Joined 6 | case Left 7 | } 8 | 9 | class StatusMessage: TCHMessage { 10 | var status: TWCMemberStatus! = nil 11 | var statusMember: TCHMember! = nil 12 | 13 | var _dateCreated: String = "" 14 | override var dateCreated: String { 15 | get { 16 | return _dateCreated 17 | } 18 | set(newDateCreated) { 19 | _dateCreated = newDateCreated 20 | } 21 | } 22 | 23 | init(statusMember: TCHMember, status: TWCMemberStatus) { 24 | super.init() 25 | let dateFormatter = DateFormatter() 26 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" 27 | dateFormatter.timeZone = NSTimeZone(forSecondsFromGMT: 0) as TimeZone? 28 | dateCreated = dateFormatter.string(from: NSDate() as Date) 29 | self.statusMember = statusMember 30 | self.status = status 31 | } 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /twiliochat/TextFieldFormHandler.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @objc public protocol TextFieldFormHandlerDelegate { 4 | @objc optional func textFieldFormHandlerDoneEnteringData(handler: TextFieldFormHandler) 5 | } 6 | 7 | public class TextFieldFormHandler: NSObject { 8 | var textFields: [UITextField]! 9 | var keyboardSize: CGFloat = 0 10 | var animationOffset: CGFloat = 0 11 | var topContainer: UIView! 12 | 13 | var _lastTextField: UITextField? 14 | var lastTextField: UITextField? { 15 | get { 16 | return _lastTextField 17 | } 18 | set (newValue){ 19 | if let textField = _lastTextField { 20 | setTextField(textField: textField, returnKeyType: .next) 21 | } 22 | _lastTextField = newValue 23 | 24 | if let textField = newValue { 25 | setTextField(textField: textField, returnKeyType: .done) 26 | } 27 | else if let textField = textFields.last { 28 | setTextField(textField: textField, returnKeyType: .done) 29 | } 30 | } 31 | } 32 | 33 | public weak var delegate: TextFieldFormHandlerDelegate? 34 | 35 | public var firstResponderIndex: Int? { 36 | if let firstResponder = self.firstResponder { 37 | return self.textFields.firstIndex(of: firstResponder) 38 | } 39 | return nil 40 | } 41 | 42 | public var firstResponder: UITextField? { 43 | return self.textFields.filter { textField in textField.isFirstResponder }.first 44 | } 45 | 46 | // MARK: - Initialization 47 | 48 | init(withTextFields textFields: [UITextField], topContainer: UIView) { 49 | super.init() 50 | self.textFields = textFields 51 | self.topContainer = topContainer 52 | initializeTextFields() 53 | initializeObservers() 54 | } 55 | 56 | func initializeTextFields() { 57 | for (index, textField) in self.textFields.enumerated() { 58 | textField.delegate = self 59 | setTextField(textField: textField, returnKeyType: (index == self.textFields.count - 1 ? .done : .next)) 60 | } 61 | } 62 | 63 | func initializeObservers() { 64 | NotificationCenter.default.addObserver(self, selector: #selector(TextFieldFormHandler.keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil) 65 | let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(TextFieldFormHandler.backgroundTap(sender:))) 66 | self.topContainer.addGestureRecognizer(tapRecognizer) 67 | } 68 | 69 | // MARK: - Public Methods 70 | 71 | public func resetScroll() { 72 | if let firstResponder = self.firstResponder { 73 | setAnimationOffsetForTextField(textField: firstResponder) 74 | moveScreenUp() 75 | } 76 | } 77 | 78 | public func setTextFieldAtIndexAsFirstResponder(index:Int) { 79 | textFields[index].becomeFirstResponder() 80 | } 81 | 82 | public func cleanUp() { 83 | NotificationCenter.default.removeObserver(self) 84 | } 85 | 86 | // MARK: - Private Methods 87 | 88 | func doneEnteringData() { 89 | topContainer.endEditing(true) 90 | moveScreenDown() 91 | delegate?.textFieldFormHandlerDoneEnteringData?(handler: self) 92 | } 93 | 94 | func moveScreenUp() { 95 | shiftScreenYPosition(position: -keyboardSize - animationOffset, duration: 0.3, curve: .easeInOut) 96 | } 97 | 98 | func moveScreenDown() { 99 | shiftScreenYPosition(position: 0, duration: 0.2, curve: .easeInOut) 100 | } 101 | 102 | func shiftScreenYPosition(position: CGFloat, duration: TimeInterval, curve: UIView.AnimationCurve) { 103 | UIView.beginAnimations("moveView", context: nil) 104 | UIView.setAnimationCurve(curve) 105 | UIView.setAnimationDuration(duration) 106 | 107 | topContainer.frame.origin.y = position 108 | UIView.commitAnimations() 109 | } 110 | 111 | @objc func backgroundTap(sender: UITapGestureRecognizer) { 112 | topContainer.endEditing(true) 113 | moveScreenDown() 114 | } 115 | 116 | @objc func keyboardWillShow(notification: NSNotification) { 117 | if (keyboardSize == 0) { 118 | if let keyboardRect = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue { 119 | keyboardSize = min(keyboardRect.height, keyboardRect.width) 120 | } 121 | } 122 | moveScreenUp() 123 | } 124 | 125 | func setAnimationOffsetForTextField(textField: UITextField) { 126 | let screenHeight = UIScreen.main.bounds.size.height 127 | let textFieldHeight = textField.frame.size.height 128 | let textFieldY = textField.convert(CGPoint.zero, to: topContainer).y 129 | 130 | animationOffset = -screenHeight + textFieldY + textFieldHeight 131 | } 132 | 133 | func setTextField(textField: UITextField, returnKeyType type: UIReturnKeyType) { 134 | if (textField.isFirstResponder) { 135 | textField.resignFirstResponder() 136 | textField.returnKeyType = type 137 | textField.becomeFirstResponder() 138 | } 139 | else { 140 | textField.returnKeyType = type 141 | } 142 | } 143 | 144 | deinit { 145 | cleanUp() 146 | } 147 | } 148 | 149 | extension TextFieldFormHandler : UITextFieldDelegate { 150 | public func textFieldShouldReturn(_ textField: UITextField) -> Bool { 151 | if let lastTextField = self.lastTextField, textField == lastTextField { 152 | doneEnteringData() 153 | return true 154 | } 155 | else if let lastTextField = self.textFields.last, lastTextField == textField { 156 | doneEnteringData() 157 | return true 158 | } 159 | 160 | let index = self.textFields.firstIndex(of: textField) 161 | let nextTextField = self.textFields[index! + 1] 162 | nextTextField.becomeFirstResponder() 163 | return true 164 | } 165 | 166 | public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { 167 | setAnimationOffsetForTextField(textField: textField) 168 | return true 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /twiliochat/TokenRequestHandler.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class TokenRequestHandler { 4 | 5 | class func postDataFrom(params:[String:String]) -> String { 6 | var data = "" 7 | 8 | for (key, value) in params { 9 | if let encodedKey = key.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed), 10 | let encodedValue = value.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) { 11 | if !data.isEmpty { 12 | data = data + "&" 13 | } 14 | data += encodedKey + "=" + encodedValue; 15 | } 16 | } 17 | 18 | return data 19 | } 20 | 21 | class func fetchToken(params:[String:String], completion:@escaping (NSDictionary, NSError?) -> Void) { 22 | if let filePath = Bundle.main.path(forResource: "Keys", ofType:"plist"), 23 | let dictionary = NSDictionary(contentsOfFile:filePath) as? [String: AnyObject], 24 | let tokenRequestUrl = dictionary["TokenRequestUrl"] as? String { 25 | 26 | var request = URLRequest(url: URL(string: tokenRequestUrl)!) 27 | request.httpMethod = "POST" 28 | let postString = self.postDataFrom(params: params) 29 | request.httpBody = postString.data(using: .utf8) 30 | let task = URLSession.shared.dataTask(with: request) { data, response, error in 31 | guard let data = data, error == nil else { 32 | print("error=\(String(describing: error))") 33 | completion(NSDictionary(), error as NSError?) 34 | return 35 | } 36 | 37 | if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { 38 | completion(NSDictionary(), NSError(domain: "TWILIO", code: 1000, userInfo: [NSLocalizedDescriptionKey: "Incorrect return code for token request."])) 39 | return 40 | } 41 | 42 | do { 43 | let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String:Any] 44 | print("json = \(json)") 45 | completion(json as NSDictionary, error as NSError?) 46 | } catch let error as NSError { 47 | completion(NSDictionary(), error) 48 | } 49 | } 50 | task.resume() 51 | } 52 | else { 53 | let userInfo = [NSLocalizedDescriptionKey : "TokenRequestUrl Key is missing"] 54 | let error = NSError(domain: "app", code: 404, userInfo: userInfo) 55 | 56 | completion(NSDictionary(), error) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /twiliochat/twiliochat-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #ifndef twiliochat_Bridging_Header_h 2 | #define twiliochat_Bridging_Header_h 3 | 4 | #import 5 | #import 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /twiliochatTests/DateTodayFormatterTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import twiliochat 3 | 4 | class DateTodayFormatterTests: XCTestCase { 5 | var dateTodayFormatter: DateTodayFormatter! 6 | 7 | override func setUp() { 8 | super.setUp() 9 | dateTodayFormatter = DateTodayFormatter() 10 | } 11 | 12 | override func tearDown() { 13 | super.tearDown() 14 | } 15 | 16 | func testStringFromDate() { 17 | let dateFormatter = DateFormatter() 18 | dateFormatter.dateFormat = "yyyy-MM-dd" 19 | let date = dateFormatter.date(from: "1990-05-14") 20 | 21 | let dateString = dateTodayFormatter.stringFromDate(date: date! as NSDate) 22 | 23 | XCTAssertEqual(dateString, "May. 14 - 12:00AM") 24 | } 25 | 26 | func testStringFromDateToday() { 27 | let dateToday = NSDate() 28 | let dateFormatter = DateFormatter() 29 | dateFormatter.dateFormat = "yyyy-MM-dd" 30 | let dateTodayString = dateFormatter.string(from: dateToday as Date) 31 | let date = dateFormatter.date(from: dateTodayString) 32 | let dateString = dateTodayFormatter.stringFromDate(date: date! as NSDate) 33 | 34 | XCTAssertEqual(dateString, "Today - 12:00AM") 35 | } 36 | 37 | func testDateFromString() { 38 | let dateFormatter = DateFormatter() 39 | dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" 40 | let dateString = "1990-05-14 21:04:12" 41 | 42 | let dateFromTestClass = NSDate.dateFromString(str: dateString, withFormat: "yyyy-MM-dd HH:mm:ss") 43 | 44 | XCTAssertEqual(dateFromTestClass, dateFormatter.date(from: "1990-05-14 21:04:12")! as NSDate) 45 | } 46 | 47 | func testDateWithISO8601String() { 48 | let dateString = "1990-05-14T12:05:12.003" 49 | let dateFormatter = DateFormatter() 50 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" 51 | let date = dateFormatter.date(from: dateString) 52 | 53 | let dateFromTestClass = NSDate.dateWithISO8601String(dateString: dateString) 54 | 55 | XCTAssertEqual(dateFromTestClass, date as NSDate?) 56 | } 57 | 58 | func testDateWithISO8601StringWithSuffix() { 59 | let dateFormatter = DateFormatter() 60 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" 61 | let date = dateFormatter.date(from: "1990-05-14T12:05:12.003-00:00") 62 | 63 | let dateFromTestClass = NSDate.dateWithISO8601String(dateString: "1990-05-14T12:05:12.003Z") 64 | 65 | XCTAssertEqual(dateFromTestClass, date as NSDate?) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /twiliochatTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /twiliochatTests/LoginViewControllerTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import twiliochat 3 | 4 | class LoginViewControllerTests: XCTestCase { 5 | var viewController: LoginViewController! 6 | 7 | override func setUp() { 8 | super.setUp() 9 | let storyBoard = UIStoryboard(name:"Main", bundle: Bundle.main) 10 | viewController = storyBoard.instantiateViewController(withIdentifier: "LoginViewController") as! LoginViewController 11 | viewController.loadView() 12 | viewController.viewDidLoad() 13 | } 14 | 15 | override func tearDown() { 16 | super.tearDown() 17 | MockMessagingManager.loginWithUsernameCalled = false 18 | MockMessagingManager.registerWithUsernameCalled = false 19 | MockMessagingManager.usernameUsed = "" 20 | MockMessagingManager.passwordUsed = "" 21 | MockAlertDialogController.showedAlertWithMessage = "" 22 | } 23 | 24 | func testLoginUser() { 25 | viewController.usernameTextField.text = "username" 26 | viewController.MessagingClientClass = MockMessagingManager.self 27 | 28 | viewController.loginUser() 29 | 30 | XCTAssertTrue(MockMessagingManager.loginWithUsernameCalled) 31 | XCTAssertEqual(MockMessagingManager.usernameUsed, "username") 32 | } 33 | 34 | func testSignUpOrLoginEmptyFields() { 35 | viewController.MessagingClientClass = MockMessagingManager.self 36 | viewController.alertDialogControllerClass = MockAlertDialogController.self 37 | viewController.loginUser() 38 | 39 | XCTAssertEqual(MockAlertDialogController.showedAlertWithMessage, "All fields are required") 40 | XCTAssertFalse(MockMessagingManager.loginWithUsernameCalled) 41 | } 42 | 43 | func testSignUpOrLoginIsLoginIn() { 44 | viewController.usernameTextField.text = "username" 45 | viewController.MessagingClientClass = MockMessagingManager.self 46 | viewController.loginUser() 47 | 48 | XCTAssertTrue(MockMessagingManager.loginWithUsernameCalled) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /twiliochatTests/MockAlertDialogController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | @testable import twiliochat 3 | 4 | class MockAlertDialogController: AlertDialogController { 5 | static var showedAlertWithMessage = "" 6 | 7 | override class func showAlertWithMessage(message:String?, title:String?, presenter:(UIViewController)) { 8 | if let message = message { 9 | showedAlertWithMessage = message 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /twiliochatTests/MockMessagingManager.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | @testable import twiliochat 3 | 4 | class MockMessagingManager: MessagingManager { 5 | static let mockManager = MockMessagingManager() 6 | 7 | static var loginWithUsernameCalled = false 8 | static var registerWithUsernameCalled = false 9 | static var passwordUsed = "" 10 | static var usernameUsed = "" 11 | 12 | override class func sharedManager() -> MessagingManager { 13 | return mockManager 14 | } 15 | 16 | override func loginWithUsername(username: String, 17 | completion: @escaping (Bool, NSError?) -> Void) { 18 | MockMessagingManager.loginWithUsernameCalled = true 19 | MockMessagingManager.usernameUsed = username 20 | } 21 | } 22 | --------------------------------------------------------------------------------