├── .gitignore ├── Podfile ├── Podfile.lock ├── README.md ├── Uther.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── Uther.xcworkspace └── contents.xcworkspacedata └── Uther ├── AppDelegate.swift ├── General ├── Extension │ ├── Uther │ │ ├── Array+Uther.swift │ │ ├── Date+Uther.swift │ │ ├── Dictionary+Operator.swift │ │ ├── Double+Uther.swift │ │ ├── String+Uther.swift │ │ ├── UIApplication+AppVersion.swift.swift │ │ ├── UICollectionView+Uther.swift │ │ ├── UIImage+Uther.swift │ │ ├── UIView+ShortCut.swift │ │ └── UIView+Uther.swift │ └── Vendor │ │ ├── LTMorphingEffect+Uther.swift │ │ ├── MoyaProvider+JSON.swift │ │ └── String+JSON.swift ├── Macro │ └── Macro.swift ├── Model │ ├── Message.swift │ └── TextMessage.swift ├── Network │ └── Data │ │ ├── QCloud.swift │ │ └── Sentiment.swift ├── Tool │ ├── DB.swift │ ├── LOG.swift │ └── Uther.swift └── Transition │ ├── HistoryTransitionManager.swift │ └── WelcomeTransitionManager.swift ├── Images.xcassets ├── AppIcon.appiconset │ ├── Contents.json │ ├── Icon-40.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 │ ├── Icon-Small.png │ ├── Icon-Small@2x.png │ └── Icon-Small@3x.png ├── CGAvatar.imageset │ ├── Contents.json │ └── icon01.pdf ├── Particles │ ├── Fire.imageset │ │ ├── Contents.json │ │ └── Fire.png │ ├── Fragment.imageset │ │ ├── Contents.json │ │ └── Fragment.png │ ├── Smoke.imageset │ │ ├── Contents.json │ │ └── Smoke.png │ └── Sparkle.imageset │ │ ├── Contents.json │ │ └── Sparkle.png ├── StarBackground.imageset │ ├── Contents.json │ └── Untitled Copy.pdf └── UtherAvatar.imageset │ ├── Contents.json │ └── Slice+1.compressed.pdf ├── Info.plist ├── Resource └── Emoji.plist ├── Section ├── History │ ├── HistoryCell.swift │ ├── HistoryDataSource.swift │ └── HistoryViewController.swift ├── Main │ ├── MainDataSource.swift │ ├── MainViewController.swift │ ├── MessageComposerView.swift │ └── UtherDisplayViewController.swift └── Welcome │ └── WelcomeViewController.swift ├── Storyboard ├── LaunchScreen.xib └── Main.storyboard └── Uther-Bridging-Header.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | ### Swift ### 4 | # Xcode 5 | # 6 | build/ 7 | *.pbxuser 8 | !default.pbxuser 9 | *.mode1v3 10 | !default.mode1v3 11 | *.mode2v3 12 | !default.mode2v3 13 | *.perspectivev3 14 | !default.perspectivev3 15 | xcuserdata 16 | *.xccheckout 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | *.dSYM.zip 22 | *.xcuserstate 23 | 24 | # CocoaPods 25 | # 26 | # We recommend against adding the Pods directory to your .gitignore. However 27 | # you should judge for yourself, the pros and cons are mentioned at: 28 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 29 | # 30 | Pods/ 31 | 32 | # Carthage 33 | # 34 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 35 | Carthage/Checkouts 36 | 37 | Carthage/Build 38 | 39 | # Fir 40 | build/ 41 | build_ipa/ 42 | fir_build_ipa/ 43 | fir-cli_tmp/ 44 | 45 | # R.swift 46 | *.generated.swift 47 | 48 | # fastlane 49 | fastlane/report.xml 50 | fastlane/Preview.html 51 | fastlane/screenshots 52 | fastlane/test_output -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '8.0' 2 | use_frameworks! 3 | 4 | target 'Uther' do 5 | pod 'KeyboardMan' 6 | pod 'JSQMessagesViewController' 7 | pod 'AsyncSwift' 8 | pod 'Alamofire' 9 | pod 'SwiftyJSON' 10 | pod 'XCGLogger' 11 | pod 'SQLite.swift' 12 | pod 'SwiftDate' 13 | pod 'CryptoSwift' 14 | pod 'SwiftHEXColors' 15 | pod 'LTMorphingLabel' 16 | pod 'Moya' 17 | pod 'Bugly' 18 | end 19 | 20 | post_install do |installer| 21 | installer.pods_project.targets.each do |target| 22 | target.build_configurations.each do |config| 23 | config.build_settings['SWIFT_VERSION'] = '3.0' 24 | end 25 | end 26 | end 27 | 28 | plugin 'cocoapods-keys', { 29 | :project => "Uther", 30 | :keys => [ 31 | "BuglyAppId", 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.2.0) 3 | - AsyncSwift (2.0.1) 4 | - Bugly (2.4.6) 5 | - CryptoSwift (0.6.7) 6 | - JSQMessagesViewController (7.3.4): 7 | - JSQSystemSoundPlayer (~> 2.0.1) 8 | - JSQSystemSoundPlayer (2.0.1) 9 | - KeyboardMan (1.0.0) 10 | - Keys (1.0.0) 11 | - LTMorphingLabel (0.4.0) 12 | - Moya (8.0.0): 13 | - Moya/Core (= 8.0.0) 14 | - Moya/Core (8.0.0): 15 | - Alamofire (~> 4.2.0) 16 | - Result (~> 3.1.0) 17 | - Result (3.1.0) 18 | - SQLite.swift (0.11.2): 19 | - SQLite.swift/standard (= 0.11.2) 20 | - SQLite.swift/standard (0.11.2) 21 | - SwiftDate (4.0.11) 22 | - SwiftHEXColors (1.1.0) 23 | - SwiftyJSON (3.1.4) 24 | - XCGLogger (4.0.0): 25 | - XCGLogger/Core (= 4.0.0) 26 | - XCGLogger/Core (4.0.0) 27 | 28 | DEPENDENCIES: 29 | - Alamofire 30 | - AsyncSwift 31 | - Bugly 32 | - CryptoSwift 33 | - JSQMessagesViewController 34 | - KeyboardMan 35 | - Keys (from `Pods/CocoaPodsKeys`) 36 | - LTMorphingLabel 37 | - Moya 38 | - SQLite.swift 39 | - SwiftDate 40 | - SwiftHEXColors 41 | - SwiftyJSON 42 | - XCGLogger 43 | 44 | EXTERNAL SOURCES: 45 | Keys: 46 | :path: Pods/CocoaPodsKeys 47 | 48 | SPEC CHECKSUMS: 49 | Alamofire: aa2e09d871c9160ac53c90e83c68064a94e3dfbe 50 | AsyncSwift: 632138e42ead868b53c745fcde9bf733ce1527ad 51 | Bugly: 7756e72c59387b0d490f46e83ab458ae94ee7fba 52 | CryptoSwift: 685ae257941e5447474348a2b545583e1a16b573 53 | JSQMessagesViewController: 39fed975e3c9f8eba7292071e29eeb541d105e66 54 | JSQSystemSoundPlayer: c5850e77a4363ffd374cd851154b9af93264ed8d 55 | KeyboardMan: 51d9e93890be5493c3c4837299d20557d7572ab4 56 | Keys: 9c35bf00f612ee1d48556f4a4b9b4551e224e90f 57 | LTMorphingLabel: d9a85f16ac306f0e07321d6819964deb2dc69638 58 | Moya: 850a166ab8241bd1c1cea6dbe012e3a2ad62944a 59 | Result: 4e3ed5995ed94d0cd6a09be9a431fce3f3624bbf 60 | SQLite.swift: 670c3e9e0a806bbfd56a3de5d530768dc0e6fe7c 61 | SwiftDate: a36d5ce7c86bed3ed92b583ccbf15045f374ac65 62 | SwiftHEXColors: 8f7e364df7929e32cff9f9eeb044bb215167d6be 63 | SwiftyJSON: c2842d878f95482ffceec5709abc3d05680c0220 64 | XCGLogger: 38063ea89b4c8b23232d18efd2e12eb0005f3ea4 65 | 66 | PODFILE CHECKSUM: 8dc6c377b2e773283e5936a1279a60fb77691d83 67 | 68 | COCOAPODS: 1.2.0 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Uther 2 | =========== 3 | 4 | Chat with the cute alien, help you make a memorandum! 5 | 6 | ![](http://ww2.sinaimg.cn/large/61d238c7gw1eve4yd04m7j20500503ye.jpg) 7 | 8 | 9 | You can download it from AppStore: [Uther](https://itunes.apple.com/cn/app/uther/id1024104920). 10 | 11 | ScreenShots: 12 | 13 | ![](http://ww3.sinaimg.cn/large/61d238c7gw1eve4x0dfwzj21150ggjx5.jpg) 14 | 15 | 16 | 17 | ### What't new in v1.2 : 18 | 19 | - Thank [LTMorphingLabel](https://github.com/lexrus/LTMorphingLabel) for the cool animation 20 | - Support English 21 | - Can delete message history now 22 | 23 | ### How to install dependencies 24 | 25 | ``` 26 | pod install 27 | ``` 28 | 29 | 30 | ### License 31 | 32 | Copyright (C) CallMeWhy 33 | 34 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 35 | 36 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 37 | 38 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 39 | -------------------------------------------------------------------------------- /Uther.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 961C2E1F1B7B64C400957368 /* UtherDisplayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961C2E1E1B7B64C400957368 /* UtherDisplayViewController.swift */; }; 11 | 961C2E231B7B737000957368 /* MainDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961C2E221B7B737000957368 /* MainDataSource.swift */; }; 12 | 961C2E251B7B738B00957368 /* MessageComposerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961C2E241B7B738B00957368 /* MessageComposerView.swift */; }; 13 | 961C2E381B7C371B00957368 /* Array+Uther.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961C2E2A1B7C371B00957368 /* Array+Uther.swift */; }; 14 | 961C2E391B7C371B00957368 /* Dictionary+Operator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961C2E2B1B7C371B00957368 /* Dictionary+Operator.swift */; }; 15 | 961C2E3A1B7C371B00957368 /* Double+Uther.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961C2E2C1B7C371B00957368 /* Double+Uther.swift */; }; 16 | 961C2E3C1B7C371B00957368 /* String+Uther.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961C2E2E1B7C371B00957368 /* String+Uther.swift */; }; 17 | 961C2E3D1B7C371B00957368 /* UIApplication+AppVersion.swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961C2E2F1B7C371B00957368 /* UIApplication+AppVersion.swift.swift */; }; 18 | 961C2E3E1B7C371B00957368 /* UICollectionView+Uther.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961C2E301B7C371B00957368 /* UICollectionView+Uther.swift */; }; 19 | 961C2E3F1B7C371B00957368 /* UIImage+Uther.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961C2E311B7C371B00957368 /* UIImage+Uther.swift */; }; 20 | 961C2E401B7C371B00957368 /* UIView+ShortCut.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961C2E321B7C371B00957368 /* UIView+ShortCut.swift */; }; 21 | 961C2E411B7C371B00957368 /* UIView+Uther.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961C2E331B7C371B00957368 /* UIView+Uther.swift */; }; 22 | 961C2E421B7C371B00957368 /* LTMorphingEffect+Uther.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961C2E351B7C371B00957368 /* LTMorphingEffect+Uther.swift */; }; 23 | 961C2E431B7C371B00957368 /* MoyaProvider+JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961C2E361B7C371B00957368 /* MoyaProvider+JSON.swift */; }; 24 | 961C2E441B7C371B00957368 /* String+JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961C2E371B7C371B00957368 /* String+JSON.swift */; }; 25 | 961C2E481B7C7AA100957368 /* HistoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961C2E471B7C7AA100957368 /* HistoryCell.swift */; }; 26 | 961C2E4A1B7C98DF00957368 /* HistoryDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961C2E491B7C98DF00957368 /* HistoryDataSource.swift */; }; 27 | 96261D081B6DC255004AA0F1 /* HistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96261D071B6DC255004AA0F1 /* HistoryViewController.swift */; }; 28 | 9661EEC11B78DEDE00C9ADD8 /* QCloud.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9661EEC01B78DEDE00C9ADD8 /* QCloud.swift */; }; 29 | 9662C85F1B70D80B000D63B6 /* Emoji.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9662C85C1B70D80B000D63B6 /* Emoji.plist */; }; 30 | 9677CF971B75F7B700F322FD /* Uther.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9677CF961B75F7B700F322FD /* Uther.swift */; }; 31 | 9677CFA11B76382600F322FD /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9677CFA01B76382600F322FD /* Security.framework */; }; 32 | 967C5F8A1B68EBC4009B1D6E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967C5F891B68EBC4009B1D6E /* AppDelegate.swift */; }; 33 | 967C5F911B68EBC4009B1D6E /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 967C5F901B68EBC4009B1D6E /* Images.xcassets */; }; 34 | 967C5FF61B6A39ED009B1D6E /* Sentiment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967C5FF51B6A39ED009B1D6E /* Sentiment.swift */; }; 35 | 967C60051B6A530C009B1D6E /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967C5FFF1B6A530C009B1D6E /* MainViewController.swift */; }; 36 | 967C600B1B6A6191009B1D6E /* LOG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967C600A1B6A6191009B1D6E /* LOG.swift */; }; 37 | 967C600E1B6A7DFF009B1D6E /* Macro.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967C600D1B6A7DFF009B1D6E /* Macro.swift */; }; 38 | 967C60111B6A82CA009B1D6E /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967C60101B6A82CA009B1D6E /* Message.swift */; }; 39 | 967C60151B6A8EBC009B1D6E /* DB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967C60141B6A8EBC009B1D6E /* DB.swift */; }; 40 | 967C60221B6BE505009B1D6E /* TextMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967C60211B6BE505009B1D6E /* TextMessage.swift */; }; 41 | 967C60281B6C63BE009B1D6E /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 967C60271B6C63BE009B1D6E /* SystemConfiguration.framework */; }; 42 | 967C60521B6CD909009B1D6E /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 967C60501B6CD909009B1D6E /* LaunchScreen.xib */; }; 43 | 967C60531B6CD909009B1D6E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 967C60511B6CD909009B1D6E /* Main.storyboard */; }; 44 | 96BCF0EA1E4060BB00CA78D7 /* Date+Uther.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCF0E91E4060BB00CA78D7 /* Date+Uther.swift */; }; 45 | 96BF24CA1B75A50F002EDDB8 /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BF24C91B75A50F002EDDB8 /* WelcomeViewController.swift */; }; 46 | 96BF25041B75AFE8002EDDB8 /* WelcomeTransitionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BF25031B75AFE8002EDDB8 /* WelcomeTransitionManager.swift */; }; 47 | 96BF25061B75D689002EDDB8 /* HistoryTransitionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BF25051B75D689002EDDB8 /* HistoryTransitionManager.swift */; }; 48 | B26F9E5C2157CF83FAB57BD4 /* Pods_Uther.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 36091C2C2027E78F38123010 /* Pods_Uther.framework */; }; 49 | /* End PBXBuildFile section */ 50 | 51 | /* Begin PBXFileReference section */ 52 | 36091C2C2027E78F38123010 /* Pods_Uther.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Uther.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | 5C0D7C8898719D60E459D572 /* Pods-Uther.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Uther.release.xcconfig"; path = "Pods/Target Support Files/Pods-Uther/Pods-Uther.release.xcconfig"; sourceTree = ""; }; 54 | 961C2E1E1B7B64C400957368 /* UtherDisplayViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UtherDisplayViewController.swift; sourceTree = ""; }; 55 | 961C2E221B7B737000957368 /* MainDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainDataSource.swift; sourceTree = ""; }; 56 | 961C2E241B7B738B00957368 /* MessageComposerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageComposerView.swift; sourceTree = ""; }; 57 | 961C2E2A1B7C371B00957368 /* Array+Uther.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Uther.swift"; sourceTree = ""; }; 58 | 961C2E2B1B7C371B00957368 /* Dictionary+Operator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Dictionary+Operator.swift"; sourceTree = ""; }; 59 | 961C2E2C1B7C371B00957368 /* Double+Uther.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Double+Uther.swift"; sourceTree = ""; }; 60 | 961C2E2E1B7C371B00957368 /* String+Uther.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Uther.swift"; sourceTree = ""; }; 61 | 961C2E2F1B7C371B00957368 /* UIApplication+AppVersion.swift.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIApplication+AppVersion.swift.swift"; sourceTree = ""; }; 62 | 961C2E301B7C371B00957368 /* UICollectionView+Uther.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UICollectionView+Uther.swift"; sourceTree = ""; }; 63 | 961C2E311B7C371B00957368 /* UIImage+Uther.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Uther.swift"; sourceTree = ""; }; 64 | 961C2E321B7C371B00957368 /* UIView+ShortCut.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+ShortCut.swift"; sourceTree = ""; }; 65 | 961C2E331B7C371B00957368 /* UIView+Uther.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Uther.swift"; sourceTree = ""; }; 66 | 961C2E351B7C371B00957368 /* LTMorphingEffect+Uther.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "LTMorphingEffect+Uther.swift"; sourceTree = ""; }; 67 | 961C2E361B7C371B00957368 /* MoyaProvider+JSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MoyaProvider+JSON.swift"; sourceTree = ""; }; 68 | 961C2E371B7C371B00957368 /* String+JSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+JSON.swift"; sourceTree = ""; }; 69 | 961C2E471B7C7AA100957368 /* HistoryCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryCell.swift; sourceTree = ""; }; 70 | 961C2E491B7C98DF00957368 /* HistoryDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryDataSource.swift; sourceTree = ""; }; 71 | 96261D071B6DC255004AA0F1 /* HistoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryViewController.swift; sourceTree = ""; }; 72 | 962AE7801E71814C00E75AF0 /* Uther-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Uther-Bridging-Header.h"; sourceTree = ""; }; 73 | 9661EEC01B78DEDE00C9ADD8 /* QCloud.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QCloud.swift; sourceTree = ""; }; 74 | 9662C85C1B70D80B000D63B6 /* Emoji.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Emoji.plist; sourceTree = ""; }; 75 | 9677CF961B75F7B700F322FD /* Uther.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Uther.swift; sourceTree = ""; }; 76 | 9677CFA01B76382600F322FD /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 77 | 967C5F841B68EBC4009B1D6E /* Uther.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Uther.app; sourceTree = BUILT_PRODUCTS_DIR; }; 78 | 967C5F881B68EBC4009B1D6E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 79 | 967C5F891B68EBC4009B1D6E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 80 | 967C5F901B68EBC4009B1D6E /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 81 | 967C5FF51B6A39ED009B1D6E /* Sentiment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sentiment.swift; sourceTree = ""; }; 82 | 967C5FFF1B6A530C009B1D6E /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; 83 | 967C600A1B6A6191009B1D6E /* LOG.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LOG.swift; sourceTree = ""; }; 84 | 967C600D1B6A7DFF009B1D6E /* Macro.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Macro.swift; sourceTree = ""; }; 85 | 967C60101B6A82CA009B1D6E /* Message.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = ""; }; 86 | 967C60141B6A8EBC009B1D6E /* DB.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DB.swift; sourceTree = ""; }; 87 | 967C60211B6BE505009B1D6E /* TextMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextMessage.swift; sourceTree = ""; }; 88 | 967C60271B6C63BE009B1D6E /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; 89 | 967C60501B6CD909009B1D6E /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; 90 | 967C60511B6CD909009B1D6E /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 91 | 96BCF0E91E4060BB00CA78D7 /* Date+Uther.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Uther.swift"; sourceTree = ""; }; 92 | 96BF24C91B75A50F002EDDB8 /* WelcomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = ""; }; 93 | 96BF25031B75AFE8002EDDB8 /* WelcomeTransitionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeTransitionManager.swift; sourceTree = ""; }; 94 | 96BF25051B75D689002EDDB8 /* HistoryTransitionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryTransitionManager.swift; sourceTree = ""; }; 95 | 96F8BF951D4A536500F4219E /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = Carthage/Build/iOS/Alamofire.framework; sourceTree = ""; }; 96 | 96F8BF961D4A536500F4219E /* Async.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Async.framework; path = Carthage/Build/iOS/Async.framework; sourceTree = ""; }; 97 | 96F8BF971D4A536500F4219E /* CryptoSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CryptoSwift.framework; path = Carthage/Build/iOS/CryptoSwift.framework; sourceTree = ""; }; 98 | 96F8BF981D4A536500F4219E /* JSQMessages.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JSQMessages.framework; path = Carthage/Build/iOS/JSQMessages.framework; sourceTree = ""; }; 99 | 96F8BF991D4A536500F4219E /* KeyboardMan.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = KeyboardMan.framework; path = Carthage/Build/iOS/KeyboardMan.framework; sourceTree = ""; }; 100 | 96F8BF9A1D4A536500F4219E /* KeychainAccess.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = KeychainAccess.framework; path = Carthage/Build/iOS/KeychainAccess.framework; sourceTree = ""; }; 101 | 96F8BF9B1D4A536500F4219E /* LTMorphingLabel.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LTMorphingLabel.framework; path = Carthage/Build/iOS/LTMorphingLabel.framework; sourceTree = ""; }; 102 | 96F8BF9C1D4A536500F4219E /* Moya.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Moya.framework; path = Carthage/Build/iOS/Moya.framework; sourceTree = ""; }; 103 | 96F8BF9D1D4A536500F4219E /* RxSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxSwift.framework; path = Carthage/Build/iOS/RxSwift.framework; sourceTree = ""; }; 104 | 96F8BF9E1D4A536500F4219E /* SQLite.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SQLite.framework; path = Carthage/Build/iOS/SQLite.framework; sourceTree = ""; }; 105 | 96F8BF9F1D4A536500F4219E /* SwiftDate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftDate.framework; path = Carthage/Build/iOS/SwiftDate.framework; sourceTree = ""; }; 106 | 96F8BFA01D4A536500F4219E /* SwiftHEXColors.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftHEXColors.framework; path = Carthage/Build/iOS/SwiftHEXColors.framework; sourceTree = ""; }; 107 | 96F8BFA11D4A536500F4219E /* SwiftyJSON.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftyJSON.framework; path = Carthage/Build/iOS/SwiftyJSON.framework; sourceTree = ""; }; 108 | 96F8BFA21D4A536500F4219E /* XCGLogger.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCGLogger.framework; path = Carthage/Build/iOS/XCGLogger.framework; sourceTree = ""; }; 109 | EE23D156760389813788857E /* Pods-Uther.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Uther.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Uther/Pods-Uther.debug.xcconfig"; sourceTree = ""; }; 110 | /* End PBXFileReference section */ 111 | 112 | /* Begin PBXFrameworksBuildPhase section */ 113 | 967C5F811B68EBC4009B1D6E /* Frameworks */ = { 114 | isa = PBXFrameworksBuildPhase; 115 | buildActionMask = 2147483647; 116 | files = ( 117 | 9677CFA11B76382600F322FD /* Security.framework in Frameworks */, 118 | 967C60281B6C63BE009B1D6E /* SystemConfiguration.framework in Frameworks */, 119 | B26F9E5C2157CF83FAB57BD4 /* Pods_Uther.framework in Frameworks */, 120 | ); 121 | runOnlyForDeploymentPostprocessing = 0; 122 | }; 123 | /* End PBXFrameworksBuildPhase section */ 124 | 125 | /* Begin PBXGroup section */ 126 | 961C2E281B7C371B00957368 /* Extension */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | 961C2E291B7C371B00957368 /* Uther */, 130 | 961C2E341B7C371B00957368 /* Vendor */, 131 | ); 132 | path = Extension; 133 | sourceTree = ""; 134 | }; 135 | 961C2E291B7C371B00957368 /* Uther */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | 961C2E2A1B7C371B00957368 /* Array+Uther.swift */, 139 | 961C2E2B1B7C371B00957368 /* Dictionary+Operator.swift */, 140 | 961C2E2C1B7C371B00957368 /* Double+Uther.swift */, 141 | 961C2E2E1B7C371B00957368 /* String+Uther.swift */, 142 | 961C2E2F1B7C371B00957368 /* UIApplication+AppVersion.swift.swift */, 143 | 961C2E301B7C371B00957368 /* UICollectionView+Uther.swift */, 144 | 961C2E311B7C371B00957368 /* UIImage+Uther.swift */, 145 | 961C2E321B7C371B00957368 /* UIView+ShortCut.swift */, 146 | 961C2E331B7C371B00957368 /* UIView+Uther.swift */, 147 | 96BCF0E91E4060BB00CA78D7 /* Date+Uther.swift */, 148 | ); 149 | path = Uther; 150 | sourceTree = ""; 151 | }; 152 | 961C2E341B7C371B00957368 /* Vendor */ = { 153 | isa = PBXGroup; 154 | children = ( 155 | 961C2E351B7C371B00957368 /* LTMorphingEffect+Uther.swift */, 156 | 961C2E361B7C371B00957368 /* MoyaProvider+JSON.swift */, 157 | 961C2E371B7C371B00957368 /* String+JSON.swift */, 158 | ); 159 | path = Vendor; 160 | sourceTree = ""; 161 | }; 162 | 96261D041B6DC1E4004AA0F1 /* History */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | 96261D071B6DC255004AA0F1 /* HistoryViewController.swift */, 166 | 961C2E491B7C98DF00957368 /* HistoryDataSource.swift */, 167 | 961C2E471B7C7AA100957368 /* HistoryCell.swift */, 168 | ); 169 | path = History; 170 | sourceTree = ""; 171 | }; 172 | 9662C85B1B70D80B000D63B6 /* Resource */ = { 173 | isa = PBXGroup; 174 | children = ( 175 | 9662C85C1B70D80B000D63B6 /* Emoji.plist */, 176 | ); 177 | path = Resource; 178 | sourceTree = ""; 179 | }; 180 | 967C5F7B1B68EBC4009B1D6E = { 181 | isa = PBXGroup; 182 | children = ( 183 | 967C5F861B68EBC4009B1D6E /* Uther */, 184 | 967C5F851B68EBC4009B1D6E /* Products */, 185 | C37D35300207225A5318125C /* Frameworks */, 186 | F5B5CD038B2ABC326A803028 /* Pods */, 187 | ); 188 | sourceTree = ""; 189 | }; 190 | 967C5F851B68EBC4009B1D6E /* Products */ = { 191 | isa = PBXGroup; 192 | children = ( 193 | 967C5F841B68EBC4009B1D6E /* Uther.app */, 194 | ); 195 | name = Products; 196 | sourceTree = ""; 197 | }; 198 | 967C5F861B68EBC4009B1D6E /* Uther */ = { 199 | isa = PBXGroup; 200 | children = ( 201 | 962AE7801E71814C00E75AF0 /* Uther-Bridging-Header.h */, 202 | 967C5F891B68EBC4009B1D6E /* AppDelegate.swift */, 203 | 967C604F1B6CD909009B1D6E /* Storyboard */, 204 | 967C5FE51B6A3358009B1D6E /* General */, 205 | 967C5FFC1B6A530C009B1D6E /* Section */, 206 | 967C5F901B68EBC4009B1D6E /* Images.xcassets */, 207 | 9662C85B1B70D80B000D63B6 /* Resource */, 208 | 967C5F871B68EBC4009B1D6E /* Supporting Files */, 209 | ); 210 | path = Uther; 211 | sourceTree = ""; 212 | }; 213 | 967C5F871B68EBC4009B1D6E /* Supporting Files */ = { 214 | isa = PBXGroup; 215 | children = ( 216 | 967C5F881B68EBC4009B1D6E /* Info.plist */, 217 | ); 218 | name = "Supporting Files"; 219 | sourceTree = ""; 220 | }; 221 | 967C5FE51B6A3358009B1D6E /* General */ = { 222 | isa = PBXGroup; 223 | children = ( 224 | 967C600C1B6A7DE1009B1D6E /* Macro */, 225 | 967C600F1B6A82B8009B1D6E /* Model */, 226 | 967C5FF91B6A4F22009B1D6E /* Tool */, 227 | 961C2E281B7C371B00957368 /* Extension */, 228 | 967C5FE61B6A3358009B1D6E /* Network */, 229 | 96BF25021B75AFDE002EDDB8 /* Transition */, 230 | ); 231 | path = General; 232 | sourceTree = ""; 233 | }; 234 | 967C5FE61B6A3358009B1D6E /* Network */ = { 235 | isa = PBXGroup; 236 | children = ( 237 | 967C5FF41B6A3980009B1D6E /* Data */, 238 | ); 239 | path = Network; 240 | sourceTree = ""; 241 | }; 242 | 967C5FF41B6A3980009B1D6E /* Data */ = { 243 | isa = PBXGroup; 244 | children = ( 245 | 967C5FF51B6A39ED009B1D6E /* Sentiment.swift */, 246 | 9661EEC01B78DEDE00C9ADD8 /* QCloud.swift */, 247 | ); 248 | path = Data; 249 | sourceTree = ""; 250 | }; 251 | 967C5FF91B6A4F22009B1D6E /* Tool */ = { 252 | isa = PBXGroup; 253 | children = ( 254 | 967C600A1B6A6191009B1D6E /* LOG.swift */, 255 | 967C60141B6A8EBC009B1D6E /* DB.swift */, 256 | 9677CF961B75F7B700F322FD /* Uther.swift */, 257 | ); 258 | path = Tool; 259 | sourceTree = ""; 260 | }; 261 | 967C5FFC1B6A530C009B1D6E /* Section */ = { 262 | isa = PBXGroup; 263 | children = ( 264 | 96BF24C81B75A4B9002EDDB8 /* Welcome */, 265 | 967C5FFD1B6A530C009B1D6E /* Main */, 266 | 96261D041B6DC1E4004AA0F1 /* History */, 267 | ); 268 | path = Section; 269 | sourceTree = ""; 270 | }; 271 | 967C5FFD1B6A530C009B1D6E /* Main */ = { 272 | isa = PBXGroup; 273 | children = ( 274 | 967C5FFF1B6A530C009B1D6E /* MainViewController.swift */, 275 | 961C2E221B7B737000957368 /* MainDataSource.swift */, 276 | 961C2E1E1B7B64C400957368 /* UtherDisplayViewController.swift */, 277 | 961C2E241B7B738B00957368 /* MessageComposerView.swift */, 278 | ); 279 | path = Main; 280 | sourceTree = ""; 281 | }; 282 | 967C600C1B6A7DE1009B1D6E /* Macro */ = { 283 | isa = PBXGroup; 284 | children = ( 285 | 967C600D1B6A7DFF009B1D6E /* Macro.swift */, 286 | ); 287 | path = Macro; 288 | sourceTree = ""; 289 | }; 290 | 967C600F1B6A82B8009B1D6E /* Model */ = { 291 | isa = PBXGroup; 292 | children = ( 293 | 967C60101B6A82CA009B1D6E /* Message.swift */, 294 | 967C60211B6BE505009B1D6E /* TextMessage.swift */, 295 | ); 296 | path = Model; 297 | sourceTree = ""; 298 | }; 299 | 967C604F1B6CD909009B1D6E /* Storyboard */ = { 300 | isa = PBXGroup; 301 | children = ( 302 | 967C60511B6CD909009B1D6E /* Main.storyboard */, 303 | 967C60501B6CD909009B1D6E /* LaunchScreen.xib */, 304 | ); 305 | path = Storyboard; 306 | sourceTree = ""; 307 | }; 308 | 96BF24C81B75A4B9002EDDB8 /* Welcome */ = { 309 | isa = PBXGroup; 310 | children = ( 311 | 96BF24C91B75A50F002EDDB8 /* WelcomeViewController.swift */, 312 | ); 313 | path = Welcome; 314 | sourceTree = ""; 315 | }; 316 | 96BF25021B75AFDE002EDDB8 /* Transition */ = { 317 | isa = PBXGroup; 318 | children = ( 319 | 96BF25031B75AFE8002EDDB8 /* WelcomeTransitionManager.swift */, 320 | 96BF25051B75D689002EDDB8 /* HistoryTransitionManager.swift */, 321 | ); 322 | path = Transition; 323 | sourceTree = ""; 324 | }; 325 | C37D35300207225A5318125C /* Frameworks */ = { 326 | isa = PBXGroup; 327 | children = ( 328 | 96F8BF951D4A536500F4219E /* Alamofire.framework */, 329 | 96F8BF961D4A536500F4219E /* Async.framework */, 330 | 96F8BF971D4A536500F4219E /* CryptoSwift.framework */, 331 | 96F8BF981D4A536500F4219E /* JSQMessages.framework */, 332 | 96F8BF991D4A536500F4219E /* KeyboardMan.framework */, 333 | 96F8BF9A1D4A536500F4219E /* KeychainAccess.framework */, 334 | 96F8BF9B1D4A536500F4219E /* LTMorphingLabel.framework */, 335 | 96F8BF9C1D4A536500F4219E /* Moya.framework */, 336 | 96F8BF9D1D4A536500F4219E /* RxSwift.framework */, 337 | 96F8BF9E1D4A536500F4219E /* SQLite.framework */, 338 | 96F8BF9F1D4A536500F4219E /* SwiftDate.framework */, 339 | 96F8BFA01D4A536500F4219E /* SwiftHEXColors.framework */, 340 | 96F8BFA11D4A536500F4219E /* SwiftyJSON.framework */, 341 | 96F8BFA21D4A536500F4219E /* XCGLogger.framework */, 342 | 9677CFA01B76382600F322FD /* Security.framework */, 343 | 967C60271B6C63BE009B1D6E /* SystemConfiguration.framework */, 344 | 36091C2C2027E78F38123010 /* Pods_Uther.framework */, 345 | ); 346 | name = Frameworks; 347 | sourceTree = ""; 348 | }; 349 | F5B5CD038B2ABC326A803028 /* Pods */ = { 350 | isa = PBXGroup; 351 | children = ( 352 | EE23D156760389813788857E /* Pods-Uther.debug.xcconfig */, 353 | 5C0D7C8898719D60E459D572 /* Pods-Uther.release.xcconfig */, 354 | ); 355 | name = Pods; 356 | sourceTree = ""; 357 | }; 358 | /* End PBXGroup section */ 359 | 360 | /* Begin PBXNativeTarget section */ 361 | 967C5F831B68EBC4009B1D6E /* Uther */ = { 362 | isa = PBXNativeTarget; 363 | buildConfigurationList = 967C5FA31B68EBC5009B1D6E /* Build configuration list for PBXNativeTarget "Uther" */; 364 | buildPhases = ( 365 | 94F6AD181AFE0834D8A76B04 /* [CP] Check Pods Manifest.lock */, 366 | 967C5F801B68EBC4009B1D6E /* Sources */, 367 | 967C5F811B68EBC4009B1D6E /* Frameworks */, 368 | 967C5F821B68EBC4009B1D6E /* Resources */, 369 | 559D3894B83D6407F8447DDA /* [CP] Embed Pods Frameworks */, 370 | F19BAE54E25A0DBFC4F58E33 /* [CP] Copy Pods Resources */, 371 | ); 372 | buildRules = ( 373 | ); 374 | dependencies = ( 375 | ); 376 | name = Uther; 377 | productName = Uther; 378 | productReference = 967C5F841B68EBC4009B1D6E /* Uther.app */; 379 | productType = "com.apple.product-type.application"; 380 | }; 381 | /* End PBXNativeTarget section */ 382 | 383 | /* Begin PBXProject section */ 384 | 967C5F7C1B68EBC4009B1D6E /* Project object */ = { 385 | isa = PBXProject; 386 | attributes = { 387 | LastSwiftUpdateCheck = 0700; 388 | LastUpgradeCheck = 0820; 389 | ORGANIZATIONNAME = callmewhy; 390 | TargetAttributes = { 391 | 967C5F831B68EBC4009B1D6E = { 392 | CreatedOnToolsVersion = 6.4; 393 | DevelopmentTeam = YKZVGCKFL7; 394 | LastSwiftMigration = 0820; 395 | ProvisioningStyle = Automatic; 396 | }; 397 | }; 398 | }; 399 | buildConfigurationList = 967C5F7F1B68EBC4009B1D6E /* Build configuration list for PBXProject "Uther" */; 400 | compatibilityVersion = "Xcode 3.2"; 401 | developmentRegion = English; 402 | hasScannedForEncodings = 0; 403 | knownRegions = ( 404 | en, 405 | "zh-Hans", 406 | ); 407 | mainGroup = 967C5F7B1B68EBC4009B1D6E; 408 | productRefGroup = 967C5F851B68EBC4009B1D6E /* Products */; 409 | projectDirPath = ""; 410 | projectRoot = ""; 411 | targets = ( 412 | 967C5F831B68EBC4009B1D6E /* Uther */, 413 | ); 414 | }; 415 | /* End PBXProject section */ 416 | 417 | /* Begin PBXResourcesBuildPhase section */ 418 | 967C5F821B68EBC4009B1D6E /* Resources */ = { 419 | isa = PBXResourcesBuildPhase; 420 | buildActionMask = 2147483647; 421 | files = ( 422 | 967C60521B6CD909009B1D6E /* LaunchScreen.xib in Resources */, 423 | 967C60531B6CD909009B1D6E /* Main.storyboard in Resources */, 424 | 9662C85F1B70D80B000D63B6 /* Emoji.plist in Resources */, 425 | 967C5F911B68EBC4009B1D6E /* Images.xcassets in Resources */, 426 | ); 427 | runOnlyForDeploymentPostprocessing = 0; 428 | }; 429 | /* End PBXResourcesBuildPhase section */ 430 | 431 | /* Begin PBXShellScriptBuildPhase section */ 432 | 559D3894B83D6407F8447DDA /* [CP] Embed Pods Frameworks */ = { 433 | isa = PBXShellScriptBuildPhase; 434 | buildActionMask = 2147483647; 435 | files = ( 436 | ); 437 | inputPaths = ( 438 | ); 439 | name = "[CP] Embed Pods Frameworks"; 440 | outputPaths = ( 441 | ); 442 | runOnlyForDeploymentPostprocessing = 0; 443 | shellPath = /bin/sh; 444 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Uther/Pods-Uther-frameworks.sh\"\n"; 445 | showEnvVarsInLog = 0; 446 | }; 447 | 94F6AD181AFE0834D8A76B04 /* [CP] Check Pods Manifest.lock */ = { 448 | isa = PBXShellScriptBuildPhase; 449 | buildActionMask = 2147483647; 450 | files = ( 451 | ); 452 | inputPaths = ( 453 | ); 454 | name = "[CP] Check Pods Manifest.lock"; 455 | outputPaths = ( 456 | ); 457 | runOnlyForDeploymentPostprocessing = 0; 458 | shellPath = /bin/sh; 459 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; 460 | showEnvVarsInLog = 0; 461 | }; 462 | F19BAE54E25A0DBFC4F58E33 /* [CP] Copy Pods Resources */ = { 463 | isa = PBXShellScriptBuildPhase; 464 | buildActionMask = 2147483647; 465 | files = ( 466 | ); 467 | inputPaths = ( 468 | ); 469 | name = "[CP] Copy Pods Resources"; 470 | outputPaths = ( 471 | ); 472 | runOnlyForDeploymentPostprocessing = 0; 473 | shellPath = /bin/sh; 474 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Uther/Pods-Uther-resources.sh\"\n"; 475 | showEnvVarsInLog = 0; 476 | }; 477 | /* End PBXShellScriptBuildPhase section */ 478 | 479 | /* Begin PBXSourcesBuildPhase section */ 480 | 967C5F801B68EBC4009B1D6E /* Sources */ = { 481 | isa = PBXSourcesBuildPhase; 482 | buildActionMask = 2147483647; 483 | files = ( 484 | 961C2E3F1B7C371B00957368 /* UIImage+Uther.swift in Sources */, 485 | 961C2E251B7B738B00957368 /* MessageComposerView.swift in Sources */, 486 | 9677CF971B75F7B700F322FD /* Uther.swift in Sources */, 487 | 96BCF0EA1E4060BB00CA78D7 /* Date+Uther.swift in Sources */, 488 | 9661EEC11B78DEDE00C9ADD8 /* QCloud.swift in Sources */, 489 | 961C2E441B7C371B00957368 /* String+JSON.swift in Sources */, 490 | 961C2E431B7C371B00957368 /* MoyaProvider+JSON.swift in Sources */, 491 | 961C2E481B7C7AA100957368 /* HistoryCell.swift in Sources */, 492 | 961C2E401B7C371B00957368 /* UIView+ShortCut.swift in Sources */, 493 | 961C2E3E1B7C371B00957368 /* UICollectionView+Uther.swift in Sources */, 494 | 961C2E3A1B7C371B00957368 /* Double+Uther.swift in Sources */, 495 | 967C600B1B6A6191009B1D6E /* LOG.swift in Sources */, 496 | 96BF24CA1B75A50F002EDDB8 /* WelcomeViewController.swift in Sources */, 497 | 967C5F8A1B68EBC4009B1D6E /* AppDelegate.swift in Sources */, 498 | 961C2E1F1B7B64C400957368 /* UtherDisplayViewController.swift in Sources */, 499 | 967C600E1B6A7DFF009B1D6E /* Macro.swift in Sources */, 500 | 967C5FF61B6A39ED009B1D6E /* Sentiment.swift in Sources */, 501 | 96261D081B6DC255004AA0F1 /* HistoryViewController.swift in Sources */, 502 | 961C2E411B7C371B00957368 /* UIView+Uther.swift in Sources */, 503 | 961C2E381B7C371B00957368 /* Array+Uther.swift in Sources */, 504 | 961C2E3D1B7C371B00957368 /* UIApplication+AppVersion.swift.swift in Sources */, 505 | 967C60221B6BE505009B1D6E /* TextMessage.swift in Sources */, 506 | 961C2E3C1B7C371B00957368 /* String+Uther.swift in Sources */, 507 | 961C2E231B7B737000957368 /* MainDataSource.swift in Sources */, 508 | 967C60111B6A82CA009B1D6E /* Message.swift in Sources */, 509 | 961C2E4A1B7C98DF00957368 /* HistoryDataSource.swift in Sources */, 510 | 967C60151B6A8EBC009B1D6E /* DB.swift in Sources */, 511 | 961C2E421B7C371B00957368 /* LTMorphingEffect+Uther.swift in Sources */, 512 | 96BF25041B75AFE8002EDDB8 /* WelcomeTransitionManager.swift in Sources */, 513 | 967C60051B6A530C009B1D6E /* MainViewController.swift in Sources */, 514 | 96BF25061B75D689002EDDB8 /* HistoryTransitionManager.swift in Sources */, 515 | 961C2E391B7C371B00957368 /* Dictionary+Operator.swift in Sources */, 516 | ); 517 | runOnlyForDeploymentPostprocessing = 0; 518 | }; 519 | /* End PBXSourcesBuildPhase section */ 520 | 521 | /* Begin XCBuildConfiguration section */ 522 | 967C5FA11B68EBC5009B1D6E /* Debug */ = { 523 | isa = XCBuildConfiguration; 524 | buildSettings = { 525 | ALWAYS_SEARCH_USER_PATHS = NO; 526 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 527 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 528 | CLANG_CXX_LIBRARY = "libc++"; 529 | CLANG_ENABLE_MODULES = YES; 530 | CLANG_ENABLE_OBJC_ARC = YES; 531 | CLANG_WARN_BOOL_CONVERSION = YES; 532 | CLANG_WARN_CONSTANT_CONVERSION = YES; 533 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 534 | CLANG_WARN_EMPTY_BODY = YES; 535 | CLANG_WARN_ENUM_CONVERSION = YES; 536 | CLANG_WARN_INFINITE_RECURSION = YES; 537 | CLANG_WARN_INT_CONVERSION = YES; 538 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 539 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 540 | CLANG_WARN_UNREACHABLE_CODE = YES; 541 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 542 | CODE_SIGN_IDENTITY = "iPhone Developer"; 543 | COPY_PHASE_STRIP = NO; 544 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 545 | ENABLE_STRICT_OBJC_MSGSEND = YES; 546 | ENABLE_TESTABILITY = YES; 547 | GCC_C_LANGUAGE_STANDARD = gnu99; 548 | GCC_DYNAMIC_NO_PIC = NO; 549 | GCC_NO_COMMON_BLOCKS = YES; 550 | GCC_OPTIMIZATION_LEVEL = 0; 551 | GCC_PREPROCESSOR_DEFINITIONS = ( 552 | "DEBUG=1", 553 | "$(inherited)", 554 | ); 555 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 556 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 557 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 558 | GCC_WARN_UNDECLARED_SELECTOR = YES; 559 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 560 | GCC_WARN_UNUSED_FUNCTION = YES; 561 | GCC_WARN_UNUSED_VARIABLE = YES; 562 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 563 | MTL_ENABLE_DEBUG_INFO = YES; 564 | ONLY_ACTIVE_ARCH = YES; 565 | SDKROOT = iphoneos; 566 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 567 | TARGETED_DEVICE_FAMILY = "1,2"; 568 | }; 569 | name = Debug; 570 | }; 571 | 967C5FA21B68EBC5009B1D6E /* Release */ = { 572 | isa = XCBuildConfiguration; 573 | buildSettings = { 574 | ALWAYS_SEARCH_USER_PATHS = NO; 575 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 576 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 577 | CLANG_CXX_LIBRARY = "libc++"; 578 | CLANG_ENABLE_MODULES = YES; 579 | CLANG_ENABLE_OBJC_ARC = YES; 580 | CLANG_WARN_BOOL_CONVERSION = YES; 581 | CLANG_WARN_CONSTANT_CONVERSION = YES; 582 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 583 | CLANG_WARN_EMPTY_BODY = YES; 584 | CLANG_WARN_ENUM_CONVERSION = YES; 585 | CLANG_WARN_INFINITE_RECURSION = YES; 586 | CLANG_WARN_INT_CONVERSION = YES; 587 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 588 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 589 | CLANG_WARN_UNREACHABLE_CODE = YES; 590 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 591 | CODE_SIGN_IDENTITY = "iPhone Developer"; 592 | COPY_PHASE_STRIP = NO; 593 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 594 | ENABLE_NS_ASSERTIONS = NO; 595 | ENABLE_STRICT_OBJC_MSGSEND = YES; 596 | GCC_C_LANGUAGE_STANDARD = gnu99; 597 | GCC_NO_COMMON_BLOCKS = YES; 598 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 599 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 600 | GCC_WARN_UNDECLARED_SELECTOR = YES; 601 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 602 | GCC_WARN_UNUSED_FUNCTION = YES; 603 | GCC_WARN_UNUSED_VARIABLE = YES; 604 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 605 | MTL_ENABLE_DEBUG_INFO = NO; 606 | SDKROOT = iphoneos; 607 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 608 | TARGETED_DEVICE_FAMILY = "1,2"; 609 | VALIDATE_PRODUCT = YES; 610 | }; 611 | name = Release; 612 | }; 613 | 967C5FA41B68EBC5009B1D6E /* Debug */ = { 614 | isa = XCBuildConfiguration; 615 | baseConfigurationReference = EE23D156760389813788857E /* Pods-Uther.debug.xcconfig */; 616 | buildSettings = { 617 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; 618 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 619 | CLANG_ENABLE_MODULES = YES; 620 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 621 | DEVELOPMENT_TEAM = YKZVGCKFL7; 622 | FRAMEWORK_SEARCH_PATHS = ( 623 | "$(inherited)", 624 | "$(PROJECT_DIR)/Uther/Vendor", 625 | ); 626 | INFOPLIST_FILE = Uther/Info.plist; 627 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 628 | LIBRARY_SEARCH_PATHS = ( 629 | "$(inherited)", 630 | "$(PROJECT_DIR)/Uther/Vendor", 631 | "$(PROJECT_DIR)/Uther/Vendor/Flurry", 632 | ); 633 | OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" -D DEBUG"; 634 | PRODUCT_BUNDLE_IDENTIFIER = com.callmewhy.uther; 635 | PRODUCT_NAME = "$(TARGET_NAME)"; 636 | SWIFT_OBJC_BRIDGING_HEADER = "Uther/Uther-Bridging-Header.h"; 637 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 638 | SWIFT_VERSION = 3.0; 639 | }; 640 | name = Debug; 641 | }; 642 | 967C5FA51B68EBC5009B1D6E /* Release */ = { 643 | isa = XCBuildConfiguration; 644 | baseConfigurationReference = 5C0D7C8898719D60E459D572 /* Pods-Uther.release.xcconfig */; 645 | buildSettings = { 646 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; 647 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 648 | CLANG_ENABLE_MODULES = YES; 649 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 650 | DEVELOPMENT_TEAM = YKZVGCKFL7; 651 | FRAMEWORK_SEARCH_PATHS = ( 652 | "$(inherited)", 653 | "$(PROJECT_DIR)/Uther/Vendor", 654 | ); 655 | INFOPLIST_FILE = Uther/Info.plist; 656 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 657 | LIBRARY_SEARCH_PATHS = ( 658 | "$(inherited)", 659 | "$(PROJECT_DIR)/Uther/Vendor", 660 | "$(PROJECT_DIR)/Uther/Vendor/Flurry", 661 | ); 662 | OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\""; 663 | PRODUCT_BUNDLE_IDENTIFIER = com.callmewhy.uther; 664 | PRODUCT_NAME = "$(TARGET_NAME)"; 665 | SWIFT_OBJC_BRIDGING_HEADER = "Uther/Uther-Bridging-Header.h"; 666 | SWIFT_VERSION = 3.0; 667 | }; 668 | name = Release; 669 | }; 670 | /* End XCBuildConfiguration section */ 671 | 672 | /* Begin XCConfigurationList section */ 673 | 967C5F7F1B68EBC4009B1D6E /* Build configuration list for PBXProject "Uther" */ = { 674 | isa = XCConfigurationList; 675 | buildConfigurations = ( 676 | 967C5FA11B68EBC5009B1D6E /* Debug */, 677 | 967C5FA21B68EBC5009B1D6E /* Release */, 678 | ); 679 | defaultConfigurationIsVisible = 0; 680 | defaultConfigurationName = Release; 681 | }; 682 | 967C5FA31B68EBC5009B1D6E /* Build configuration list for PBXNativeTarget "Uther" */ = { 683 | isa = XCConfigurationList; 684 | buildConfigurations = ( 685 | 967C5FA41B68EBC5009B1D6E /* Debug */, 686 | 967C5FA51B68EBC5009B1D6E /* Release */, 687 | ); 688 | defaultConfigurationIsVisible = 0; 689 | defaultConfigurationName = Release; 690 | }; 691 | /* End XCConfigurationList section */ 692 | }; 693 | rootObject = 967C5F7C1B68EBC4009B1D6E /* Project object */; 694 | } 695 | -------------------------------------------------------------------------------- /Uther.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Uther.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Uther/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Uther 4 | // 5 | // Created by why on 7/29/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Keys 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | 19 | let keys = UtherKeys() 20 | Bugly.start(withAppId: keys.buglyKey) 21 | DB.setupDatabase() 22 | return true 23 | } 24 | 25 | func applicationWillResignActive(_ application: UIApplication) { 26 | 27 | } 28 | 29 | func applicationDidEnterBackground(_ application: UIApplication) { 30 | 31 | } 32 | 33 | func applicationWillEnterForeground(_ application: UIApplication) { 34 | 35 | } 36 | 37 | func applicationDidBecomeActive(_ application: UIApplication) { 38 | 39 | } 40 | 41 | func applicationWillTerminate(_ application: UIApplication) { 42 | 43 | } 44 | 45 | 46 | } 47 | 48 | -------------------------------------------------------------------------------- /Uther/General/Extension/Uther/Array+Uther.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+Uther.swift 3 | // Uther 4 | // 5 | // Created by why on 8/2/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Array { 12 | func last(i: Int) -> ArraySlice { 13 | return self.suffix(from: i) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Uther/General/Extension/Uther/Date+Uther.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date+Uther.swift 3 | // Uther 4 | // 5 | // Created by why on 31/01/2017. 6 | // Copyright © 2017 callmewhy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Date { 12 | func toString() -> String { 13 | let dateFormatter = DateFormatter() 14 | dateFormatter.dateFormat = "YYYY-MM-DD" 15 | return dateFormatter.string(from: self) 16 | } 17 | 18 | func startTime() -> Date { 19 | return Calendar.current.startOfDay(for: self) 20 | } 21 | 22 | func endTime() -> Date { 23 | var components = DateComponents() 24 | components.day = 1 25 | components.second = -1 26 | return Calendar.current.date(byAdding: components, to: startTime())! 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Uther/General/Extension/Uther/Dictionary+Operator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary+Operator.swift 3 | // Uther 4 | // 5 | // Created by why on 8/6/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | func += (left: inout Dictionary, right: Dictionary) { 12 | for (k, v) in right { 13 | left.updateValue(v, forKey: k) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Uther/General/Extension/Uther/Double+Uther.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Double+Uther.swift 3 | // Uther 4 | // 5 | // Created by why on 8/1/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Double { 12 | var emoji: String { 13 | get { 14 | return Emojis.getEmoji(positive: self) 15 | } 16 | } 17 | 18 | func format(_ f: String) -> String { 19 | return String(format: "%\(f)f", self) 20 | } 21 | } 22 | 23 | 24 | private let allEmojiDic = NSDictionary(contentsOfFile: Bundle.main.path(forResource: "Emoji", ofType: "plist")!)! 25 | 26 | struct Emojis { 27 | static fileprivate var emojiDic = [String:[String]]() 28 | 29 | static func getEmoji(positive v:Double) -> String { 30 | var v = min(1, v) 31 | v = max(0, v) 32 | let key = v.format(".1") 33 | if let _ = allEmojiDic[key] { 34 | // check need update 35 | var needUpdate = false 36 | if let array = emojiDic[key] { 37 | needUpdate = array.isEmpty 38 | } else { 39 | needUpdate = true 40 | } 41 | if needUpdate { 42 | emojiDic[key] = allEmojiDic["\(key)"] as? [String] 43 | } 44 | 45 | // get emoji 46 | if var array = emojiDic[key] { 47 | if let _ = array.last { 48 | let last = array.removeLast() 49 | emojiDic[key] = array 50 | return last 51 | } 52 | } 53 | } 54 | return "..." 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Uther/General/Extension/Uther/String+Uther.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Uther.swift 3 | // Uther 4 | // 5 | // Created by why on 8/1/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | var toDouble: Double? { 13 | return NumberFormatter().number(from: self)?.doubleValue 14 | } 15 | 16 | var localized: String { 17 | let s = NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "") 18 | return s 19 | } 20 | 21 | var isChinese: Bool { 22 | return self.range(of: "\\p{Han}", options: .regularExpression) != nil 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Uther/General/Extension/Uther/UIApplication+AppVersion.swift.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication+AppVersion.swift.swift 3 | // Uther 4 | // 5 | // Created by why on 8/8/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIApplication { 12 | class func appVersion() -> String { 13 | return Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String 14 | } 15 | 16 | class func appBuild() -> String { 17 | return Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as! String 18 | } 19 | 20 | class func versionDescription() -> String { 21 | let version = appVersion() 22 | #if DEBUG 23 | return "Debug - \(version)" 24 | #else 25 | return "Release - \(version)" 26 | #endif 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Uther/General/Extension/Uther/UICollectionView+Uther.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionView+Uther.swift 3 | // Uther 4 | // 5 | // Created by why on 7/31/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UICollectionView { 12 | func scrollToBottom(_ animated:Bool = false) { 13 | let contentHeight = self.collectionViewLayout.collectionViewContentSize.height 14 | if contentHeight < self.bounds.height { 15 | self.scrollRectToVisible(CGRect(x: 0.0, y: 0, width: 1.0, height: 1.0), animated:animated) 16 | return; 17 | } 18 | self.scrollRectToVisible(CGRect(x: 0.0, y: contentHeight - 1.0, width: 1.0, height: 1.0), animated:animated) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Uther/General/Extension/Uther/UIImage+Uther.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Uther.swift 3 | // Uther 4 | // 5 | // Created by why on 8/5/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIImage { 12 | static func imageWithColor(_ color: UIColor, size: CGSize) -> UIImage { 13 | let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) 14 | UIGraphicsBeginImageContextWithOptions(size, false, 0) 15 | color.setFill() 16 | UIRectFill(rect) 17 | let image = UIGraphicsGetImageFromCurrentImageContext() 18 | UIGraphicsEndImageContext() 19 | return image! 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Uther/General/Extension/Uther/UIView+ShortCut.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+ShortCut.swift 3 | // Uther 4 | // 5 | // Created by why on 8/5/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | //UIView 12 | extension UIView { 13 | var width: CGFloat { return self.frame.size.width } 14 | var height: CGFloat { return self.frame.size.height } 15 | var size: CGSize { return self.frame.size} 16 | 17 | var origin: CGPoint { return self.frame.origin } 18 | var x: CGFloat { return self.frame.origin.x } 19 | var y: CGFloat { return self.frame.origin.y } 20 | var centerX: CGFloat { return self.center.x } 21 | var centerY: CGFloat { return self.center.y } 22 | 23 | var left: CGFloat { return self.frame.origin.x } 24 | var right: CGFloat { return self.frame.origin.x + self.frame.size.width } 25 | var top: CGFloat { return self.frame.origin.y } 26 | var bottom: CGFloat { return self.frame.origin.y + self.frame.size.height } 27 | 28 | func setWidth(_ width:CGFloat) { 29 | self.frame.size.width = width 30 | } 31 | 32 | func setHeight(_ height:CGFloat) { 33 | self.frame.size.height = height 34 | } 35 | 36 | func setSize(_ size:CGSize) { 37 | self.frame.size = size 38 | } 39 | 40 | func setOrigin(_ point:CGPoint) { 41 | self.frame.origin = point 42 | } 43 | 44 | func setX(_ x:CGFloat) { 45 | self.frame.origin = CGPoint(x: x, y: self.frame.origin.y) 46 | } 47 | 48 | func setY(_ y:CGFloat) { 49 | self.frame.origin = CGPoint(x: self.frame.origin.x, y: y) 50 | } 51 | 52 | func setCenterX(_ x:CGFloat) { 53 | self.center = CGPoint(x: x, y: self.center.y) 54 | } 55 | 56 | func setCenterY(_ y:CGFloat) { 57 | self.center = CGPoint(x: self.center.x, y: y) 58 | } 59 | 60 | func roundCorner(_ radius:CGFloat) { 61 | self.layer.cornerRadius = radius 62 | } 63 | 64 | func setTop(_ top:CGFloat) { 65 | self.frame.origin.y = top 66 | } 67 | 68 | func setLeft(_ left:CGFloat) { 69 | self.frame.origin.x = left 70 | } 71 | 72 | func setRight(_ right:CGFloat) { 73 | self.frame.origin.x = right - self.frame.size.width 74 | } 75 | 76 | func setBottom(_ bottom:CGFloat) { 77 | self.frame.origin.y = bottom - self.frame.size.height 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Uther/General/Extension/Uther/UIView+Uther.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Uther.swift 3 | // Uther 4 | // 5 | // Created by why on 8/1/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIView { 12 | func updateLocalizableString() { 13 | for v in self.subviews { 14 | if v.subviews.count > 0 { 15 | v.updateLocalizableString() 16 | } 17 | if v.isKind(of: UILabel.self) { 18 | let l = v as! UILabel 19 | l.text = l.text?.localized 20 | l.sizeToFit() 21 | } 22 | if v.isKind(of: UIButton.self) { 23 | let b = v as! UIButton 24 | b.setTitle(b.titleLabel?.text?.localized, for: UIControlState()) 25 | } 26 | if v.isKind(of: UITextField.self) { 27 | let t = v as! UITextField 28 | t.placeholder = t.placeholder?.localized 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Uther/General/Extension/Vendor/LTMorphingEffect+Uther.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LTMorphingEffect+Uther.swift 3 | // Uther 4 | // 5 | // Created by why on 8/12/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import LTMorphingLabel 11 | 12 | 13 | private var index = 0 14 | extension LTMorphingEffect { 15 | static func next() -> LTMorphingEffect { 16 | let max = 7 17 | let i = index < max ? index : ((index - max) / 3) % 7 18 | index += 1 19 | log.debug("\(i)") 20 | return LTMorphingEffect(rawValue: i)! 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Uther/General/Extension/Vendor/MoyaProvider+JSON.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoyaProvider+JSON.swift 3 | // Uther 4 | // 5 | // Created by why on 8/11/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Moya 11 | import SwiftyJSON 12 | 13 | extension MoyaProvider { 14 | func requestJSON(_ endpoint: Target, completion: @escaping (JSON?) -> Void) -> Cancellable { 15 | return self.request(endpoint) { result in 16 | switch result { 17 | case .success(let data): 18 | let json = JSON(data: data.data) 19 | log.debug("\(json)") 20 | completion(json) 21 | case .failure(let error): 22 | log.error("\(error)") 23 | completion(nil) 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Uther/General/Extension/Vendor/String+JSON.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+JSON.swift 3 | // Uther 4 | // 5 | // Created by why on 8/1/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | 12 | extension String { 13 | var json: JSON { 14 | get { 15 | if let dataFromString = self.data(using: String.Encoding.utf8, allowLossyConversion: false) { 16 | return JSON(data: dataFromString) 17 | } else { 18 | return JSON(self) 19 | } 20 | } 21 | } 22 | } 23 | 24 | extension JSON { 25 | var jsonString: String { 26 | get { 27 | return self.rawString() ?? "" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Uther/General/Macro/Macro.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Macro.swift 3 | // Uther 4 | // 5 | // Created by why on 7/30/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // 默认动画时长 12 | let kDefaultAnimationDuration = 0.3 13 | 14 | typealias ImageName = String 15 | 16 | // 是否开启 Debug 模式 17 | var debug = false -------------------------------------------------------------------------------- /Uther/General/Model/Message.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Message.swift 3 | // Uther 4 | // 5 | // Created by why on 7/31/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SQLite 11 | import SwiftyJSON 12 | import JSQMessagesViewController 13 | 14 | enum MessageType: Double { 15 | case text = 1001 16 | } 17 | 18 | class Message: JSQMessage { 19 | var pid: Pid? 20 | var type: MessageType 21 | let content: String 22 | var detail: JSON? 23 | 24 | init(type: MessageType, content: String, date: Date = Date()) { 25 | self.content = content 26 | self.type = type 27 | super.init(senderId: "ME", senderDisplayName: "", date: date, text: content) 28 | } 29 | 30 | required init(coder aDecoder: NSCoder) { 31 | fatalError("init(coder:) has not been implemented") 32 | } 33 | } 34 | 35 | // MARK: Convinince 36 | extension Message { 37 | func saveToDatabase() { 38 | pid = DB.saveMessage(self) 39 | } 40 | func deleteFromDatabase() { 41 | DB.deleteMessage(self) 42 | } 43 | } 44 | 45 | // MARK: DB extension 46 | extension DB { 47 | fileprivate static func saveMessage(_ message: Message) -> Pid? { 48 | var setters = [ 49 | DB.MessageDB.createdTime <- message.date.timeIntervalSince1970, 50 | DB.MessageDB.content <- message.content, 51 | DB.MessageDB.type <- message.type.rawValue, 52 | DB.MessageDB.detail <- message.detail?.jsonString] 53 | 54 | if let pid = message.pid { 55 | setters += [DB.MessageDB.pid <- pid] 56 | } 57 | let insert = DB.MessageDB.table.insert(or: OnConflict.replace, setters) 58 | do { 59 | let rowid = try DB.db.run(insert) 60 | return rowid 61 | } catch { 62 | log.error("insertion failed: \(error)") 63 | } 64 | return nil 65 | } 66 | fileprivate static func deleteMessage(_ message: Message) { 67 | if let pid = message.pid { 68 | let query = DB.MessageDB.table.filter(DB.MessageDB.pid == pid) 69 | let _ = query.delete() 70 | } 71 | } 72 | 73 | static func getReverseMessages(_ offset: Int, limit: Int) -> [Message]{ 74 | var messages = [Message]() 75 | let q = DB.MessageDB.table.order(DB.MessageDB.pid.desc).limit(limit, offset: offset) 76 | for row in try! DB.db.prepare(q) { 77 | let type = MessageType(rawValue: row[DB.MessageDB.type]) 78 | if let type = type { 79 | let date = NSDate(timeIntervalSince1970: row[DB.MessageDB.createdTime]) 80 | let message = Message(type: type, content: row[DB.MessageDB.content], date:date as Date) 81 | message.pid = row[DB.MessageDB.pid] 82 | message.detail = row[DB.MessageDB.detail]?.json 83 | messages.append(message) 84 | } 85 | } 86 | return messages 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Uther/General/Model/TextMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextMessage.swift 3 | // Uther 4 | // 5 | // Created by why on 8/1/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftyJSON 11 | 12 | typealias PositiveValue = Double 13 | 14 | class TextMessage: Message { 15 | // positive value, 0~1 16 | var positive: PositiveValue = 0.5 { 17 | didSet { 18 | detail = JSON(["positive": positive]) 19 | } 20 | } 21 | 22 | init(text: String) { 23 | super.init(type: .text, content: text) 24 | } 25 | 26 | required init(coder aDecoder: NSCoder) { 27 | fatalError("init(coder:) has not been implemented") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Uther/General/Network/Data/QCloud.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QCloud.swift 3 | // Uther 4 | // 5 | // Created by why on 8/10/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CryptoSwift 11 | 12 | struct QCloud { 13 | fileprivate struct Secret { 14 | static let id = "YOUR_ID" 15 | static let key = "YOUR_KEY" 16 | } 17 | 18 | fileprivate struct ParameterName { 19 | static let action = "Action" 20 | static let nonce = "Nonce" 21 | static let region = "Region" 22 | static let secretId = "SecretId" 23 | static let timestamp = "Timestamp" 24 | static let signature = "Signature" 25 | static let content = "content" 26 | } 27 | 28 | static func getRequestParameters(_ content: String) -> [String: String] { 29 | // Parameters 30 | var paras = [ 31 | ParameterName.action : "TextSentiment", 32 | ParameterName.nonce : "\(arc4random_uniform(2333))", 33 | ParameterName.region : "sz", 34 | ParameterName.secretId : Secret.id, 35 | ParameterName.timestamp : "\(Int(Date().timeIntervalSince1970))", 36 | ParameterName.content : content 37 | ] 38 | 39 | let parasString = paras.sorted{ $0.0 < $1.0 }.map { "\($0.0)=\($0.1)" }.joined(separator: "&") 40 | 41 | // HMAC 42 | let msg = "GETwenzhi.api.qcloud.com/v2/index.php?\(parasString)" 43 | let key = Secret.key 44 | var keyBuff = [UInt8]() 45 | keyBuff += key.utf8 46 | var msgBuff = [UInt8]() 47 | msgBuff += msg.utf8 48 | let hmac = try! HMAC(key: keyBuff, variant: .sha1).authenticate(msgBuff) 49 | let signature = Data(bytes: hmac).base64EncodedString() 50 | paras += [ParameterName.signature: signature] 51 | return paras 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Uther/General/Network/Data/Sentiment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sentiment.swift 3 | // Uther 4 | // 5 | // Created by why on 7/30/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | import Moya 12 | 13 | public enum Sentiment { 14 | case chinese(String) 15 | case english(String) 16 | } 17 | 18 | // MARK: - MoyaProvider 19 | let SentimentProvider = MoyaProvider() 20 | 21 | // TODO: extension MoyaTarget to handle respose 22 | extension MoyaProvider { 23 | typealias positiveHandler = (PositiveValue?) -> Void 24 | func requestCPositive(_ endpoint: Target, completion: @escaping positiveHandler) -> Cancellable { 25 | return self.requestJSON(endpoint, completion: { json in 26 | guard let json = json, json["code"].intValue == 0 else { 27 | completion(nil) 28 | return 29 | } 30 | let p = json["positive"].double 31 | log.info("positive value: \(p)") 32 | completion(p) 33 | return 34 | }) 35 | } 36 | 37 | func requestEPositive(_ endpoint: Target, completion: @escaping positiveHandler) -> Cancellable { 38 | return self.requestJSON(endpoint, completion: { json in 39 | if let json = json { 40 | let probability = json["probability"] 41 | let p = probability["pos"].double 42 | log.info("服务器返回解析结果:开心概率 = \(p)") 43 | completion(p) 44 | return 45 | } 46 | completion(nil) 47 | }) 48 | } 49 | } 50 | 51 | // MARK: - MoyaTarget 52 | extension Sentiment: TargetType { 53 | public var task: Task { 54 | return Task.request 55 | } 56 | 57 | public var parameterEncoding: ParameterEncoding { 58 | return URLEncoding.default 59 | } 60 | 61 | public var baseURL: URL { 62 | switch self { 63 | case .chinese: 64 | return URL(string: "https://wenzhi.api.qcloud.com")! 65 | case .english: 66 | return URL(string: "http://text-processing.com")! 67 | } 68 | } 69 | 70 | public var path: String { 71 | switch self { 72 | case .chinese: 73 | return "v2/index.php" 74 | case .english: 75 | return "api/sentiment/" 76 | } 77 | } 78 | 79 | public var method: Moya.Method { 80 | switch self { 81 | case .chinese: 82 | return .get 83 | case .english: 84 | return .post 85 | } 86 | } 87 | 88 | public var parameters: [String : Any]? { 89 | switch self { 90 | case .chinese(let text): 91 | return QCloud.getRequestParameters(text) 92 | case .english(let text): 93 | return ["text": text as AnyObject] 94 | } 95 | } 96 | 97 | public var sampleData: Data { 98 | switch self { 99 | case .chinese: 100 | return "{}".data(using: String.Encoding.utf8)! 101 | case .english: 102 | return "{\"probability\": {\"neg\": 0.22499999999999998,\"neutral\": 0.099999999999999978,\"pos\": 0.77500000000000002},\"label\": \"pos\"}".data(using: String.Encoding.utf8)! 103 | } 104 | } 105 | 106 | public var multipartBody: [Moya.MultipartFormData]? { 107 | return nil 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Uther/General/Tool/DB.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Database.swift 3 | // Uther 4 | // 5 | // Created by why on 7/31/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import SQLite 10 | 11 | typealias Pid = Int64 12 | 13 | struct DB { 14 | static let db = try! Connection((documentsDirectory.appendingPathComponent("uther.db").absoluteString)) 15 | 16 | struct MessageDB { 17 | static let table = Table("message") 18 | // 唯一索引,主键 19 | static let pid = Expression("pid") 20 | // 消息创建的时间 21 | static let createdTime = Expression("created_time") 22 | // 消息的内容 23 | static let content = Expression("content") 24 | // 消息的类型 25 | static let type = Expression("type") 26 | // 消息的其他附属内容, JSON 格式 27 | static let detail = Expression("detail") 28 | } 29 | 30 | static func setupDatabase() { 31 | do { 32 | try db.run(MessageDB.table.create(ifNotExists: true) { t in 33 | t.column(MessageDB.pid, primaryKey: true) 34 | t.column(MessageDB.createdTime) 35 | t.column(MessageDB.content) 36 | t.column(MessageDB.type) 37 | t.column(MessageDB.detail) 38 | }) 39 | } catch { 40 | log.error("DB ERROR \(error)") 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Uther/General/Tool/LOG.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LOG.swift 3 | // Uther 4 | // 5 | // Created by why on 7/30/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCGLogger 11 | 12 | let log: XCGLogger = { 13 | let log = XCGLogger.default 14 | let logPath : NSURL = cacheDirectory.appendingPathComponent("XCGLogger.Log") as NSURL 15 | log.setup(level: .debug, 16 | showThreadName: true, 17 | showLevel: true, 18 | showFileNames: true, 19 | showLineNumbers: false, 20 | writeToFile: logPath, 21 | fileLevel: .info) 22 | return log 23 | }() 24 | 25 | var documentsDirectory: URL { 26 | let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) 27 | return urls[urls.endIndex-1] 28 | } 29 | 30 | var cacheDirectory: URL { 31 | let urls = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask) 32 | return urls[urls.endIndex-1] 33 | } 34 | -------------------------------------------------------------------------------- /Uther/General/Tool/Uther.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Uther.swift 3 | // Uther 4 | // 5 | // Created by why on 8/8/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | import SwiftyJSON 12 | 13 | struct Uther { 14 | struct JsonKey { 15 | static let currentVersion = "curent_version" 16 | static let userDictionary = "user_dictionary" 17 | } 18 | 19 | fileprivate static var dictionary: [String:String]? 20 | 21 | static func handleMessage(_ text:String, completion:@escaping (EventType)->()) { 22 | // 查看版本 23 | if text.uppercased() == "VERSION" { 24 | completion(.text(UIApplication.versionDescription())) 25 | return 26 | } 27 | // 开启测试模式 28 | if text.uppercased() == "DEBUG" { 29 | debug = !debug 30 | completion(.text("DEBUG MODE ON")) 31 | return 32 | } 33 | // 如果处于测试模式,则随机显示一个表情 34 | if debug { 35 | let r = Double(arc4random() % 10) * 0.1 36 | completion(.emoji(r)) 37 | return 38 | } 39 | 40 | // 本地词库检测 41 | if let d = dictionary, let localResult = d[text.uppercased()] { 42 | completion(.text(localResult)) 43 | return 44 | } 45 | // 联网检测 46 | if text.isChinese { 47 | let _ = SentimentProvider.requestCPositive(Sentiment.chinese(text)) { p in 48 | if let p = p { 49 | completion(.emoji(p)) 50 | } else { 51 | completion(.error) 52 | } 53 | } 54 | } else { 55 | let _ = SentimentProvider.requestEPositive(Sentiment.english(text)) { p in 56 | if let p = p { 57 | completion(.emoji(p)) 58 | } else { 59 | completion(.error) 60 | } 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Uther/General/Transition/HistoryTransitionManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HistoryTransitionManager.swift 3 | // Uther 4 | // 5 | // Created by why on 8/8/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | class HistoryTransitionManager: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate { 11 | var isPresenting = false 12 | 13 | // MARK: UIViewControllerAnimatedTransitioning 14 | func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 15 | let container = transitionContext.containerView 16 | let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)! 17 | let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)! 18 | if isPresenting { 19 | container.addSubview(fromView) 20 | container.addSubview(toView) 21 | } else { 22 | container.addSubview(toView) 23 | container.addSubview(fromView) 24 | } 25 | 26 | // avatar 27 | let fromAvatar = isPresenting ? fromView.viewWithTag(1002)! : fromView.viewWithTag(1003)! 28 | let toAvatar = isPresenting ? toView.viewWithTag(1003)! : toView.viewWithTag(1002)! 29 | let avatar = fromAvatar.snapshotView(afterScreenUpdates: true) 30 | container.addSubview(avatar!) 31 | avatar?.frame = fromView.convert(fromAvatar.frame, to: container) 32 | fromAvatar.alpha = 0 33 | toAvatar.alpha = 0 34 | let targetFrame = toView.convert(toAvatar.frame, to: container) 35 | 36 | // view 37 | let offsetY = abs(fromAvatar.height - toAvatar.height) 38 | let offScreenBottom = CGAffineTransform(translationX: 0, y: offsetY) 39 | let duration = self.transitionDuration(using: transitionContext) 40 | 41 | if isPresenting { 42 | toView.alpha = 0 43 | toView.transform = offScreenBottom 44 | } 45 | 46 | UIView.animate(withDuration: duration, 47 | animations: { 48 | avatar?.frame = targetFrame 49 | toView.alpha = 1 50 | toView.transform = CGAffineTransform.identity 51 | if !self.isPresenting { 52 | fromView.alpha = 0 53 | fromView.transform = offScreenBottom 54 | } 55 | }, 56 | completion: { finished in 57 | fromView.alpha = 1 58 | fromAvatar.alpha = 1 59 | toAvatar.alpha = 1 60 | fromView.transform = CGAffineTransform.identity 61 | toView.transform = CGAffineTransform.identity 62 | avatar?.removeFromSuperview() 63 | transitionContext.completeTransition(true) 64 | } 65 | ) 66 | } 67 | 68 | func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 69 | return 0.5 70 | } 71 | 72 | // MARK: UIViewControllerTransitioningDelegate 73 | func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { 74 | isPresenting = true 75 | return self 76 | } 77 | func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { 78 | isPresenting = false 79 | return self 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Uther/General/Transition/WelcomeTransitionManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeTransitionManager.swift 3 | // Uther 4 | // 5 | // Created by why on 8/8/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class WelcomeTransitionManager: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate { 12 | // MARK: UIViewControllerAnimatedTransitioning 13 | func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 14 | let container = transitionContext.containerView 15 | let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)! 16 | let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)! 17 | container.addSubview(toView) 18 | container.addSubview(fromView) 19 | 20 | // avatar 21 | let fromAvatar = fromView.viewWithTag(1001)! 22 | let toAvatar = toView.viewWithTag(1002)! 23 | 24 | let avatar = fromAvatar.snapshotView(afterScreenUpdates: true) 25 | avatar?.frame = fromAvatar.frame 26 | container.addSubview(avatar!) 27 | 28 | fromAvatar.alpha = 0 29 | toAvatar.alpha = 0 30 | let duration = self.transitionDuration(using: transitionContext) 31 | UIView.animate(withDuration: duration, animations: { 32 | fromView.alpha = 0 33 | avatar?.frame = toAvatar.frame 34 | }, 35 | completion: { finished in 36 | fromView.alpha = 1 37 | fromAvatar.alpha = 1 38 | toAvatar.alpha = 1 39 | avatar?.removeFromSuperview() 40 | transitionContext.completeTransition(true) 41 | } 42 | ) 43 | } 44 | 45 | func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 46 | return 0.5 47 | } 48 | 49 | // MARK: UIViewControllerTransitioningDelegate 50 | func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { 51 | return self 52 | } 53 | func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { 54 | return self 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Uther/Images.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-Small@2x.png", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "Icon-Small@3x.png", 23 | "scale" : "3x" 24 | }, 25 | { 26 | "size" : "40x40", 27 | "idiom" : "iphone", 28 | "filename" : "Icon-40@2x.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-Small.png", 63 | "scale" : "1x" 64 | }, 65 | { 66 | "size" : "29x29", 67 | "idiom" : "ipad", 68 | "filename" : "Icon-Small@2x.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 | } -------------------------------------------------------------------------------- /Uther/Images.xcassets/AppIcon.appiconset/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmewhy/Uther/9d7c0f9b4039aa3cd7d1f1f04a1e42cae8455687/Uther/Images.xcassets/AppIcon.appiconset/Icon-40.png -------------------------------------------------------------------------------- /Uther/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmewhy/Uther/9d7c0f9b4039aa3cd7d1f1f04a1e42cae8455687/Uther/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /Uther/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmewhy/Uther/9d7c0f9b4039aa3cd7d1f1f04a1e42cae8455687/Uther/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /Uther/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmewhy/Uther/9d7c0f9b4039aa3cd7d1f1f04a1e42cae8455687/Uther/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /Uther/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmewhy/Uther/9d7c0f9b4039aa3cd7d1f1f04a1e42cae8455687/Uther/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /Uther/Images.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmewhy/Uther/9d7c0f9b4039aa3cd7d1f1f04a1e42cae8455687/Uther/Images.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /Uther/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmewhy/Uther/9d7c0f9b4039aa3cd7d1f1f04a1e42cae8455687/Uther/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /Uther/Images.xcassets/AppIcon.appiconset/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmewhy/Uther/9d7c0f9b4039aa3cd7d1f1f04a1e42cae8455687/Uther/Images.xcassets/AppIcon.appiconset/Icon-83.5@2x.png -------------------------------------------------------------------------------- /Uther/Images.xcassets/AppIcon.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmewhy/Uther/9d7c0f9b4039aa3cd7d1f1f04a1e42cae8455687/Uther/Images.xcassets/AppIcon.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /Uther/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmewhy/Uther/9d7c0f9b4039aa3cd7d1f1f04a1e42cae8455687/Uther/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /Uther/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmewhy/Uther/9d7c0f9b4039aa3cd7d1f1f04a1e42cae8455687/Uther/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /Uther/Images.xcassets/CGAvatar.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon01.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Uther/Images.xcassets/CGAvatar.imageset/icon01.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmewhy/Uther/9d7c0f9b4039aa3cd7d1f1f04a1e42cae8455687/Uther/Images.xcassets/CGAvatar.imageset/icon01.pdf -------------------------------------------------------------------------------- /Uther/Images.xcassets/Particles/Fire.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "Fire.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Uther/Images.xcassets/Particles/Fire.imageset/Fire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmewhy/Uther/9d7c0f9b4039aa3cd7d1f1f04a1e42cae8455687/Uther/Images.xcassets/Particles/Fire.imageset/Fire.png -------------------------------------------------------------------------------- /Uther/Images.xcassets/Particles/Fragment.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "Fragment.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Uther/Images.xcassets/Particles/Fragment.imageset/Fragment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmewhy/Uther/9d7c0f9b4039aa3cd7d1f1f04a1e42cae8455687/Uther/Images.xcassets/Particles/Fragment.imageset/Fragment.png -------------------------------------------------------------------------------- /Uther/Images.xcassets/Particles/Smoke.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "Smoke.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Uther/Images.xcassets/Particles/Smoke.imageset/Smoke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmewhy/Uther/9d7c0f9b4039aa3cd7d1f1f04a1e42cae8455687/Uther/Images.xcassets/Particles/Smoke.imageset/Smoke.png -------------------------------------------------------------------------------- /Uther/Images.xcassets/Particles/Sparkle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "Sparkle.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Uther/Images.xcassets/Particles/Sparkle.imageset/Sparkle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmewhy/Uther/9d7c0f9b4039aa3cd7d1f1f04a1e42cae8455687/Uther/Images.xcassets/Particles/Sparkle.imageset/Sparkle.png -------------------------------------------------------------------------------- /Uther/Images.xcassets/StarBackground.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Untitled Copy.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Uther/Images.xcassets/StarBackground.imageset/Untitled Copy.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmewhy/Uther/9d7c0f9b4039aa3cd7d1f1f04a1e42cae8455687/Uther/Images.xcassets/StarBackground.imageset/Untitled Copy.pdf -------------------------------------------------------------------------------- /Uther/Images.xcassets/UtherAvatar.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Slice+1.compressed.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Uther/Images.xcassets/UtherAvatar.imageset/Slice+1.compressed.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmewhy/Uther/9d7c0f9b4039aa3cd7d1f1f04a1e42cae8455687/Uther/Images.xcassets/UtherAvatar.imageset/Slice+1.compressed.pdf -------------------------------------------------------------------------------- /Uther/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 | APPL 17 | CFBundleShortVersionString 18 | 1.2.3 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 3 23 | Fabric 24 | 25 | APIKey 26 | a44d8087e7d8eb1eeb2b7bd73784f5a725b7fcb5 27 | Kits 28 | 29 | 30 | KitInfo 31 | 32 | KitName 33 | Crashlytics 34 | 35 | 36 | 37 | LSRequiresIPhoneOS 38 | 39 | NSAppTransportSecurity 40 | 41 | NSExceptionDomains 42 | 43 | text-processing.com 44 | 45 | NSExceptionAllowsInsecureHTTPLoads 46 | 47 | NSExceptionRequiresForwardSecrecy 48 | NO 49 | NSIncludesSubdomains 50 | 51 | 52 | 53 | 54 | UILaunchStoryboardName 55 | LaunchScreen 56 | UIMainStoryboardFile 57 | Main 58 | UIRequiredDeviceCapabilities 59 | 60 | armv7 61 | 62 | UIStatusBarHidden 63 | 64 | UISupportedInterfaceOrientations 65 | 66 | UIInterfaceOrientationPortrait 67 | 68 | UISupportedInterfaceOrientations~ipad 69 | 70 | UIInterfaceOrientationPortrait 71 | UIInterfaceOrientationPortraitUpsideDown 72 | UIInterfaceOrientationLandscapeLeft 73 | UIInterfaceOrientationLandscapeRight 74 | 75 | UIViewControllerBasedStatusBarAppearance 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Uther/Resource/Emoji.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 1.0 6 | 7 | ٩(๑❛ᴗ❛๑)۶ 8 | |( ̄3 ̄)| 9 | ┏(^0^)┛ 10 | (/≧▽≦/) 11 | ฅ( ̳• ◡ • ̳)ฅ 12 | 13 | 0.9 14 | 15 | 〜( ̄▽ ̄〜) 16 | 〜( ̄▽ ̄)〜* 17 | (~_~;) 18 | ( ̄︶ ̄)〜 19 | (*^_^*) 20 | 21 | 0.8 22 | 23 | ( ´ ▽ ` )ノ 24 | ฅ( ̳• ◡ • ̳)ฅ 25 | (≖ ‿ ≖)✧ 26 | ー( ´ ▽ ` )ノ 27 | (・∀・) 28 | 29 | 0.7 30 | 31 | (^_^)v 32 | p(*^-^*)q 33 | ( ̄0  ̄)y 34 | <( ̄︶ ̄)/ 35 | (@ ̄︶ ̄@) 36 | 37 | 0.6 38 | 39 | 八(^□^*) 40 | (・_・ヾ 41 | (-’๏_๏’-) 42 | (─__─) 43 | (。・ˍ・。) 44 | 45 | 0.5 46 | 47 | ( ・◇・)? 48 | ⊂((〃’⊥’〃))⊃ 49 | _(._.)_ 50 | ( ̄・・ ̄) 51 | (→_→) 52 | (|||❛︵❛.) 53 | o_O? 54 | (ㆁ × ㆁ) 55 | (..◜ᴗ◝..) 56 | (⊙_⊙;) 57 | 58 | 0.4 59 | 60 | (´・_・`) 61 | (⌒_⌒;) 62 | (* ̄(エ) ̄*) 63 | (>_<) 64 | (/ˍ・、) 65 | 66 | 0.3 67 | 68 | ( ̄^ ̄) 69 | (*・_・)ノ⌒* 70 | (—.—||) 71 | (⊙ˍ⊙) 72 | <(‵^′)> 73 | 74 | 0.2 75 | 76 | (ㄒoㄒ) 77 | (/_\) 78 | (_・・_) 79 | (@・ˍ・) 80 | (。•́︿•̀。) 81 | 82 | 0.1 83 | 84 | (o´・_・)っ 85 | (ノ*゚ー゚)ノ 86 | (。・ˇ_ˇ・。) 87 | (。_。) 88 | (。・`ω´・) 89 | 90 | 0.0 91 | 92 | (┬_┬) 93 | (︶︿︶) 94 | ╰(‵□′)╯ 95 | (>﹏<) 96 | (╯‵□′)╯ 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Uther/Section/History/HistoryCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HistoryCell.swift 3 | // Uther 4 | // 5 | // Created by why on 8/13/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class HistoryCell: UITableViewCell { 12 | @IBOutlet weak var messageLabel: UILabel! 13 | 14 | func update(_ text: String, seperatorHidden: Bool) { 15 | messageLabel.text = text 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Uther/Section/History/HistoryDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HistoryDataSource.swift 3 | // Uther 4 | // 5 | // Created by why on 8/13/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftDate 11 | import SwiftHEXColors 12 | 13 | class HistoryDataSource: NSObject { 14 | fileprivate var messages = [Date: [Message]]() { 15 | didSet { 16 | dataSource = Array(messages.keys).sorted(by: >).map { date -> [Message] in self.messages[date]! } 17 | } 18 | } 19 | fileprivate var dataSource = [[Message]]() 20 | 21 | func loadFromDatabase() -> Int { 22 | let offset = dataSource.reduce(0) { $0 + $1.count } 23 | let newMessages = DB.getReverseMessages(offset, limit: 20) 24 | for message in newMessages { 25 | let date = message.date.startOfDay 26 | var tMessages = messages[date] ?? [] 27 | tMessages.append(message) 28 | messages[date] = tMessages 29 | } 30 | return dataSource.reduce(0) { $0 + $1.count } - offset 31 | } 32 | } 33 | 34 | // MARK: - Private 35 | extension HistoryDataSource { 36 | fileprivate func removeMessage(_ indexPath: IndexPath) { 37 | let message = dataSource[indexPath.section][indexPath.row] 38 | var cMessages = messages[message.date.startOfDay] 39 | if let index = cMessages!.index(of: message) { 40 | cMessages!.remove(at: index) 41 | messages[message.date.startOfDay] = cMessages 42 | message.deleteFromDatabase() 43 | } 44 | } 45 | } 46 | 47 | // MARK: - UICollectionViewDataSource 48 | extension HistoryDataSource: UITableViewDataSource { 49 | func numberOfSections(in tableView: UITableView) -> Int { 50 | return dataSource.count 51 | } 52 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 53 | return dataSource[section].count 54 | } 55 | 56 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 57 | let cell = tableView.dequeueReusableCell(withIdentifier: "HistoryCell", for: indexPath) as! HistoryCell 58 | let tMessages = dataSource[indexPath.section] 59 | let message = tMessages[indexPath.row] 60 | let isLast = (tMessages.count - 1 == indexPath.row) 61 | cell.update(message.text, seperatorHidden:isLast) 62 | cell.backgroundColor = UIColor.clear 63 | return cell 64 | } 65 | 66 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 67 | return dataSource[section][0].date.toString() 68 | } 69 | 70 | func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { 71 | switch editingStyle { 72 | case .delete: 73 | removeMessage(indexPath) 74 | tableView.deleteRows(at: [indexPath], with: .fade) 75 | default: 76 | break 77 | } 78 | } 79 | } 80 | 81 | -------------------------------------------------------------------------------- /Uther/Section/History/HistoryViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HistoryViewController.swift 3 | // Uther 4 | // 5 | // Created by why on 8/2/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Async 11 | 12 | class HistoryViewController: UIViewController { 13 | let dataSource = HistoryDataSource() 14 | fileprivate var isLoading = false 15 | 16 | @IBOutlet weak var tableView: UITableView! { 17 | didSet { 18 | tableView.dataSource = dataSource 19 | tableView.delegate = self 20 | tableView.rowHeight = UITableViewAutomaticDimension; 21 | tableView.estimatedRowHeight = 44.0; 22 | } 23 | } 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | let _ = dataSource.loadFromDatabase() 28 | } 29 | 30 | @IBAction func back(_ sender: AnyObject) { 31 | self.dismiss(animated: true) {} 32 | } 33 | } 34 | 35 | // MARK: - Private 36 | extension HistoryViewController { 37 | func setupHeaderMask() { 38 | guard var v = tableView.indexPathsForVisibleRows else { 39 | return 40 | } 41 | if let first = v.first { 42 | if first.row > 0 { 43 | v.append(IndexPath(row: first.row - 1, section: first.section)) 44 | } 45 | } 46 | 47 | for i in v { 48 | let headerHeight = tableView.delegate!.tableView!(tableView, heightForHeaderInSection: i.section) 49 | let cell = tableView.dataSource!.tableView(tableView, cellForRowAt: i) 50 | let height = tableView.contentOffset.y + headerHeight - cell.frame.origin.y; 51 | if (height > 0 && height < cell.height) { 52 | let maskLayer = CAShapeLayer() 53 | maskLayer.path = CGPath(rect: CGRect(x: 0, y: height, width: cell.width, height: cell.height - height), transform: nil) 54 | cell.layer.mask = maskLayer 55 | } else if height < 0 { 56 | cell.layer.mask = nil 57 | } 58 | } 59 | } 60 | } 61 | 62 | // MARK: - UITableViewDelegate 63 | extension HistoryViewController: UITableViewDelegate { 64 | func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 65 | return 20 66 | } 67 | 68 | func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 69 | let height = tableView.delegate!.tableView!(tableView, heightForHeaderInSection: section) 70 | let view = UIView(frame: CGRect(x: 0, y: 0, width: tableView.width, height: height)) 71 | view.backgroundColor = UIColor(white: 1, alpha: 0.1) 72 | let label = UILabel(frame: view.bounds) 73 | label.textAlignment = .center 74 | label.font = UIFont.systemFont(ofSize: 13) 75 | label.text = dataSource.tableView(tableView, titleForHeaderInSection: section) 76 | label.textColor = UIColor(hexString: "f5f5f5", alpha: 0.5) 77 | view.addSubview(label) 78 | return view 79 | } 80 | } 81 | 82 | 83 | // MARK: - UIScrollViewDelegate 84 | extension HistoryViewController: UIScrollViewDelegate { 85 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 86 | // self.setupHeaderMask() 87 | if isLoading { 88 | return 89 | } 90 | let contentHeight = tableView.contentSize.height 91 | if tableView.contentOffset.y + tableView.height > contentHeight { 92 | isLoading = true 93 | Async.background { 94 | let count = self.dataSource.loadFromDatabase() 95 | if count == 0 { 96 | return 97 | } 98 | Async.main { 99 | self.tableView.reloadData() 100 | self.tableView.layoutIfNeeded() 101 | self.isLoading = false 102 | } 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Uther/Section/Main/MainDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainDataSource.swift 3 | // Uther 4 | // 5 | // Created by why on 7/30/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftDate 11 | import SwiftHEXColors 12 | import JSQMessagesViewController 13 | 14 | class MainDataSource: NSObject { 15 | fileprivate var messages:[Message] = [] 16 | 17 | func loadFromDatabase() -> Int { 18 | let offset = messages.count 19 | messages = DB.getReverseMessages(offset, limit: 3).reversed() + messages 20 | return messages.count - offset 21 | } 22 | 23 | func addTextMessage(_ text:String, saved:([Message])->(), received:@escaping (EventType)->()) { 24 | let message = TextMessage(text: text) 25 | message.saveToDatabase() 26 | messages.append(message) 27 | saved(messages) 28 | 29 | Uther.handleMessage(text, completion: { e in 30 | switch e { 31 | case let .emoji(p): 32 | message.positive = p 33 | message.saveToDatabase() 34 | default: 35 | break 36 | } 37 | received(e) 38 | }) 39 | } 40 | 41 | func removeMessageAtIndex(_ i: Int) -> Message? { 42 | if i >= 0 && i < messages.count { 43 | return messages.remove(at: i) 44 | } 45 | return nil 46 | } 47 | } 48 | 49 | // MARK: - UICollectionViewDataSource 50 | extension MainDataSource: UICollectionViewDataSource { 51 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 52 | return messages.count 53 | } 54 | 55 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 56 | let collectionView = collectionView as! JSQMessagesCollectionView 57 | let dataSource = collectionView.dataSource! 58 | let message = dataSource.collectionView(collectionView, messageDataForItemAt: indexPath) 59 | 60 | // cell 61 | let identifier: String = JSQMessagesCollectionViewCellOutgoing.cellReuseIdentifier() 62 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as! JSQMessagesCollectionViewCell 63 | cell.delegate = collectionView 64 | cell.backgroundColor = UIColor.clear 65 | 66 | // label 67 | cell.cellTopLabel!.attributedText = dataSource.collectionView?(collectionView, attributedTextForCellTopLabelAt: indexPath) 68 | cell.messageBubbleTopLabel!.attributedText = dataSource.collectionView?(collectionView, attributedTextForMessageBubbleTopLabelAt: indexPath) 69 | cell.cellBottomLabel!.attributedText = dataSource.collectionView?(collectionView, attributedTextForCellBottomLabelAt: indexPath) 70 | 71 | // text 72 | cell.textView!.text = message?.text!() 73 | cell.textView!.textColor = UIColor(hexString: "#F5F5F5")! 74 | cell.textView!.isUserInteractionEnabled = false 75 | 76 | // bubble image 77 | if let bubbleDataSource = dataSource.collectionView(collectionView, messageBubbleImageDataForItemAt: indexPath) { 78 | cell.messageBubbleImageView!.image = bubbleDataSource.messageBubbleImage() 79 | cell.messageBubbleImageView!.highlightedImage = bubbleDataSource.messageBubbleHighlightedImage() 80 | } 81 | 82 | return cell 83 | } 84 | } 85 | 86 | // MARK: - JSQMessagesCollectionViewDataSource 87 | extension MainDataSource: JSQMessagesCollectionViewDataSource { 88 | public func collectionView(_ collectionView: JSQMessagesCollectionView!, avatarImageDataForItemAt indexPath: IndexPath!) -> JSQMessageAvatarImageDataSource! { 89 | return nil 90 | } 91 | 92 | public func collectionView(_ collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAt indexPath: IndexPath!) -> JSQMessageBubbleImageDataSource! { 93 | let factory = JSQMessagesBubbleImageFactory() 94 | return factory!.outgoingMessagesBubbleImage(with: UIColor(white: 1, alpha: 0.2)) 95 | } 96 | 97 | public func senderDisplayName() -> String! { 98 | return "" 99 | } 100 | 101 | public func senderId() -> String! { 102 | return "ME" 103 | } 104 | 105 | public func collectionView(_ collectionView: JSQMessagesCollectionView!, messageDataForItemAt indexPath: IndexPath!) -> JSQMessageData! { 106 | return messages[indexPath.row] 107 | } 108 | 109 | public func collectionView(_ collectionView: JSQMessagesCollectionView!, didDeleteMessageAt indexPath: IndexPath!) { 110 | 111 | } 112 | 113 | public func collectionView(_ collectionView: JSQMessagesCollectionView!, attributedTextForCellTopLabelAt indexPath: IndexPath!) -> NSAttributedString! { 114 | let message = messages[indexPath.row] 115 | let attributes = [ NSForegroundColorAttributeName: UIColor(hexString: "#F5F5F5")!.withAlphaComponent(0.5) ] 116 | let dateFormatter = DateFormatter() 117 | dateFormatter.dateFormat = "yyyy-MM-dd" 118 | let dateString = dateFormatter.string(from: message.date) 119 | let time = NSAttributedString(string: dateString, attributes: attributes) 120 | return time 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Uther/Section/Main/MainViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.swift 3 | // Uther 4 | // 5 | // Created by why on 7/29/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import KeyboardMan 11 | import JSQMessagesViewController 12 | 13 | class MainViewController: UIViewController { 14 | 15 | let keyboardMan = KeyboardMan() 16 | let chatDataSource = MainDataSource() 17 | var keyboardShowing = false 18 | 19 | var utherViewController: UtherDisplayViewController! 20 | @IBOutlet weak var bottomConstraint: NSLayoutConstraint! 21 | @IBOutlet weak var heightConstraint: NSLayoutConstraint! 22 | @IBOutlet weak var collectionContainerView: UIView! 23 | @IBOutlet weak var collectionView: JSQMessagesCollectionView! 24 | 25 | @IBOutlet weak var composerView: UITextView! { 26 | didSet { 27 | composerView.textColor = UIColor(hexString: "#F4FFF8") 28 | composerView.backgroundColor = UIColor(white: 1, alpha: 0.05) 29 | composerView.layer.borderColor = UIColor(hexString: "#F4FFF8", alpha: 0.2)?.cgColor 30 | composerView.layer.borderWidth = 1 31 | composerView.layer.cornerRadius = 5 32 | } 33 | } 34 | 35 | override func viewDidLoad() { 36 | super.viewDidLoad() 37 | setupKeyboardAnimation() 38 | let _ = chatDataSource.loadFromDatabase() 39 | collectionView.backgroundColor = UIColor.clear 40 | collectionView.keyboardDismissMode = .onDrag 41 | collectionView.dataSource = chatDataSource 42 | collectionView.delegate = self 43 | } 44 | 45 | override func viewDidLayoutSubviews() { 46 | super.viewDidLayoutSubviews(); 47 | if collectionContainerView.layer.mask == nil { 48 | self.setupGradientMask(); 49 | } 50 | } 51 | 52 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 53 | if segue.identifier == "uther_display" { 54 | utherViewController = segue.destination as! UtherDisplayViewController; 55 | } 56 | } 57 | 58 | @IBAction func backgroundTaped(_ sender: AnyObject) { 59 | view.endEditing(true) 60 | } 61 | 62 | @IBAction func inputBackgroundTaped(_ sender: AnyObject) { 63 | composerView.becomeFirstResponder() 64 | } 65 | } 66 | 67 | // MARK: - Setup 68 | extension MainViewController { 69 | func setupKeyboardAnimation() { 70 | keyboardMan.animateWhenKeyboardAppear = { [unowned self] _, keyboardHeight, _ in 71 | self.bottomConstraint.constant = keyboardHeight 72 | self.view.layoutIfNeeded() 73 | self.collectionView.scrollToBottom() 74 | self.keyboardShowing = true 75 | } 76 | keyboardMan.animateWhenKeyboardDisappear = { [unowned self] keyboardHeight in 77 | self.bottomConstraint.constant = 0 78 | self.view.layoutIfNeeded() 79 | self.keyboardShowing = false 80 | } 81 | } 82 | 83 | func setupGradientMask() { 84 | let gradient = CAGradientLayer(); 85 | gradient.frame = collectionContainerView.bounds 86 | gradient.colors = [UIColor.clear.cgColor, UIColor.black.cgColor]; 87 | gradient.locations = [0, 0.3] 88 | collectionContainerView.layer.mask = gradient 89 | } 90 | } 91 | 92 | // MARK: - Private 93 | extension MainViewController { 94 | fileprivate func updateCollectionViewWithMessages(_ messages: [Message]) { 95 | if messages.count <= 3 { 96 | return 97 | } 98 | let layout = collectionView.collectionViewLayout! 99 | let firstIndexPath = IndexPath(row: 0, section: 0) 100 | let firstCellHeight = layout.sizeForItem(at: firstIndexPath).height + layout.minimumLineSpacing 101 | let oldOffsetY = collectionView.contentOffset.y 102 | let oldContentHeight = layout.collectionViewContentSize.height 103 | 104 | let c = collectionView! 105 | UIView.animate(withDuration: 0) { _ in 106 | c.performBatchUpdates({ 107 | let _ = self.chatDataSource.removeMessageAtIndex(0) 108 | c.deleteItems(at: [NSIndexPath(row: 0, section: 0) as IndexPath]) 109 | c.contentOffset = CGPoint(x: 0, y: oldOffsetY - firstCellHeight) 110 | }, completion:nil) 111 | } 112 | 113 | let y = max(oldContentHeight - firstCellHeight - collectionView.height, 0.0) 114 | UIView.animate(withDuration: kDefaultAnimationDuration) { _ in 115 | c.contentOffset = CGPoint(x: 0, y: y) 116 | } 117 | } 118 | } 119 | 120 | // MARK: - MessageComposerDelegate 121 | extension MainViewController: MessageComposerDelegate { 122 | func sendMessage(_ textView: UITextView, text: String) { 123 | chatDataSource.addTextMessage(text, 124 | saved: { messages in 125 | self.collectionView.reloadData() 126 | self.updateCollectionViewWithMessages(messages) 127 | }, 128 | received: { event in 129 | self.utherViewController.updateWithEventType(event) 130 | } 131 | ) 132 | } 133 | 134 | func messageDidChange(_ textView: UITextView, height: CGFloat) { 135 | let oldH = heightConstraint.constant; 136 | let newH = height 137 | if (fabs(newH - oldH) > 1) { 138 | heightConstraint.constant = newH 139 | UIView.animate(withDuration: kDefaultAnimationDuration, animations: { () -> Void in 140 | self.view.layoutIfNeeded() 141 | }) 142 | collectionView.scrollToBottom(true) 143 | } 144 | } 145 | } 146 | 147 | // MARK: - UICollectionDelegate 148 | extension MainViewController: JSQMessagesCollectionViewDelegateFlowLayout { 149 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 150 | let layout = collectionViewLayout as! JSQMessagesCollectionViewFlowLayout 151 | return layout.sizeForItem(at: indexPath) 152 | } 153 | 154 | func collectionView(_ collectionView: JSQMessagesCollectionView!, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout!, heightForCellTopLabelAt indexPath: IndexPath!) -> CGFloat { 155 | return 0.0 156 | } 157 | 158 | func collectionView(_ collectionView: JSQMessagesCollectionView!, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout!, heightForMessageBubbleTopLabelAt indexPath: IndexPath!) -> CGFloat { 159 | return 0.0 160 | } 161 | 162 | func collectionView(_ collectionView: JSQMessagesCollectionView!, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout!, heightForCellBottomLabelAt indexPath: IndexPath!) -> CGFloat { 163 | return 0.0 164 | } 165 | 166 | func collectionView(_ collectionView: JSQMessagesCollectionView!, didTapAvatarImageView avatarImageView: UIImageView!, at indexPath: IndexPath!) { 167 | 168 | } 169 | 170 | func collectionView(_ collectionView: JSQMessagesCollectionView!, didTapMessageBubbleAt indexPath: IndexPath!) { 171 | view.endEditing(true) 172 | } 173 | 174 | func collectionView(_ collectionView: JSQMessagesCollectionView!, didTapCellAt indexPath: IndexPath!, touchLocation: CGPoint) { 175 | view.endEditing(true) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /Uther/Section/Main/MessageComposerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageComposerView.swift 3 | // Uther 4 | // 5 | // Created by why on 7/29/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @objc protocol MessageComposerDelegate { 12 | func messageDidChange(_ textView: UITextView, height: CGFloat) 13 | func sendMessage(_ textView: UITextView, text: String) 14 | } 15 | 16 | 17 | private let kMaxHeight = CGFloat(150) 18 | 19 | class MessageComposerView: UITextView { 20 | @IBOutlet weak var messageDelegate: AnyObject? 21 | 22 | let textPaddingH:CGFloat = 8.0 23 | let textPaddingV:CGFloat = 6.0 24 | 25 | override func awakeFromNib() { 26 | self.textContainerInset = UIEdgeInsetsMake(textPaddingV, textPaddingH, textPaddingV, textPaddingH) 27 | self.textContainer.lineFragmentPadding = 0 28 | self.layoutManager.allowsNonContiguousLayout = false 29 | self.delegate = self 30 | } 31 | 32 | override func scrollRectToVisible(_ rect: CGRect, animated: Bool) { 33 | } 34 | 35 | func scrollRectToVisible(_ rect: CGRect) { 36 | if contentSize.height < kMaxHeight { 37 | return 38 | } 39 | super.scrollRectToVisible(rect, animated: true) 40 | } 41 | } 42 | 43 | // MARK: - UITextViewDelegate 44 | extension MessageComposerView: UITextViewDelegate { 45 | func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { 46 | if (text == "\n") { 47 | messageDelegate?.sendMessage(self, text: self.text) 48 | self.text = "" 49 | textViewDidChange(self) 50 | return false; 51 | } 52 | return true; 53 | } 54 | 55 | func textViewDidChange(_ textView: UITextView) { 56 | resetContentSizeAndOffset() 57 | } 58 | } 59 | 60 | // MARK: - Private 61 | extension MessageComposerView { 62 | func resetContentSizeAndOffset() { 63 | layoutIfNeeded() 64 | messageDelegate?.messageDidChange(self, height: min(self.contentSize.height, kMaxHeight)) 65 | if let selectedTextRange = self.selectedTextRange { 66 | let caretRect = self.caretRect(for: selectedTextRange.end); 67 | let height = textContainerInset.bottom + caretRect.size.height 68 | self.scrollRectToVisible(CGRect(x: caretRect.origin.x, y: caretRect.origin.y, width: caretRect.size.width, height: height)) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Uther/Section/Main/UtherDisplayViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UtherDisplayViewController.swift 3 | // Uther 4 | // 5 | // Created by why on 7/29/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import LTMorphingLabel 11 | import Async 12 | 13 | // 消息更新的事件 14 | enum EventType: CustomStringConvertible { 15 | // 错误 16 | case error 17 | // 设置头像 18 | case avatar(ImageName) 19 | // 设置表情 20 | case emoji(PositiveValue) 21 | // 设置文字 22 | case text(String) 23 | 24 | var description: String { 25 | get { 26 | switch self { 27 | case .error: 28 | return "Error" 29 | case let .avatar(i): 30 | return "Avatar:\(i)" 31 | case let .emoji(p): 32 | return "Emoji(\(p))" 33 | case let .text(t): 34 | return "Text:\(t)" 35 | } 36 | } 37 | } 38 | } 39 | 40 | class UtherDisplayViewController: UIViewController { 41 | 42 | @IBOutlet weak var avatarImageView: UIImageView! 43 | @IBOutlet weak var messageLabel: LTMorphingLabel! 44 | 45 | override func viewDidLoad() { 46 | super.viewDidLoad() 47 | } 48 | override func viewDidAppear(_ animated: Bool) { 49 | super.viewDidAppear(animated) 50 | } 51 | 52 | func updateWithEventType(_ event: EventType) { 53 | log.debug(event.description) 54 | switch event { 55 | case let .avatar(imageName): 56 | avatarImageView.image = UIImage(named: imageName) 57 | case let .emoji(p): 58 | messageLabel.text = p.emoji + (debug ? " \(p)" : "") 59 | messageLabel.morphingEffect = LTMorphingEffect.next() 60 | case let .text(text): 61 | messageLabel.text = text 62 | case .error: 63 | messageLabel.text = "_(:з」∠)_" + (debug ? " error" : "") 64 | } 65 | } 66 | 67 | let transitionManager = HistoryTransitionManager() 68 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 69 | if segue.identifier == "show_history" { 70 | let toViewController = segue.destination 71 | toViewController.transitioningDelegate = self.transitionManager 72 | } 73 | 74 | } 75 | @IBAction func avatarTapped(_ sender: AnyObject) { 76 | let vc = self.parent! as! MainViewController 77 | if vc.keyboardShowing { 78 | vc.view!.endEditing(true) 79 | Async.main(after: 0.3) { 80 | self.performSegue(withIdentifier: "show_history", sender: sender) 81 | } 82 | } else { 83 | self.performSegue(withIdentifier: "show_history", sender: sender) 84 | } 85 | } 86 | } 87 | 88 | extension UtherDisplayViewController: LTMorphingLabelDelegate { 89 | func morphingDidStart(_ label: LTMorphingLabel) { 90 | 91 | } 92 | 93 | func morphingDidComplete(_ label: LTMorphingLabel) { 94 | 95 | } 96 | 97 | func morphingOnProgress(_ label: LTMorphingLabel, _ progress: Float) { 98 | 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Uther/Section/Welcome/WelcomeViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeViewController.swift 3 | // Uther 4 | // 5 | // Created by why on 8/8/15. 6 | // Copyright (c) 2015 callmewhy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class WelcomeViewController: UIViewController { 12 | let transitionManager = WelcomeTransitionManager() 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | UIApplication.shared.setStatusBarHidden(false, with: .slide) 17 | UIApplication.shared.setStatusBarStyle(.lightContent, animated: true) 18 | } 19 | 20 | override func viewDidAppear(_ animated: Bool) { 21 | self.performSegue(withIdentifier: "show_main", sender: nil) 22 | } 23 | 24 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 25 | let toViewController = segue.destination 26 | toViewController.transitioningDelegate = self.transitionManager 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Uther/Storyboard/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Uther/Storyboard/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 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 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 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 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 | 309 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | -------------------------------------------------------------------------------- /Uther/Uther-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import 6 | 7 | --------------------------------------------------------------------------------