├── LICENSE
├── Podfile
├── Podfile.lock
├── R.generated.swift
├── README.md
├── TweeBox.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── xcuserdata
│ └── 4faramita.xcuserdatad
│ └── xcschemes
│ ├── TweeBox.xcscheme
│ └── xcschememanagement.plist
├── TweeBox.xcworkspace
├── contents.xcworkspacedata
└── xcuserdata
│ └── 4faramita.xcuserdatad
│ └── xcdebugger
│ └── Breakpoints_v2.xcbkptlist
├── TweeBox
├── AppDelegate.swift
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── icon@2x.png
│ │ └── icon@3x.png
│ ├── Contents.json
│ ├── delete.imageset
│ │ ├── Contents.json
│ │ ├── cross (1).png
│ │ ├── cross (2).png
│ │ └── cross.png
│ ├── hashtag.imageset
│ │ ├── Contents.json
│ │ ├── hashtag (1).png
│ │ ├── hashtag (2).png
│ │ └── hashtag.png
│ ├── launch_screen.imageset
│ │ ├── Contents.json
│ │ ├── launch_screen@2x.png
│ │ └── launch_screen@3x.png
│ ├── like_false.imageset
│ │ ├── Contents.json
│ │ ├── like (1).png
│ │ ├── like (2).png
│ │ └── like (3).png
│ ├── like_true.imageset
│ │ ├── Contents.json
│ │ ├── like (4).png
│ │ ├── like (5).png
│ │ └── like (6).png
│ ├── mention.imageset
│ │ ├── Contents.json
│ │ ├── at-2.png
│ │ ├── at-3.png
│ │ └── at.png
│ ├── mention_thin.imageset
│ │ ├── Contents.json
│ │ ├── arroba (1).png
│ │ ├── arroba (2).png
│ │ └── arroba.png
│ ├── picPlaceholder.imageset
│ │ ├── Contents.json
│ │ └── placeholder.png
│ ├── picture.imageset
│ │ ├── Contents.json
│ │ ├── picture (1).png
│ │ ├── picture (2).png
│ │ └── picture (3).png
│ ├── play_gif.imageset
│ │ ├── Contents.json
│ │ ├── gif-images-file-type-interface-symbol-of-stroke (1).png
│ │ ├── gif-images-file-type-interface-symbol-of-stroke (2).png
│ │ └── gif-images-file-type-interface-symbol-of-stroke.png
│ ├── play_video.imageset
│ │ ├── Contents.json
│ │ ├── play-button (1).png
│ │ ├── play-button (2).png
│ │ └── play-button.png
│ ├── profile.imageset
│ │ ├── Contents.json
│ │ ├── avatar (1).png
│ │ ├── avatar (2).png
│ │ └── avatar.png
│ ├── profile_selected.imageset
│ │ ├── Contents.json
│ │ ├── man-user (1).png
│ │ ├── man-user (2).png
│ │ └── man-user.png
│ ├── reply.imageset
│ │ ├── Contents.json
│ │ ├── reply (1).png
│ │ ├── reply (2).png
│ │ └── reply (3).png
│ ├── reply_false.imageset
│ │ ├── Contents.json
│ │ ├── reply (4).png
│ │ ├── reply (5).png
│ │ └── reply (6).png
│ ├── reply_true.imageset
│ │ ├── Contents.json
│ │ ├── reply (7).png
│ │ ├── reply (8).png
│ │ └── reply (9).png
│ ├── retweet_false.imageset
│ │ ├── Contents.json
│ │ ├── retweet-arrows-couple-outline (1).png
│ │ ├── retweet-arrows-couple-outline (2).png
│ │ └── retweet-arrows-couple-outline.png
│ ├── retweet_true.imageset
│ │ ├── Contents.json
│ │ ├── retweet-symbol (1).png
│ │ ├── retweet-symbol (2).png
│ │ └── retweet-symbol.png
│ ├── twitter.imageset
│ │ ├── Contents.json
│ │ ├── twitter (6).png
│ │ ├── twitter (7).png
│ │ └── twitter (8).png
│ └── twitter_selected.imageset
│ │ ├── Contents.json
│ │ ├── twitter (1).png
│ │ ├── twitter (2).png
│ │ └── twitter.png
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── Constants.swift
├── Coordinates.swift
├── DataExtension.swift
├── EmbededPageViewController.swift
├── Entity.swift
├── Error.swift
├── FMBlurable.swift
├── FavoritePoster.swift
├── FavoriteTimelineParams.swift
├── FavoriteTimelineTableViewController.swift
├── FollowerTableViewController.swift
├── FollowingTableViewController.swift
├── FriendshipManager.swift
├── FriendshipPoster.swift
├── GeneralSearchViewController.swift
├── GeneralTweetTableViewCell.swift
├── HashTag.swift
├── HomeTimelineParams.swift
├── HomeTimelineTableViewController.swift
├── ImageContainerViewController.swift
├── ImageViewerViewController.swift
├── Info.plist
├── IntegerExtension.swift
├── InteractiveViewController.swift
├── LaunchScreenViewController.swift
├── LoginView.swift
├── LoginViewController.swift
├── MediaSize.swift
├── MediaUploadInitParams.swift
├── Mention.swift
├── MentionTimelineParams.swift
├── MentionTimelineTableViewController.swift
├── MultiUserParams.swift
├── NavigationViewControllerExtension.swift
├── OriginTweetView.swift
├── PageManagerViewController.swift
├── PannableViewController.swift
├── Params.swift
├── ParamsWithBounds.swift
├── ParamsWithCursor.swift
├── Place.swift
├── RESTfulClient.swift
├── RESTfulClientWithID.swift
├── RESTfulClientWithMedia.swift
├── ReplyTableViewController.swift
├── ReplyTimeline.swift
├── ResourceURL.swift
├── RetweetTableViewCell.swift
├── RetweeterID.swift
├── RetweetersIDParams.swift
├── SearchDialogView.swift
├── SearchResultType.swift
├── SearchTimeline.swift
├── SearchTimelineTableViewController.swift
├── SearchTweetParams.swift
├── SearchUsers.swift
├── SimplePostParams.swift
├── SimpleSearchViewController.swift
├── SimpleTweetComposer.swift
├── SimpleTweetPoster.swift
├── SimpleUserParams.swift
├── SingleTweet.swift
├── SingleTweetViewController.swift
├── SingleUser.swift
├── SpamUserParams.swift
├── StringExtension.swift
├── Timeline.swift
├── TimelineParams.swift
├── TimelineTableViewController.swift
├── TweeBox.xcdatamodeld
│ ├── .xccurrentversion
│ └── TweeBox.xcdatamodel
│ │ └── contents
├── Tweet.swift
├── TweetClickableContentProtocol.swift
├── TweetComposer.swift
├── TweetComposerViewController.swift
├── TweetEntity.swift
├── TweetLabel.swift
├── TweetMedia.swift
├── TweetMediaContainerView.swift
├── TweetParams.swift
├── TweetPhoto.swift
├── TweetPhotoSize.swift
├── TweetPostParams.swift
├── TweetPoster.swift
├── TweetSymbol.swift
├── TweetTableViewCell.swift
├── TweetURL.swift
├── TweetVideoInfo.swift
├── TweetWithPicAndTextTableViewCell.swift
├── TweetWithPicAndTextUsingButtonTableViewCell.swift
├── TweetWithPicTableViewCell.swift
├── TweetWithTextTableViewCell.swift
├── TwitterAttributedContent.swift
├── TwitterDate.swift
├── TwitterUser.swift
├── TwitterUserTableViewCell.swift
├── UIViewExtension.swift
├── URLExtension.swift
├── UserList.swift
├── UserListParams.swift
├── UserListRetrieverProtocol.swift
├── UserListTableViewController.swift
├── UserParams.swift
├── UserProfileViewController.swift
├── UserTimelineParams.swift
├── UserTimelineTableViewController.swift
├── UsersSearchParams.swift
└── VideoViewerViewController.swift
├── TweeBoxTests
├── Info.plist
└── TweeBoxTests.swift
└── TweeBoxUITests
├── Info.plist
└── TweeBoxUITests.swift
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 4faramita
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | source 'https://github.com/CocoaPods/Specs.git'
2 | platform :ios, '10.0'
3 | use_frameworks!
4 | # swift_version = "4.0"
5 |
6 | target 'TweeBox' do
7 |
8 | pod 'Reveal-SDK', :configurations => ['Debug']
9 |
10 | pod 'SnapKit', '~> 4.0.0'
11 | pod 'TwitterKit'
12 | pod 'SwiftyJSON'
13 | pod 'Kingfisher', '~> 4.0'
14 | pod 'DateToolsSwift'
15 | pod 'AMScrollingNavbar'
16 | # pod 'Eureka'
17 | pod 'BMPlayer'
18 | pod "MXParallaxHeader"
19 | pod "VisualEffectView"
20 | # pod 'Kanna', '~> 2.1.0'
21 | pod 'PopupDialog', '~> 0.6'
22 | pod 'YYText'
23 | pod "ESPullToRefresh"
24 | pod 'SwipeCellKit'
25 | # pod 'STPopup'
26 | pod 'JDStatusBarNotification'
27 | pod 'R.swift'
28 | pod 'SwiftyAttributes'
29 |
30 | pod 'Gallery'
31 | pod 'Lightbox'
32 |
33 |
34 | # pod 'RealmSwift'
35 | end
36 |
--------------------------------------------------------------------------------
/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - AMScrollingNavbar (4.0.4)
3 | - BMPlayer (1.0.0):
4 | - BMPlayer/Full (= 1.0.0)
5 | - BMPlayer/Core (1.0.0)
6 | - BMPlayer/Full (1.0.0):
7 | - BMPlayer/Core
8 | - NVActivityIndicatorView (~> 4.0.0)
9 | - SnapKit (~> 4.0.0)
10 | - Cache (4.0.4):
11 | - SwiftHash (~> 2.0.0)
12 | - DateToolsSwift (2.0.3)
13 | - ESPullToRefresh (2.7)
14 | - Gallery (2.0.2)
15 | - Hue (3.0.0)
16 | - Imaginary (3.0.0):
17 | - Cache (~> 4.0)
18 | - JDStatusBarNotification (1.5.6)
19 | - Kingfisher (4.2.0)
20 | - Lightbox (2.0.0):
21 | - Hue (~> 3.0)
22 | - Imaginary (~> 3.0)
23 | - MXParallaxHeader (0.6.1)
24 | - NVActivityIndicatorView (4.0.0):
25 | - NVActivityIndicatorView/Presenter (= 4.0.0)
26 | - NVActivityIndicatorView/Presenter (4.0.0)
27 | - PopupDialog (0.6.0)
28 | - R.swift (4.0.0):
29 | - R.swift.Library (~> 4.0.0)
30 | - R.swift.Library (4.0.0)
31 | - Reveal-SDK (11)
32 | - SnapKit (4.0.0)
33 | - SwiftHash (2.0.0)
34 | - SwiftyAttributes (4.1.0)
35 | - SwiftyJSON (3.1.4)
36 | - SwipeCellKit (2.0.0)
37 | - TwitterCore (3.0.2)
38 | - TwitterKit (3.2.1):
39 | - TwitterCore (>= 3.0.2)
40 | - VisualEffectView (3.0.0)
41 | - YYText (1.0.7)
42 |
43 | DEPENDENCIES:
44 | - AMScrollingNavbar
45 | - BMPlayer
46 | - DateToolsSwift
47 | - ESPullToRefresh
48 | - Gallery
49 | - JDStatusBarNotification
50 | - Kingfisher (~> 4.0)
51 | - Lightbox
52 | - MXParallaxHeader
53 | - PopupDialog (~> 0.6)
54 | - R.swift
55 | - Reveal-SDK
56 | - SnapKit (~> 4.0.0)
57 | - SwiftyAttributes
58 | - SwiftyJSON
59 | - SwipeCellKit
60 | - TwitterKit
61 | - VisualEffectView
62 | - YYText
63 |
64 | SPEC CHECKSUMS:
65 | AMScrollingNavbar: 3f35e65f5c97918c31be2179bda9ecb25dc5a3df
66 | BMPlayer: 06717ac5daf25d403329df24667abe2354dd9b17
67 | Cache: 1b3907ee2f5a457e7cfe9ac0be22dc9207f55a09
68 | DateToolsSwift: bf69bf9056d9e53c5c03f06e51f219b9b8b02f9e
69 | ESPullToRefresh: 119e2710b82b75536aab2bbe4883ba119fca35c8
70 | Gallery: a5cdf94495b789c622e8286dea6bf6f29a0b3429
71 | Hue: b8fe1e43eef13631331eebecb2198b68e2622f95
72 | Imaginary: 2765d293d425cbed3b07fa11642554cbaebe913d
73 | JDStatusBarNotification: d5829a8f2f291b23b8a54b87fac8bf2549945a2f
74 | Kingfisher: 9ee7e788d8ba07c3f21ce0d43f33cec310a4f781
75 | Lightbox: 83ec9f4aba4c79fc7bb9bcd263386df9969f09bb
76 | MXParallaxHeader: 858c403532ed933b381556a81ce333d7065ad182
77 | NVActivityIndicatorView: 54c9b93b6f25a4f467512680c0c90c09a1095cf2
78 | PopupDialog: 687af43af0f7b89948cd411ac701865f931bd6dc
79 | R.swift: d6a5ec2f55a8441dc0ed9f1f8b37d7d11ae85c66
80 | R.swift.Library: c3af34921024333546e23b70e70d0b4e0cffca75
81 | Reveal-SDK: 7fa13d04bb55db61ff2342a990cf731d5159361d
82 | SnapKit: a42d492c16e80209130a3379f73596c3454b7694
83 | SwiftHash: d2e09b13495447178cdfb8e46e54a5c46f15f5a9
84 | SwiftyAttributes: cef9463f6e7da1ab393896ca036c9ac37d8a406b
85 | SwiftyJSON: c2842d878f95482ffceec5709abc3d05680c0220
86 | SwipeCellKit: 349c61ffde96ac3ae51c2691a03e421b5b29ee98
87 | TwitterCore: e0420b01e02e7d20e94690e37f43e75de219ab8d
88 | TwitterKit: 205ac89824bbfe9e85a16c420d30bb7bcbedac45
89 | VisualEffectView: d47d295c68bf1b8648aa8e7d6eb4acf6dc18ea12
90 | YYText: 5c461d709e24d55a182d1441c41dc639a18a4849
91 |
92 | PODFILE CHECKSUM: d1c2d244ffe96253e4531923d1be380a59ae323b
93 |
94 | COCOAPODS: 1.3.1
95 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ## TweeBox - A iOS Twitter Reader.
4 |
5 | > WARNING: TweeBox is still in a quite early phase of developing (after all, it's only one-month old now). So the bad news is, it still has many bugs and unfinished features; on the bright side, you will be there to witness its growth :)
6 |
7 | There are a lot of Twitter "clients", but this is not one of those -- TweeBox is a Twitter "reader", designed for those who would like to browse on twitter rather than speak loadly to the Twitter street.
8 |
9 | As a result, TweeBox is friendly to many tweet reading features, including but not limited to "favorite", "block" and "report spam". What's more, you can also easily delete your tweets by just a swipe.
10 |
11 | 
12 |
13 | 
14 |
15 | Also, I'm currently living in a country where I have to use Twitter via proxy, which can be slow. So TweeBox also contains features like "do not show the image until it's fully loaded", so you can continue browsing your timeline while loading.
16 |
17 | 
18 |
19 | TweeBox also comes with a ton of other features and a decent UI design. More pictures:
20 |
21 | 
22 |
23 |
24 | ## Getting Started
25 | TweeBox is currently absent from Apple App Store (just for now). So if you want to try it,please do as follow:
26 |
27 | check out the source in a way you feel suits, for example:
28 |
29 | ```
30 | git clone "https://github.com/4faramita/TweeBox.git"
31 | ```
32 |
33 | Then
34 | ```
35 | cd TweeBox
36 | pod install
37 | ```
38 | Now you can open the `TweeBox.xcworkspace` file in Xcode.
39 |
40 | ## Change Log
41 | 2017-08-31: First release. Happy month-aversary, TweeBox!
42 |
43 | ## Roadmap
44 | ### Basic
45 | - [x] RESTful client
46 | - [x] Twitter API wrapper
47 | - [ ] Error handling
48 |
49 | ### Timeline:
50 | - [x] Basic timeline information display
51 | - [x] Timeline text parsing
52 | - [x] Timeline click action
53 | - [x] Click and wait indicator
54 | - [ ] Image viewer:
55 | - [x] Gestures, alerts
56 | - [x] Single Image viewer
57 | - [x] Share sheet
58 | - [ ] Multiple Image viewer
59 | - [x] GIF / video viewer
60 |
61 | - [x] Relative time
62 | - [ ] Content filter
63 | - [ ] User profile page:
64 | - [x] Basic infomation
65 | - [ ] Text parsing
66 | - [x] Click action (query)
67 | - [ ] Save to database
68 | - [x] Load upward and downward
69 | - [x] View replies
70 | - [x] View context
71 |
72 | #### Special:
73 | - [x] Tweet gesture
74 | - [ ] Waiting bubble
75 |
76 | ### Composer:
77 | - [x] Retweet
78 | - [x] Reply
79 | - [x] Compose new tweet
80 |
81 | ### Settings:
82 |
83 | ### Search:
84 | - [x] General search
85 | - [ ] Database search
86 |
87 | ### DM:
88 |
89 |
90 |
--------------------------------------------------------------------------------
/TweeBox.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/TweeBox.xcodeproj/xcuserdata/4faramita.xcuserdatad/xcschemes/TweeBox.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
44 |
50 |
51 |
52 |
53 |
54 |
60 |
61 |
62 |
63 |
64 |
65 |
76 |
78 |
84 |
85 |
86 |
87 |
88 |
89 |
95 |
97 |
103 |
104 |
105 |
106 |
108 |
109 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/TweeBox.xcodeproj/xcuserdata/4faramita.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | TweeBox.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | B429859B1F29E8E600AFFB97
16 |
17 | primary
18 |
19 |
20 | B42985B21F29E8E700AFFB97
21 |
22 | primary
23 |
24 |
25 | B42985BD1F29E8E700AFFB97
26 |
27 | primary
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/TweeBox.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/TweeBox.xcworkspace/xcuserdata/4faramita.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
8 |
14 |
15 |
23 |
24 |
32 |
33 |
41 |
42 |
43 |
44 |
45 |
47 |
53 |
54 |
62 |
63 |
64 |
65 |
66 |
68 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/TweeBox/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/7/27.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import CoreData
11 | import TwitterKit
12 |
13 |
14 | @UIApplicationMain
15 | class AppDelegate: UIResponder, UIApplicationDelegate {
16 |
17 | var window: UIWindow?
18 |
19 |
20 | // Twitter Kit
21 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
22 | Twitter.sharedInstance().start(withConsumerKey:"KEY", consumerSecret:"SECRET")
23 |
24 | return true
25 | }
26 |
27 | func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
28 | return Twitter.sharedInstance().application(app, open: url, options: options)
29 | }
30 |
31 |
32 | func applicationWillResignActive(_ application: UIApplication) {
33 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
34 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
35 | }
36 |
37 | func applicationDidEnterBackground(_ application: UIApplication) {
38 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
39 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
40 | }
41 |
42 | func applicationWillEnterForeground(_ application: UIApplication) {
43 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
44 | }
45 |
46 | func applicationDidBecomeActive(_ application: UIApplication) {
47 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
48 | }
49 |
50 | func applicationWillTerminate(_ application: UIApplication) {
51 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
52 | // Saves changes in the application's managed object context before the application terminates.
53 | self.saveContext()
54 | }
55 |
56 | // MARK: - Core Data stack
57 |
58 | lazy var persistentContainer: NSPersistentContainer = {
59 | /*
60 | The persistent container for the application. This implementation
61 | creates and returns a container, having loaded the store for the
62 | application to it. This property is optional since there are legitimate
63 | error conditions that could cause the creation of the store to fail.
64 | */
65 | let container = NSPersistentContainer(name: "TweeBox")
66 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in
67 | if let error = error as NSError? {
68 | // Replace this implementation with code to handle the error appropriately.
69 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
70 |
71 | /*
72 | Typical reasons for an error here include:
73 | * The parent directory does not exist, cannot be created, or disallows writing.
74 | * The persistent store is not accessible, due to permissions or data protection when the device is locked.
75 | * The device is out of space.
76 | * The store could not be migrated to the current model version.
77 | Check the error message to determine what the actual problem was.
78 | */
79 | fatalError("Unresolved error \(error), \(error.userInfo)")
80 | }
81 | })
82 | return container
83 | }()
84 |
85 | // MARK: - Core Data Saving support
86 |
87 | func saveContext () {
88 | let context = persistentContainer.viewContext
89 | if context.hasChanges {
90 | do {
91 | try context.save()
92 | } catch {
93 | // Replace this implementation with code to handle the error appropriately.
94 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
95 | let nserror = error as NSError
96 | fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
97 | }
98 | }
99 | }
100 |
101 | // MARK: - Core Data Convenience
102 |
103 | static var persistentContainer: NSPersistentContainer {
104 | return (UIApplication.shared.delegate as! AppDelegate).persistentContainer
105 | }
106 |
107 | static var viewContext: NSManagedObjectContext {
108 | return persistentContainer.viewContext
109 | }
110 | }
111 |
112 |
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "60x60",
35 | "idiom" : "iphone",
36 | "filename" : "icon@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "60x60",
41 | "idiom" : "iphone",
42 | "filename" : "icon@3x.png",
43 | "scale" : "3x"
44 | }
45 | ],
46 | "info" : {
47 | "version" : 1,
48 | "author" : "xcode"
49 | }
50 | }
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/AppIcon.appiconset/icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/AppIcon.appiconset/icon@2x.png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/AppIcon.appiconset/icon@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/AppIcon.appiconset/icon@3x.png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/delete.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "cross.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "cross (1).png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "cross (2).png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/delete.imageset/cross (1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/delete.imageset/cross (1).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/delete.imageset/cross (2).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/delete.imageset/cross (2).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/delete.imageset/cross.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/delete.imageset/cross.png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/hashtag.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "hashtag.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "hashtag (1).png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "hashtag (2).png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/hashtag.imageset/hashtag (1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/hashtag.imageset/hashtag (1).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/hashtag.imageset/hashtag (2).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/hashtag.imageset/hashtag (2).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/hashtag.imageset/hashtag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/hashtag.imageset/hashtag.png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/launch_screen.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "launch_screen@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "launch_screen@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/launch_screen.imageset/launch_screen@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/launch_screen.imageset/launch_screen@2x.png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/launch_screen.imageset/launch_screen@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/launch_screen.imageset/launch_screen@3x.png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/like_false.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "like (1).png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "like (2).png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "like (3).png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/like_false.imageset/like (1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/like_false.imageset/like (1).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/like_false.imageset/like (2).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/like_false.imageset/like (2).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/like_false.imageset/like (3).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/like_false.imageset/like (3).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/like_true.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "like (4).png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "like (5).png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "like (6).png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/like_true.imageset/like (4).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/like_true.imageset/like (4).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/like_true.imageset/like (5).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/like_true.imageset/like (5).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/like_true.imageset/like (6).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/like_true.imageset/like (6).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/mention.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "at.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "at-2.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "at-3.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/mention.imageset/at-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/mention.imageset/at-2.png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/mention.imageset/at-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/mention.imageset/at-3.png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/mention.imageset/at.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/mention.imageset/at.png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/mention_thin.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "arroba.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "arroba (1).png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "arroba (2).png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/mention_thin.imageset/arroba (1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/mention_thin.imageset/arroba (1).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/mention_thin.imageset/arroba (2).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/mention_thin.imageset/arroba (2).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/mention_thin.imageset/arroba.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/mention_thin.imageset/arroba.png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/picPlaceholder.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "placeholder.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/picPlaceholder.imageset/placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/picPlaceholder.imageset/placeholder.png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/picture.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "picture (1).png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "picture (2).png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "picture (3).png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/picture.imageset/picture (1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/picture.imageset/picture (1).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/picture.imageset/picture (2).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/picture.imageset/picture (2).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/picture.imageset/picture (3).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/picture.imageset/picture (3).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/play_gif.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "gif-images-file-type-interface-symbol-of-stroke.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "gif-images-file-type-interface-symbol-of-stroke (1).png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "gif-images-file-type-interface-symbol-of-stroke (2).png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/play_gif.imageset/gif-images-file-type-interface-symbol-of-stroke (1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/play_gif.imageset/gif-images-file-type-interface-symbol-of-stroke (1).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/play_gif.imageset/gif-images-file-type-interface-symbol-of-stroke (2).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/play_gif.imageset/gif-images-file-type-interface-symbol-of-stroke (2).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/play_gif.imageset/gif-images-file-type-interface-symbol-of-stroke.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/play_gif.imageset/gif-images-file-type-interface-symbol-of-stroke.png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/play_video.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "play-button.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "play-button (1).png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "play-button (2).png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/play_video.imageset/play-button (1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/play_video.imageset/play-button (1).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/play_video.imageset/play-button (2).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/play_video.imageset/play-button (2).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/play_video.imageset/play-button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/play_video.imageset/play-button.png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/profile.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "avatar (2).png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "avatar (1).png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "avatar.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/profile.imageset/avatar (1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/profile.imageset/avatar (1).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/profile.imageset/avatar (2).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/profile.imageset/avatar (2).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/profile.imageset/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/profile.imageset/avatar.png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/profile_selected.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "man-user (2).png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "man-user (1).png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "man-user.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/profile_selected.imageset/man-user (1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/profile_selected.imageset/man-user (1).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/profile_selected.imageset/man-user (2).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/profile_selected.imageset/man-user (2).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/profile_selected.imageset/man-user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/profile_selected.imageset/man-user.png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/reply.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "reply (1).png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "reply (2).png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "reply (3).png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/reply.imageset/reply (1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/reply.imageset/reply (1).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/reply.imageset/reply (2).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/reply.imageset/reply (2).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/reply.imageset/reply (3).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/reply.imageset/reply (3).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/reply_false.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "reply (4).png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "reply (5).png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "reply (6).png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/reply_false.imageset/reply (4).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/reply_false.imageset/reply (4).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/reply_false.imageset/reply (5).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/reply_false.imageset/reply (5).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/reply_false.imageset/reply (6).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/reply_false.imageset/reply (6).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/reply_true.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "reply (7).png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "reply (8).png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "reply (9).png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/reply_true.imageset/reply (7).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/reply_true.imageset/reply (7).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/reply_true.imageset/reply (8).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/reply_true.imageset/reply (8).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/reply_true.imageset/reply (9).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/reply_true.imageset/reply (9).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/retweet_false.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "retweet-arrows-couple-outline.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "retweet-arrows-couple-outline (1).png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "retweet-arrows-couple-outline (2).png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/retweet_false.imageset/retweet-arrows-couple-outline (1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/retweet_false.imageset/retweet-arrows-couple-outline (1).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/retweet_false.imageset/retweet-arrows-couple-outline (2).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/retweet_false.imageset/retweet-arrows-couple-outline (2).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/retweet_false.imageset/retweet-arrows-couple-outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/retweet_false.imageset/retweet-arrows-couple-outline.png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/retweet_true.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "retweet-symbol.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "retweet-symbol (1).png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "retweet-symbol (2).png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/retweet_true.imageset/retweet-symbol (1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/retweet_true.imageset/retweet-symbol (1).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/retweet_true.imageset/retweet-symbol (2).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/retweet_true.imageset/retweet-symbol (2).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/retweet_true.imageset/retweet-symbol.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/retweet_true.imageset/retweet-symbol.png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/twitter.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "twitter (8).png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "twitter (7).png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "twitter (6).png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/twitter.imageset/twitter (6).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/twitter.imageset/twitter (6).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/twitter.imageset/twitter (7).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/twitter.imageset/twitter (7).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/twitter.imageset/twitter (8).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/twitter.imageset/twitter (8).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/twitter_selected.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "twitter (2).png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "twitter (1).png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "twitter.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/twitter_selected.imageset/twitter (1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/twitter_selected.imageset/twitter (1).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/twitter_selected.imageset/twitter (2).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/twitter_selected.imageset/twitter (2).png
--------------------------------------------------------------------------------
/TweeBox/Assets.xcassets/twitter_selected.imageset/twitter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4faramita/TweeBox/ac5539e5eb7f5fad1dfa3737f08cadf04c729189/TweeBox/Assets.xcassets/twitter_selected.imageset/twitter.png
--------------------------------------------------------------------------------
/TweeBox/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/TweeBox/Constants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Constants.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/7/28.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import TwitterKit
12 |
13 | struct Constants {
14 |
15 | static var selfID: String {
16 | return Twitter.sharedInstance().sessionStore.session()?.userID ?? "-1"
17 | }
18 |
19 | static let tweetLimitPerRefresh = "100"
20 | static let userLimitPerRefresh = "100"
21 |
22 | static let picQuality = MediaSize.large
23 |
24 | static let aspectRatioWidth: CGFloat = 16
25 | static let aspectRatioHeight: CGFloat = 9
26 |
27 | static let normalAspectRatio = aspectRatioHeight / aspectRatioWidth
28 | static let thinAspectRatio = normalAspectRatio * 2
29 |
30 | static let picCornerRadius: CGFloat = 3
31 |
32 | static let defaultProfileRadius = ProfileRadius.round.rawValue
33 |
34 | static let picFadeInDuration = 0.2
35 |
36 | static let naturalReading = true
37 |
38 | static let profileImageRadius: CGFloat = 50
39 | static let profilePanelDragOffset: CGFloat = 100
40 | static let profileToolbarHeight: CGFloat = 50
41 | static let contentUnifiedOffset: CGFloat = 20
42 |
43 | static let themeColor = UIColor.orange // (red: 0, green: 0, blue: 1, alpha: 1)
44 | static let lightenThemeColor = UIColor.gray // (red: 0, green: 0, blue: 1, alpha: 0.5)
45 | }
46 |
47 | enum ProfileRadius: CGFloat {
48 | case round = 200
49 | case square = 20
50 | }
51 |
--------------------------------------------------------------------------------
/TweeBox/Coordinates.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Coordinates.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/5.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | // The inner coordinates array is formatted as geoJSON (longitude first, then latitude)
10 |
11 | import Foundation
12 | import SwiftyJSON
13 |
14 | struct Coordinates {
15 |
16 | public var type: String
17 |
18 | public var coordinates: [Any]
19 |
20 | init(with json: JSON) {
21 |
22 | type = json["type"].string ?? "Point"
23 |
24 | switch type {
25 | case "Point":
26 | coordinates = json["coordinates"].arrayValue.map({ $0.floatValue })
27 | case "Polygon":
28 | coordinates = [json["coordinates"][0].arrayValue.map({ [$0[0].floatValue, $0[1].floatValue] })]
29 | default:
30 | coordinates = [0.0, 0.0]
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/TweeBox/DataExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DataExtension.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/31.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Data {
12 | var MIMEType: String {
13 | var values = [UInt8](repeating:0, count:1)
14 | self.copyBytes(to: &values, count: 1)
15 |
16 | let ext: String
17 | switch (values[0]) {
18 | case 0xFF:
19 | ext = "image/jpeg"
20 | case 0x89:
21 | ext = "image/png"
22 | case 0x47:
23 | ext = "image/gif"
24 | case 0x49, 0x4D :
25 | ext = "image/tiff"
26 | default:
27 | ext = "image/png"
28 | }
29 | return ext
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/TweeBox/EmbededPageViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EmbededPageViewController.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/13.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class EmbededPageViewController: UIPageViewController {
12 |
13 | public var currentIndex: Int!
14 |
15 | public var imageViewers: [ImageViewerViewController]!
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 |
20 | dataSource = self
21 | }
22 |
23 | func viewImageViewerViewController(_ index: Int) -> ImageViewerViewController? {
24 | guard storyboard != nil
25 | else {
26 | return nil
27 | }
28 |
29 | return imageViewers[index]
30 | }
31 | }
32 |
33 |
34 | extension EmbededPageViewController: UIPageViewControllerDataSource {
35 |
36 | func pageViewController(_ pageViewController: UIPageViewController,
37 | viewControllerBefore viewController: UIViewController) -> UIViewController? {
38 |
39 | if let viewController = viewController as? ImageViewerViewController,
40 | let index = viewController.photoIndex,
41 | index > 0 {
42 | return viewImageViewerViewController(index - 1)
43 | }
44 |
45 | return nil
46 | }
47 |
48 | func pageViewController(_ pageViewController: UIPageViewController,
49 | viewControllerAfter viewController: UIViewController) -> UIViewController? {
50 |
51 | if let viewController = viewController as? ImageViewerViewController,
52 | let index = viewController.photoIndex,
53 | (index + 1) < imageViewers.count {
54 | return viewImageViewerViewController(index + 1)
55 | }
56 |
57 | return nil
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/TweeBox/Entity.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Entity.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/5.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | struct Entity {
13 |
14 | typealias TweetSymbol = Hashtag
15 |
16 | public var hashtags: [Hashtag]
17 | public var urls: [TweetURL]
18 | public var userMentions: [Mention]
19 | public var symbols: [TweetSymbol]
20 |
21 | public var media: [TweetMedia]?
22 | public var realMedia: [TweetMedia]?
23 | public var mediaToShare: [TweetMedia]?
24 | public var thumbMedia: [TweetMedia]?
25 |
26 |
27 | init(with json: JSON, and extendedJson: JSON) {
28 |
29 | hashtags = json["hashtags"].arrayValue.map { Hashtag(with: $0) }
30 | urls = json["urls"].arrayValue.map { TweetURL(with: $0) }
31 | userMentions = json["user_mentions"].arrayValue.map { Mention(with: $0) }
32 | symbols = json["symbols"].arrayValue.map { TweetSymbol(with: $0) }
33 |
34 | if extendedJson.null == nil {
35 | // there exists extended_json
36 | media = extendedJson["media"].arrayValue.map { TweetMedia(with: $0, quality: MediaSize.small) }
37 | realMedia = extendedJson["media"].arrayValue.map { TweetMedia(with: $0, quality: Constants.picQuality) }
38 | mediaToShare = extendedJson["media"].arrayValue.map { TweetMedia(with: $0, quality: .nonNormal) }
39 | thumbMedia = extendedJson["media"].arrayValue.map { TweetMedia(with: $0, quality: MediaSize.thumb) }
40 | // media in extended_entities
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/TweeBox/Error.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Errors.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/7/28.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum Error: Int {
12 | case netWorkError
13 | case unauthorized
14 | }
15 |
--------------------------------------------------------------------------------
/TweeBox/FMBlurable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FMBlurable.swift
3 | // FMBlurable
4 | //
5 | // Created by SIMON_NON_ADMIN on 18/09/2015.
6 | // Copyright © 2015 Simon Gladman. All rights reserved.
7 | //
8 | // Thanks to romainmenke (https://twitter.com/romainmenke) for hint on a larger sample...
9 |
10 | import UIKit
11 |
12 |
13 | protocol Blurable
14 | {
15 | var layer: CALayer { get }
16 | var subviews: [UIView] { get }
17 | var frame: CGRect { get }
18 | var superview: UIView? { get }
19 |
20 | func addSubview(_: UIView)
21 | func removeFromSuperview()
22 |
23 | func blur(blurRadius: CGFloat)
24 | func unBlur()
25 |
26 | var isBlurred: Bool { get }
27 | }
28 |
29 | extension Blurable
30 | {
31 | func blur(blurRadius: CGFloat)
32 | {
33 | if self.superview == nil
34 | {
35 | return
36 | }
37 |
38 | UIGraphicsBeginImageContextWithOptions(CGSize(width: frame.width, height: frame.height), false, 1)
39 |
40 | layer.render(in: UIGraphicsGetCurrentContext()!)
41 |
42 | let image = UIGraphicsGetImageFromCurrentImageContext()
43 |
44 | UIGraphicsEndImageContext();
45 |
46 | guard let blur = CIFilter(name: "CIGaussianBlur"),
47 | let this = self as? UIView else
48 | {
49 | return
50 | }
51 |
52 | blur.setValue(CIImage(image: image!), forKey: kCIInputImageKey)
53 | // UNWRAP
54 | blur.setValue(blurRadius, forKey: kCIInputRadiusKey)
55 |
56 | let ciContext = CIContext(options: nil)
57 |
58 | let result = blur.value(forKey: kCIOutputImageKey) as! CIImage!
59 |
60 | let boundingRect = CGRect(x:0,
61 | y: 0,
62 | width: frame.width,
63 | height: frame.height)
64 |
65 | let cgImage = ciContext.createCGImage(result!, from: boundingRect)
66 | // UNWRAP
67 |
68 | let filteredImage = UIImage(cgImage: cgImage!)
69 | // UNWRAP
70 |
71 | let blurOverlay = BlurOverlay()
72 | blurOverlay.frame = boundingRect
73 |
74 | blurOverlay.image = filteredImage
75 | blurOverlay.contentMode = UIViewContentMode.left
76 |
77 | if let superview = superview as? UIStackView,
78 | let index = (superview as UIStackView).arrangedSubviews.index(of: this)
79 | {
80 | removeFromSuperview()
81 | superview.insertArrangedSubview(blurOverlay, at: index)
82 | }
83 | else
84 | {
85 | blurOverlay.frame.origin = frame.origin
86 |
87 | UIView.transition(from: this,
88 | to: blurOverlay,
89 | duration: 0.2,
90 | options: UIViewAnimationOptions.curveEaseIn,
91 | completion: nil)
92 | }
93 |
94 | objc_setAssociatedObject(this,
95 | &BlurableKey.blurable,
96 | blurOverlay,
97 | objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
98 | }
99 |
100 | func unBlur()
101 | {
102 | guard let this = self as? UIView,
103 | let blurOverlay = objc_getAssociatedObject(self as? UIView, &BlurableKey.blurable) as? BlurOverlay else
104 | {
105 | return
106 | }
107 |
108 | if let superview = blurOverlay.superview as? UIStackView,
109 | let index = (blurOverlay.superview as! UIStackView).arrangedSubviews.index(of: blurOverlay)
110 | {
111 | blurOverlay.removeFromSuperview()
112 | superview.insertArrangedSubview(this, at: index)
113 | }
114 | else
115 | {
116 | this.frame.origin = blurOverlay.frame.origin
117 |
118 | UIView.transition(from: blurOverlay,
119 | to: this,
120 | duration: 0.2,
121 | options: UIViewAnimationOptions.curveEaseIn,
122 | completion: nil)
123 | }
124 |
125 | objc_setAssociatedObject(this,
126 | &BlurableKey.blurable,
127 | nil,
128 | objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
129 | }
130 |
131 | var isBlurred: Bool
132 | {
133 | return objc_getAssociatedObject(self as? UIView, &BlurableKey.blurable) is BlurOverlay
134 | }
135 | }
136 |
137 | extension UIView: Blurable
138 | {
139 | }
140 |
141 | class BlurOverlay: UIImageView
142 | {
143 | }
144 |
145 | struct BlurableKey
146 | {
147 | static var blurable = "blurable"
148 | }
149 |
--------------------------------------------------------------------------------
/TweeBox/FavoritePoster.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FavoritePoster.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/29.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | class FavoritePoster: SimpleTweetPoster {
13 |
14 | override func postData(_ handler: @escaping (Tweet?) -> Void) {
15 |
16 | if Constants.selfID != "-1" {
17 | let client = RESTfulClient(resource: resourceURL, params: params.getParams())
18 |
19 | print(">>> FavoritePoster >> \(params.getParams())")
20 |
21 | client.getData() { data in
22 | if let data = data {
23 | let json = JSON(data: data)
24 | if json.null == nil {
25 | let tweet = Tweet(with: json)
26 | handler(tweet)
27 | } else {
28 | handler(nil)
29 | }
30 | } else {
31 | handler(nil)
32 | }
33 | }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/TweeBox/FavoriteTimelineParams.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FavoriteTimelineParams.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/31.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class FavoriteTimelineParams: UserTimelineParams {
12 |
13 | override init(of userID: String, sinceID: String? = nil, maxID: String? = nil, excludeReplies: Bool = false, includeRetweets: Bool = true) {
14 |
15 | super.init(of: userID, sinceID: sinceID, maxID: maxID, excludeReplies: excludeReplies, includeRetweets: includeRetweets)
16 | resourceURL = ResourceURL.favorites_list
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/TweeBox/FavoriteTimelineTableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FavoriteTimelineTableViewController.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/31.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class FavoriteTimelineTableViewController: TimelineTableViewController {
12 |
13 | var userID: String {
14 | return Constants.selfID
15 | }
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 |
20 | self.navigationItem.leftBarButtonItem = nil
21 | }
22 |
23 | override func refreshTimeline(handler: (() -> Void)?) {
24 |
25 | let params = FavoriteTimelineParams(of: userID)
26 |
27 | let timeline = Timeline(
28 | maxID: maxID,
29 | sinceID: sinceID,
30 | fetchNewer: fetchNewer,
31 | resourceURL: params.resourceURL,
32 | params: params
33 | )
34 |
35 | timeline.fetchData { [weak self] (maxID, sinceID, tweets) in
36 |
37 | if (self?.maxID == nil) && (self?.sinceID == nil) {
38 | if let sinceID = sinceID {
39 | self?.sinceID = sinceID
40 | }
41 | if let maxID = maxID {
42 | self?.maxID = maxID
43 | }
44 | } else {
45 | if (self?.fetchNewer)! {
46 | if let sinceID = sinceID {
47 | self?.sinceID = sinceID
48 | }
49 | } else {
50 | if let maxID = maxID {
51 | self?.maxID = maxID
52 | }
53 | }
54 |
55 | }
56 |
57 | if tweets.count > 0 {
58 |
59 | self?.insertNewTweets(with: tweets)
60 |
61 | let cells = self?.tableView.visibleCells
62 | if cells != nil {
63 | for cell in cells! {
64 | let indexPath = self?.tableView.indexPath(for: cell)
65 | if let tweetCell = cell as? GeneralTweetTableViewCell {
66 | tweetCell.section = indexPath?.section
67 | }
68 | }
69 |
70 | }
71 |
72 | }
73 |
74 | if let handler = handler {
75 | handler()
76 | }
77 | }
78 |
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/TweeBox/FollowerTableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FollowerTableViewController.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/15.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class FollowerTableViewController: UserListTableViewController {
12 |
13 | override func viewDidLoad() {
14 | super.viewDidLoad()
15 |
16 | resourceURL = ResourceURL.followers_list
17 | }
18 |
19 | init(followerListParams: UserListParams) {
20 | self.userListParams = followerListParams
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/TweeBox/FollowingTableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FollowingTableViewController.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/15.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class FollowingTableViewController: UserListTableViewController {
12 |
13 | override func viewDidLoad() {
14 | super.viewDidLoad()
15 |
16 | resourceURL = ResourceURL.followings_list
17 | }
18 |
19 | init(followingListParams: UserListParams) {
20 | self.userListParams = followingListParams
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/TweeBox/FriendshipManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FriendshipManager.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/29.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct FriendshipManager {
12 |
13 | var userID: String
14 |
15 | func follow(handler: @escaping (Bool, TwitterUser?) -> Void) {
16 |
17 | let params = SimpleUserParams(userID: userID, screenName: nil)
18 |
19 | let dataRetriever = FriendshipPoster(userParams: params, resourceURL: ResourceURL.friendships_create)
20 |
21 | dataRetriever.postData { (user) in
22 | if self.userID == user?.id {
23 | handler(true, user)
24 | } else {
25 | handler(false, user)
26 | }
27 | }
28 | }
29 |
30 |
31 | func unfollow(handler: @escaping (Bool, TwitterUser?) -> Void) {
32 |
33 | let params = SimpleUserParams(userID: userID, screenName: nil)
34 |
35 | let dataRetriever = FriendshipPoster(userParams: params, resourceURL: ResourceURL.friendships_destroy)
36 |
37 | dataRetriever.postData { (user) in
38 | if self.userID == user?.id {
39 | handler(true, user)
40 | } else {
41 | handler(false, user)
42 | }
43 | }
44 | }
45 |
46 | func block(handler: @escaping (Bool, TwitterUser?) -> Void) {
47 |
48 | let params = SimpleUserParams(userID: userID, screenName: nil)
49 |
50 | let dataRetriever = FriendshipPoster(userParams: params, resourceURL: ResourceURL.blocks_create)
51 |
52 | dataRetriever.postData { (user) in
53 | if self.userID == user?.id {
54 | handler(true, user)
55 | } else {
56 | handler(false, user)
57 | }
58 | }
59 | }
60 |
61 |
62 | func unblock(handler: @escaping (Bool, TwitterUser?) -> Void) {
63 |
64 | let params = SimpleUserParams(userID: userID, screenName: nil)
65 |
66 | let dataRetriever = FriendshipPoster(userParams: params, resourceURL: ResourceURL.blocks_destroy)
67 |
68 | dataRetriever.postData { (user) in
69 | if self.userID == user?.id {
70 | handler(true, user)
71 | } else {
72 | handler(false, user)
73 | }
74 | }
75 | }
76 |
77 |
78 | func reportSpam(block: Bool?, handler: @escaping (Bool, TwitterUser?) -> Void) {
79 |
80 | let params = SpamUserParams(userID: userID, screenName: nil, performBlock: block)
81 |
82 | let dataRetriever = FriendshipPoster(userParams: params, resourceURL: ResourceURL.users_report_spam)
83 |
84 | dataRetriever.postData { (user) in
85 | if self.userID == user?.id {
86 | handler(true, user)
87 | } else {
88 | handler(false, user)
89 | }
90 | }
91 | }
92 |
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/TweeBox/FriendshipPoster.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FriendshipPoster.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/29.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | class FriendshipPoster {
13 |
14 | public var resourceURL: (String, String)
15 | public var userParams: SimpleUserParams
16 |
17 | init(userParams: SimpleUserParams, resourceURL: (String, String)) {
18 | self.resourceURL = resourceURL
19 | self.userParams = userParams
20 | }
21 |
22 |
23 | func postData(_ handler: @escaping (TwitterUser?) -> Void) {
24 |
25 | if Constants.selfID != "-1" {
26 |
27 | let client = RESTfulClient(resource: resourceURL, params: userParams.getParams())
28 |
29 | print(">>> FavoritePoster >> \(userParams.getParams())")
30 |
31 | client.getData() { data in
32 | if let data = data {
33 | let json = JSON(data: data)
34 | if json.null == nil {
35 | let user = TwitterUser(with: json)
36 | handler(user)
37 | } else {
38 | handler(nil)
39 | }
40 | } else {
41 | handler(nil)
42 | }
43 | }
44 | }
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/TweeBox/GeneralSearchViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GeneralSearchViewController.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/24.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SnapKit
11 | import PopupDialog
12 |
13 | class GeneralSearchViewController: UIViewController {
14 |
15 | @IBOutlet weak var dialogView: SearchDialogView!
16 |
17 | fileprivate var keyword: String? {
18 | return dialogView.keyword
19 | }
20 |
21 | fileprivate var fetchedUser: TwitterUser?
22 |
23 |
24 | @IBAction func findUser(_ sender: UIButton) {
25 |
26 | fetchUser() { [weak self] (user) in
27 |
28 | if let user = user {
29 | self?.fetchedUser = user
30 | self?.performSegue(withIdentifier: "Show User", sender: self)
31 | } else {
32 | self?.alertForNoSuchUser()
33 | }
34 | }
35 |
36 | }
37 |
38 |
39 | override func viewDidLoad() {
40 | super.viewDidLoad()
41 |
42 | setTextField()
43 | dialogView.initSearchButtons()
44 | }
45 |
46 | private func setTextField() {
47 |
48 | dialogView.inputTextField.delegate = self
49 |
50 | dialogView.snp.makeConstraints { (make) in
51 | make.height.equalTo(350)
52 | }
53 |
54 | dialogView.inputTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
55 | }
56 |
57 | @objc private func textFieldDidChange(_ textField: UITextField) {
58 |
59 | Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { [weak self] (timer) in
60 | self?.dialogView.keyword = textField.text
61 | }
62 | }
63 | }
64 |
65 | // Segue related
66 | extension GeneralSearchViewController {
67 |
68 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
69 | if let keyword = keyword, let identifier = segue.identifier {
70 | switch identifier {
71 | case "Show Tweets":
72 | if let searchTimelineViewController = segue.destination.content as? SearchTimelineTableViewController {
73 | searchTimelineViewController.query = keyword
74 | searchTimelineViewController.navigationItem.title = "\"\(keyword)\""
75 | }
76 |
77 | case "Show Tweets with Hashtag":
78 | if let searchTimelineViewController = segue.destination.content as? SearchTimelineTableViewController {
79 | // if let keyword = keyword {
80 | searchTimelineViewController.query = "%23\(keyword)"
81 | searchTimelineViewController.navigationItem.title = "#\(keyword)"
82 | // }
83 | }
84 |
85 | case "Show User":
86 | if let profileViewController = segue.destination.content as? UserTimelineTableViewController {
87 | if let user = fetchedUser {
88 | profileViewController.user = user
89 | }
90 | }
91 |
92 | case "Show Users":
93 | if let userListTableViewController = segue.destination.content as? UserListTableViewController {
94 |
95 | let simpleUserListRetriever = SearchUsers(
96 | params: UsersSearchParams(query: keyword),
97 | resourceURL: ResourceURL.users_search
98 | )
99 |
100 | userListTableViewController.userListRetriever = simpleUserListRetriever
101 | userListTableViewController.navigationItem.title = "\"\(keyword)\""
102 | }
103 |
104 | default:
105 | return
106 | }
107 | }
108 | }
109 |
110 |
111 |
112 | fileprivate func alertForNoSuchUser() {
113 |
114 | print(">>> no user")
115 |
116 | let popup = PopupDialog(title: "Cannot Find User @\(keyword ?? "")", message: "Please check your input.", image: nil)
117 | let cancelButton = CancelButton(title: "OK") { }
118 | popup.addButton(cancelButton)
119 | present(popup, animated: true, completion: nil)
120 |
121 | }
122 |
123 |
124 | fileprivate func fetchUser(_ handler: @escaping (TwitterUser?) -> Void) {
125 |
126 | SingleUser(
127 | params: UserParams(userID: nil, screenName: keyword),
128 | resourceURL: ResourceURL.user_show
129 | ).fetchData { (singleUser) in
130 | handler(singleUser)
131 | }
132 | }
133 | }
134 |
135 |
136 | extension GeneralSearchViewController: UITextFieldDelegate {
137 |
138 | func textFieldShouldReturn(_ textField: UITextField) -> Bool {
139 | textField.resignFirstResponder()
140 | return true
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/TweeBox/HashTag.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HashTag.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/27.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | class Hashtag: TweetEntity {
13 |
14 | var text: String
15 |
16 | override init(with json: JSON) {
17 |
18 | text = json["text"].stringValue
19 |
20 | super.init(with: json)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/TweeBox/HomeTimelineParams.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HomeTimelineParams.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/9.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class HomeTimelineParams: TimelineParams {
12 |
13 | override init(sinceID: String? = nil, maxID: String? = nil, excludeReplies: Bool? = false, includeRetweets: Bool? = true) {
14 |
15 | super.init(excludeReplies: nil, includeRetweets: nil)
16 | resourceURL = ResourceURL.statuses_home_timeline
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/TweeBox/HomeTimelineTableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HomeTimelineTableViewController.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/14.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class HomeTimelineTableViewController: UserTimelineTableViewController {
12 |
13 | override func viewDidLoad() {
14 | super.viewDidLoad()
15 |
16 | userID = Constants.selfID
17 | // let's user database here
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/TweeBox/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSCameraUsageDescription
6 | It requires access to camera to upload media directly from camera.
7 | CFBundleDevelopmentRegion
8 | en
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | CFBundleURLTypes
40 |
41 |
42 | CFBundleURLSchemes
43 |
44 | twitterkit-yuo33GhvlWXA465CE1wrIhvss
45 |
46 |
47 |
48 | NSPhotoLibraryUsageDescription
49 | Need the access to your photo library to save to or upload from it.
50 | LSApplicationQueriesSchemes
51 |
52 | twitter
53 | twitterauth
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/TweeBox/IntegerExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IntegerExtension.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/21.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Int {
12 |
13 | var shortExpression: String {
14 |
15 | let shortenExpression: String
16 |
17 | if self > 10000 {
18 | shortenExpression = "\(Int(Float(self) / 1000))k"
19 | } else {
20 | shortenExpression = "\(self)"
21 | }
22 |
23 | return shortenExpression
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/TweeBox/LaunchScreenViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LaunchScreenViewController.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/31.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class LaunchScreenViewController: UIViewController {
12 |
13 | @IBOutlet weak var imageView: UIImageView!
14 |
15 | override func viewDidLoad() {
16 | super.viewDidLoad()
17 |
18 | imageView.image = UIImage(named: "launch_screen")
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/TweeBox/LoginView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginView.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/7/27.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class LoginView: UIView {
12 |
13 | /*
14 | // Only override draw() if you perform custom drawing.
15 | // An empty implementation adversely affects performance during animation.
16 | override func draw(_ rect: CGRect) {
17 | // Drawing code
18 | }
19 | */
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/TweeBox/LoginViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginViewController.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/7/27.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SafariServices
11 | import TwitterKit
12 |
13 | class LoginViewController: UIViewController, SFSafariViewControllerDelegate {
14 |
15 | override func viewDidAppear(_ animated: Bool) {
16 | super.viewWillAppear(animated)
17 | addLoginButton()
18 |
19 | if Twitter.sharedInstance().sessionStore.session()?.userID != nil {
20 | performSegue(withIdentifier: "login", sender: self)
21 | }
22 | }
23 |
24 | private func addLoginButton() {
25 | let logInButton = TWTRLogInButton(logInCompletion: { [weak self] session, error in
26 | if session != nil {
27 | print("signed in as \(session!.userName)")
28 | self?.performSegue(withIdentifier: "login", sender: nil)
29 | } else if error != nil {
30 | print("error: \(error!.localizedDescription)")
31 | }
32 | })
33 |
34 | logInButton.center = self.view.center
35 | self.view.addSubview(logInButton)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/TweeBox/MediaSize.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MediaSize.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/8.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum MediaSize: String {
12 | case thumb = ":thumb"
13 | case small = ":small"
14 | case medium = ":medium"
15 | case large = ":large"
16 |
17 | case nonNormal = ""
18 | case bigger = "_bigger"
19 | case max = "_400x400"
20 | }
21 |
--------------------------------------------------------------------------------
/TweeBox/MediaUploadInitParams.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MediaUploadInitParams.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/31.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | class MediaUploadInitParams: ParamsProtocol {
13 |
14 | private let command = "INIT"
15 |
16 | var size: String // total_bytes
17 |
18 | var type: String // media_type
19 |
20 | var category: String // media_category
21 |
22 | // additional_owners
23 |
24 |
25 | init(size: String, type: String, category: String) {
26 |
27 | self.size = size
28 | self.type = type
29 | self.category = category
30 | }
31 |
32 |
33 | func getParams() -> [String : Any] {
34 |
35 | var params = [String: String]()
36 |
37 | params["command"] = command
38 |
39 | params["total_bytes"] = size
40 |
41 | params["media_type"] = type
42 |
43 | params["media_category"] = category
44 |
45 | return params
46 |
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/TweeBox/Mention.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Mention.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/27.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 |
13 | class Mention: TweetEntity {
14 | var id: String
15 | var screenName: String
16 | var name: String
17 |
18 | override init(with json: JSON) {
19 |
20 | id = json["id_str"].stringValue
21 | screenName = json["screen_name"].stringValue
22 | name = json["name"].stringValue
23 |
24 | super.init(with: json)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/TweeBox/MentionTimelineParams.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MentionTimelineParams.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/23.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class MentionTimelineParams: TimelineParams {
12 |
13 | init(sinceID: String? = nil, maxID: String? = nil) {
14 |
15 | super.init(sinceID: sinceID, maxID: maxID, excludeReplies: nil, includeRetweets: nil)
16 |
17 | resourceURL = ResourceURL.statuses_mentions_timeline
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/TweeBox/MentionTimelineTableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MentionTimelineTableViewController.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/23.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class MentionTimelineTableViewController: TimelineTableViewController {
12 |
13 | override func refreshTimeline(handler: (() -> Void)?) {
14 |
15 | let mentionTimelineParams = MentionTimelineParams()
16 |
17 | let timeline = Timeline(
18 | maxID: maxID,
19 | sinceID: sinceID,
20 | fetchNewer: fetchNewer,
21 | resourceURL: mentionTimelineParams.resourceURL,
22 | params: mentionTimelineParams
23 | )
24 |
25 | timeline.fetchData { [weak self] (maxID, sinceID, tweets) in
26 |
27 | if (self?.maxID == nil) && (self?.sinceID == nil) {
28 | if let sinceID = sinceID {
29 | self?.sinceID = sinceID
30 | }
31 | if let maxID = maxID {
32 | self?.maxID = maxID
33 | }
34 | } else {
35 | if (self?.fetchNewer)! {
36 | if let sinceID = sinceID {
37 | self?.sinceID = sinceID
38 | }
39 | } else {
40 | if let maxID = maxID {
41 | self?.maxID = maxID
42 | }
43 | }
44 |
45 | }
46 |
47 | if tweets.count > 0 {
48 |
49 | self?.insertNewTweets(with: tweets)
50 |
51 | let cells = self?.tableView.visibleCells
52 | if cells != nil {
53 | for cell in cells! {
54 | let indexPath = self?.tableView.indexPath(for: cell)
55 | if let tweetCell = cell as? GeneralTweetTableViewCell {
56 | tweetCell.section = indexPath?.section
57 | }
58 | }
59 |
60 | }
61 |
62 | }
63 |
64 | if let handler = handler {
65 | handler()
66 | }
67 | }
68 |
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/TweeBox/MultiUserParams.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MultiUserParams.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/23.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class MultiUserParams: ParamsProtocol {
12 |
13 | public var userID: [String]?
14 |
15 | public var screenName: [String]?
16 |
17 | public var includeEntities = false
18 |
19 | init(userID: [String]?, screenName: [String]?) {
20 | self.userID = userID
21 | self.screenName = screenName
22 | }
23 |
24 |
25 | public func getParams() -> [String: Any] {
26 |
27 | var params = [String: Any]()
28 |
29 | guard (userID != nil || screenName != nil) else {
30 | return params
31 | }
32 |
33 | if userID != nil {
34 | params["user_id"] = userID
35 | } else {
36 | params["screen_name"] = screenName
37 | }
38 |
39 | params["include_entities"] = includeEntities.description
40 |
41 | return params
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/TweeBox/NavigationViewControllerExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NavigationViewControllerExtension.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/12.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIViewController {
12 | var content: UIViewController {
13 | if let navigationViewController = self as? UINavigationController {
14 | return navigationViewController.visibleViewController ?? self
15 | } else {
16 | return self
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/TweeBox/OriginTweetView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OriginTweetView.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/19.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Kingfisher
11 |
12 | class OriginTweetView: UIView {
13 |
14 | public var originTweet: Tweet! {
15 | didSet {
16 | setOriginTweet()
17 | }
18 | }
19 |
20 | private var thumbImages: [TweetMedia]? {
21 | return originTweet?.entities?.thumbMedia
22 | }
23 |
24 | private var hasMedia: Bool {
25 | return (thumbImages != nil && thumbImages!.count > 0)
26 | }
27 |
28 | private func setOriginTweet() {
29 |
30 | for subview in subviews {
31 | subview.removeFromSuperview()
32 | }
33 |
34 | if hasMedia {
35 | print(">>> has media >> \(originTweet.text)")
36 | self.snp.makeConstraints({ (make) in
37 | make.height.equalTo(150)
38 | })
39 | }
40 |
41 |
42 | var thumbImageView: UIImageView?
43 |
44 | let upperBorder = UIButton()
45 | addSubview(upperBorder)
46 | upperBorder.backgroundColor = .lightGray
47 | upperBorder.alpha = 0.5
48 | upperBorder.isUserInteractionEnabled = false
49 | upperBorder.snp.makeConstraints { (make) in
50 | make.top.equalTo(self)
51 | make.width.equalTo(self).multipliedBy(0.8)
52 | make.height.equalTo(1)
53 | make.centerX.equalTo(self)
54 | }
55 |
56 | let lowerBorder = UIButton()
57 | addSubview(lowerBorder)
58 | lowerBorder.backgroundColor = .lightGray
59 | lowerBorder.alpha = 0.5
60 | lowerBorder.isUserInteractionEnabled = false
61 | lowerBorder.snp.makeConstraints { (make) in
62 | make.bottom.equalTo(self)
63 | make.width.equalTo(self).multipliedBy(0.8)
64 | make.height.equalTo(1)
65 | make.centerX.equalTo(self)
66 | }
67 |
68 | if hasMedia {
69 |
70 | thumbImageView = UIImageView()
71 | addSubview(thumbImageView!)
72 | thumbImageView?.snp.makeConstraints { (make) in
73 | make.trailing.equalTo(self)
74 | make.top.equalTo(self)
75 | make.height.equalTo(150)
76 | make.width.equalTo(150)
77 | }
78 | thumbImageView!.kf.setImage(with: thumbImages?[0].mediaURL)
79 | }
80 |
81 | let profileImageView = UIImageView()
82 | addSubview(profileImageView)
83 | profileImageView.snp.makeConstraints { (make) in
84 | make.top.equalTo(self).offset(8)
85 | make.leading.equalTo(upperBorder).offset(-16)
86 | make.height.equalTo(32)
87 | make.width.equalTo(32)
88 | }
89 | profileImageView.cutToRound(radius: 16)
90 | profileImageView.kf.setImage(with: originTweet?.user.profileImageURL)
91 |
92 | let originNameLabel = UILabel()
93 | addSubview(originNameLabel)
94 | originNameLabel.snp.makeConstraints { (make) in
95 | make.top.equalTo(profileImageView)
96 | make.leading.equalTo(profileImageView.snp.trailing).offset(5)
97 | if hasMedia {
98 | make.trailing.greaterThanOrEqualTo(thumbImageView!.snp.leading).offset(-5)
99 | } else {
100 | make.trailing.greaterThanOrEqualTo(self)
101 | }
102 | }
103 | originNameLabel.text = originTweet.user.name
104 | originNameLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
105 | originNameLabel.lineBreakMode = .byTruncatingTail
106 |
107 | let originScreenNameLabel = UILabel()
108 | addSubview(originScreenNameLabel)
109 | originScreenNameLabel.snp.makeConstraints { (make) in
110 | make.bottom.equalTo(profileImageView)
111 | make.leading.equalTo(profileImageView.snp.trailing).offset(5)
112 | if hasMedia {
113 | make.trailing.greaterThanOrEqualTo(thumbImageView!.snp.leading).offset(-5)
114 | } else {
115 | make.trailing.greaterThanOrEqualTo(self)
116 | }
117 | }
118 | originScreenNameLabel.text = "@\(originTweet.user.screenName)"
119 | originScreenNameLabel.textColor = .darkGray
120 | originScreenNameLabel.font = UIFont.preferredFont(forTextStyle: .caption2)
121 | originScreenNameLabel.lineBreakMode = .byTruncatingTail
122 |
123 | let originTextLabel = UILabel()
124 | addSubview(originTextLabel)
125 | originTextLabel.text = originTweet.text
126 | originTextLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
127 | originTextLabel.lineBreakMode = .byTruncatingTail
128 | originTextLabel.numberOfLines = 0
129 | originTextLabel.snp.makeConstraints { (make) in
130 | make.top.equalTo(profileImageView.snp.bottom).offset(3)
131 | make.leading.equalTo(upperBorder)
132 | make.bottom.equalTo(self).offset(-5)
133 | if hasMedia {
134 | make.trailing.equalTo(thumbImageView!.snp.leading).offset(-5)
135 | } else {
136 | make.trailing.equalTo(upperBorder)
137 | }
138 | }
139 | }
140 |
141 | }
142 |
--------------------------------------------------------------------------------
/TweeBox/PageManagerViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageManagerViewController.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/14.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class PageManagerViewController: PannableViewController {
12 |
13 | var pageViewController: EmbededPageViewController!
14 |
15 | public var currentIndex: Int!
16 |
17 | public var imageViewers: [ImageViewerViewController]!
18 |
19 | override func viewDidLoad() {
20 | super.viewDidLoad()
21 |
22 | pageViewController = self.childViewControllers.first as! EmbededPageViewController
23 | pageViewController.currentIndex = currentIndex
24 | pageViewController.imageViewers = imageViewers
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/TweeBox/PannableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PannableViewController.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/11.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 |
12 | class PannableViewController: UIViewController {
13 |
14 | var panGestureRecognizer: UIPanGestureRecognizer?
15 | var originalPosition: CGPoint?
16 | var currentPositionTouched: CGPoint?
17 |
18 | override func viewDidLoad() {
19 | super.viewDidLoad()
20 |
21 | panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panGestureAction(_:)))
22 | view.addGestureRecognizer(panGestureRecognizer!)
23 | }
24 |
25 | @objc func panGestureAction(_ panGesture: UIPanGestureRecognizer) {
26 |
27 | let translation = panGesture.translation(in: view)
28 |
29 | if panGesture.state == .began {
30 | originalPosition = view.center
31 | currentPositionTouched = panGesture.location(in: view)
32 | } else if panGesture.state == .changed {
33 | view.frame.origin = CGPoint(
34 | x: translation.x,
35 | y: translation.y
36 | )
37 | } else if panGesture.state == .ended {
38 | let velocity = panGesture.velocity(in: view)
39 |
40 | if velocity.y >= 500 {
41 | UIView.animate(withDuration: 0.2
42 | , animations: {
43 | self.view.frame.origin = CGPoint(
44 | x: self.view.frame.origin.x,
45 | y: self.view.frame.size.height
46 | )
47 | }, completion: { (isCompleted) in
48 | if isCompleted {
49 | self.dismiss(animated: true, completion: nil)
50 | }
51 | })
52 | } else if velocity.y <= -500 {
53 | UIView.animate(withDuration: 0.2
54 | , animations: {
55 | self.view.frame.origin = CGPoint(
56 | x: self.view.frame.origin.x,
57 | y: -self.view.frame.size.height
58 | )
59 | }, completion: { (isCompleted) in
60 | if isCompleted {
61 | self.dismiss(animated: true, completion: nil)
62 | }
63 | })
64 | } else {
65 | UIView.animate(withDuration: 0.2, animations: {
66 | self.view.center = self.originalPosition!
67 | })
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/TweeBox/Params.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Params.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/24.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol ParamsProtocol {
12 | func getParams() -> [String: Any]
13 | }
14 |
--------------------------------------------------------------------------------
/TweeBox/ParamsWithBounds.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParamsWithBounds.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/24.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol ParamsWithBoundsProtocol: ParamsProtocol {
12 |
13 | var sinceID: String? { get set }
14 | var maxID: String? { get set }
15 | }
16 |
--------------------------------------------------------------------------------
/TweeBox/ParamsWithCursor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParamsWithCursor.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/24.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol ParamsWithCursorProtocol: ParamsProtocol {
12 |
13 | var cursor: String? { get set }
14 | }
15 |
--------------------------------------------------------------------------------
/TweeBox/Place.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Place.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/5.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | struct Place {
13 |
14 | public var attributes: PlaceAttributes
15 | public var boundingBox: Coordinates
16 | public var country: String
17 | public var countryCode: String
18 | public var fullName: String
19 | public var id: String
20 | public var name: String
21 | public var placeType: String
22 | public var url: URL?
23 |
24 | init(with json: JSON) {
25 |
26 | attributes = PlaceAttributes(with: json["attributes"])
27 | boundingBox = Coordinates(with: json["bounding_box"])
28 | country = json["country"].stringValue
29 | countryCode = json["country_code"].stringValue
30 | fullName = json["full_name"].stringValue
31 | id = json["id"].stringValue
32 | name = json["name"].stringValue
33 | placeType = json["place_type"].stringValue
34 | url = URL(string: json["url"].stringValue)
35 | }
36 |
37 |
38 | struct PlaceAttributes {
39 |
40 | public var streetAddress: String?
41 | public var locality: String?
42 | // the city the place is in
43 | public var region: String?
44 | // the administrative region the place is in
45 | public var iso3: String?
46 | // the country code
47 | public var postalCode: String?
48 | // in the preferred local format for the place
49 | public var phone: String?
50 | // in the preferred local format for the place, include long distance code
51 | public var twitter: String?
52 | // twitter screen-name, without @
53 | public var url: URL?
54 | // official/canonical URL for place
55 | // public var appID: String?
56 | // An ID or comma separated list of IDs representing the place in the applications place database.
57 |
58 | init(with json: JSON) {
59 |
60 | streetAddress = json["street_address"].string
61 | locality = json["locality"].string
62 | region = json["region"].string
63 | iso3 = json["iso3"].string
64 | postalCode = json["postal_code"].string
65 | phone = json["phone"].string
66 | twitter = json["twitter"].string
67 | url = URL(string: json["url"].stringValue)
68 | // appID = json["app:id"].string
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/TweeBox/RESTfulClient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RESTfulClient.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/7/28.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import TwitterKit
11 |
12 | class RESTfulClient {
13 |
14 | var resource: (url: String, method: String)
15 |
16 | var params: [String: Any]?
17 |
18 | // private var dataJSON: Data?
19 |
20 | init(resource: (url: String, method: String), params: [String: Any]?) {
21 | self.resource = resource
22 | self.params = params
23 | }
24 |
25 | public func getData(_ handler: @escaping (_ data: Data?) -> Void) {
26 |
27 |
28 | if let userID = Twitter.sharedInstance().sessionStore.session()?.userID {
29 |
30 | let client = TWTRAPIClient(userID: userID)
31 | var clientError : NSError?
32 |
33 |
34 |
35 | let request = client.urlRequest(withMethod: resource.method, url: resource.url, parameters: params, error: &clientError)
36 |
37 | client.sendTwitterRequest(request) { (response, data, connectionError) -> Void in
38 | if connectionError != nil {
39 | print("Error: \(connectionError!)")
40 | }
41 |
42 | if data != nil {
43 | handler(data!)
44 | } else {
45 | print(">>> NO DATA")
46 | handler(nil)
47 | }
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/TweeBox/RESTfulClientWithID.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RESTfulClientWithID.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/29.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import TwitterKit
11 |
12 | class RESTfulClientWithID: RESTfulClient {
13 |
14 | override func getData(_ handler: @escaping (Data?) -> Void) {
15 |
16 | if let userID = Twitter.sharedInstance().sessionStore.session()?.userID {
17 |
18 | let client = TWTRAPIClient(userID: userID)
19 | var clientError : NSError?
20 |
21 | if let tweetID = params?["id"] as? String {
22 |
23 | let url = resource.url.replacingOccurrences(of: ":id", with: tweetID)
24 | print(">>> url \(url)")
25 |
26 | let request = client.urlRequest(withMethod: resource.method, url: url, parameters: nil, error: &clientError)
27 |
28 | client.sendTwitterRequest(request) { (response, data, connectionError) -> Void in
29 | if connectionError != nil {
30 | print("Error: \(connectionError!)")
31 | }
32 |
33 | if data != nil {
34 | handler(data!)
35 | } else {
36 | print(">>> NO DATA")
37 | handler(nil)
38 | }
39 | }
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/TweeBox/RESTfulClientWithMedia.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RESTfulClientWithMedia.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/31.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import TwitterKit
11 |
12 | class RESTfulClientWithMedia {
13 |
14 | var imageData: Data
15 |
16 | private var type: String {
17 | return imageData.MIMEType
18 | }
19 |
20 |
21 | init(imageData: Data) {
22 | self.imageData = imageData
23 | }
24 |
25 |
26 | func getData(_ handler: @escaping (String?) -> Void) {
27 |
28 | if let userID = Twitter.sharedInstance().sessionStore.session()?.userID {
29 |
30 | let client = TWTRAPIClient(userID: userID)
31 |
32 | print(">>> type >> \(type)")
33 |
34 | client.uploadMedia(imageData, contentType: type, completion: { (mediaID, error) in
35 |
36 | guard error == nil else {
37 | print(">>> client error >> \(error.debugDescription)")
38 | return
39 | }
40 |
41 | if let mediaID = mediaID {
42 | handler(mediaID)
43 | } else {
44 | handler(nil)
45 | }
46 | })
47 | }
48 |
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/TweeBox/ReplyTableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReplyTableViewController.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/23.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ReplyTableViewController: SearchTimelineTableViewController {
12 |
13 | @IBOutlet weak var tweetInfoContainerView: UIView!
14 |
15 | private var retweet: Tweet?
16 |
17 | public var tweet: Tweet! {
18 | didSet {
19 | if let originTweet = tweet.retweetedStatus, tweet.text.hasPrefix("RT @") {
20 | retweet = tweet
21 | tweet = originTweet
22 | }
23 |
24 | if tweetID == nil {
25 | tweetID = tweet?.id
26 | }
27 | }
28 | }
29 |
30 | public var tweetID: String! {
31 | didSet {
32 | if tweet == nil {
33 | setTweet()
34 | }
35 | }
36 | }
37 |
38 | override var resultType: SearchResultType {
39 | return .recent
40 | }
41 |
42 | // MARK - Header height
43 | public var cellTextLabelHeight: CGFloat?
44 | private var hasMedia: Bool {
45 | if let media = tweet?.entities?.realMedia, media.count > 0 {
46 | return true
47 | } else {
48 | return false
49 | }
50 | }
51 |
52 | override func viewDidLayoutSubviews() {
53 | super.viewDidLayoutSubviews()
54 |
55 | for subview in (tweetInfoContainerView.subviews[0].subviews) {
56 | if subview.frame.height > 0, subview.subviews.count != 0 {
57 | tweetInfoContainerView?.frame.size.height = subview.frame.height + CGFloat(30)
58 | }
59 | }
60 | }
61 |
62 |
63 | override func viewWillAppear(_ animated: Bool) {
64 | super.viewWillAppear(animated)
65 |
66 | refreshTimeline(handler: nil)
67 | }
68 |
69 |
70 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
71 |
72 | if segue.identifier == "Single Tweet Info" {
73 | if let singleTweetViewController = segue.destination.content as? SingleTweetViewController {
74 | singleTweetViewController.tweet = tweet
75 | }
76 | }
77 |
78 | super.prepare(for: segue, sender: sender)
79 | }
80 |
81 | override func refreshTimeline(handler: (() -> Void)?) {
82 |
83 | let screenName = "%40\(tweet?.user.screenName ?? "")"
84 |
85 | let replyTimelineParams = SearchTweetParams(
86 | query: screenName,
87 | resultType: self.resultType,
88 | until: nil,
89 | sinceID: nil,
90 | maxID: nil,
91 | includeEntities: nil,
92 | resourceURL: ResourceURL.search_tweets
93 | )
94 |
95 | let replyTimeline = ReplyTimeline(
96 | maxID: maxID,
97 | sinceID: sinceID,
98 | fetchNewer: fetchNewer,
99 | resourceURL: replyTimelineParams.resourceURL,
100 | timelineParams: replyTimelineParams,
101 | mainTweetID: tweetID ?? ""
102 | )
103 |
104 | replyTimeline.fetchData { [weak self] (maxID, sinceID, tweets) in
105 |
106 | if (self?.maxID == nil) && (self?.sinceID == nil) {
107 | if let sinceID = sinceID {
108 | self?.sinceID = sinceID
109 | }
110 | if let maxID = maxID {
111 | self?.maxID = maxID
112 | }
113 | } else {
114 | if (self?.fetchNewer)! {
115 | if let sinceID = sinceID {
116 | self?.sinceID = sinceID
117 | }
118 | } else {
119 | if let maxID = maxID {
120 | self?.maxID = maxID
121 | }
122 | }
123 |
124 | }
125 |
126 | if tweets.count > 0 {
127 |
128 | self?.insertNewTweets(with: tweets)
129 |
130 | let cells = self?.tableView.visibleCells
131 | if cells != nil {
132 | for cell in cells! {
133 | let indexPath = self?.tableView.indexPath(for: cell)
134 | if let tweetCell = cell as? GeneralTweetTableViewCell {
135 | tweetCell.section = indexPath?.section
136 | }
137 | }
138 |
139 | }
140 |
141 | }
142 |
143 | if let handler = handler {
144 | handler()
145 | }}
146 |
147 | }
148 |
149 | private func setTweet() {
150 | SingleTweet(
151 | params: TweetParams(of: tweetID!),
152 | resourceURL: ResourceURL.statuses_show_id
153 | ).fetchData { [weak self] (tweet) in
154 | if tweet != nil {
155 | self?.tweet = tweet
156 | }
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/TweeBox/ReplyTimeline.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReplyTimeline.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/24.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | class ReplyTimeline: SearchTimeline {
13 |
14 | public var mainTweetID: String
15 |
16 | init(maxID: String?, sinceID: String?, fetchNewer: Bool = true, resourceURL: (String, String), timelineParams: ParamsWithBoundsProtocol, mainTweetID: String) {
17 |
18 | self.mainTweetID = mainTweetID
19 |
20 | super.init(maxID: maxID, sinceID: sinceID, resourceURL: resourceURL, params: timelineParams)
21 | }
22 |
23 |
24 | override func addToTimeline(_ tweet: Tweet) {
25 |
26 | if let repliedID = tweet.inReplyToStatusID, repliedID == mainTweetID {
27 | self.timeline.append(tweet)
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/TweeBox/ResourceURL.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ResourceURL.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/7/28.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct ResourceURL {
12 |
13 | // GET
14 |
15 | static let user_lookup = (url: "https://api.twitter.com/1.1/users/lookup.json", method: "GET")
16 | /*
17 | Returns fully-hydrated user objects for up to 100 users per request, as specified by comma-separated values passed to the user_id and/or screen_name parameters.
18 |
19 | This method is especially useful when used in conjunction with collections of user IDs returned from GET friends / ids and GET followers / ids.
20 | */
21 |
22 | static let user_show = (url: "https://api.twitter.com/1.1/users/show.json", method: "GET")
23 |
24 | static let users_search = (url: "https://api.twitter.com/1.1/users/search.json", method: "GET")
25 |
26 |
27 | static let statuses_show_id = (url: "https://api.twitter.com/1.1/statuses/show.json", method: "GET")
28 |
29 | static let statuses_retweeters_ids = (url: "https://api.twitter.com/1.1/statuses/retweeters/ids.json", method: "GET")
30 |
31 | static let statuses_home_timeline = (url: "https://api.twitter.com/1.1/statuses/home_timeline.json", method: "GET")
32 |
33 | static let statuses_user_timeline = (url: "https://api.twitter.com/1.1/statuses/user_timeline.json", method: "GET")
34 |
35 | static let statuses_mentions_timeline = (url: "https://api.twitter.com/1.1/statuses/mentions_timeline.json", method: "GET")
36 |
37 | static let favorites_list = (url: "https://api.twitter.com/1.1/favorites/list.json", method: "GET")
38 |
39 |
40 | static let followers_list = (url: "https://api.twitter.com/1.1/followers/list.json", method: "GET")
41 |
42 | static let followings_list = (url: "https://api.twitter.com/1.1/friends/list.json", method: "GET")
43 |
44 |
45 | static let search_tweets = (url: "https://api.twitter.com/1.1/search/tweets.json", method: "GET")
46 |
47 |
48 |
49 | // POST
50 |
51 | static let statuses_update = (url: "https://api.twitter.com/1.1/statuses/update.json", method: "POST")
52 |
53 | static let statuses_destroy_id = (url: "https://api.twitter.com/1.1/statuses/destroy/:id.json", method: "POST")
54 |
55 | static let statuses_unretweet_id = (url: "https://api.twitter.com/1.1/statuses/unretweet/:id.json", method: "POST")
56 |
57 | static let statuses_retweet_id = (url: "https://api.twitter.com/1.1/statuses/retweet/:id.json", method: "POST")
58 |
59 |
60 | static let favorites_create = (url: "https://api.twitter.com/1.1/favorites/create.json", method: "POST")
61 |
62 | static let favorites_destroy = (url: "https://api.twitter.com/1.1/favorites/destroy.json", method: "POST")
63 |
64 |
65 | static let friendships_create = (url: "https://api.twitter.com/1.1/friendships/create.json", method: "POST")
66 |
67 | static let friendships_destroy = (url: "https://api.twitter.com/1.1/friendships/destroy.json", method: "POST")
68 |
69 | static let friendships_update = (url: "https://api.twitter.com/1.1/friendships/update.json", method: "POST")
70 |
71 |
72 | static let blocks_create = (url: "https://api.twitter.com/1.1/blocks/create.json", method: "POST")
73 |
74 | static let blocks_destroy = (url: "https://api.twitter.com/1.1/blocks/destroy.json", method: "POST")
75 |
76 | static let users_report_spam = (url: "https://api.twitter.com/1.1/users/report_spam.json", method: "POST")
77 |
78 |
79 | static let media_upload = (url: "https://upload.twitter.com/1.1/media/upload.json", method: "POST")
80 | }
81 |
--------------------------------------------------------------------------------
/TweeBox/RetweetTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RetweetTableViewCell.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/18.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Kingfisher
11 | import DateToolsSwift
12 | import SnapKit
13 |
14 |
15 | class RetweetTableViewCell: TweetWithTextTableViewCell {
16 |
17 |
18 | @IBOutlet weak var originTweetView: OriginTweetView!
19 |
20 | @IBAction func originTweetTapped(byReactingTo: UIGestureRecognizer) {
21 | guard let section = section, let row = row else { return }
22 | delegate?.originTweetTapped(section: section, row: row)
23 | }
24 |
25 | override func updateUI() {
26 |
27 | super.updateUI()
28 |
29 | addTapGesture()
30 |
31 | originTweetView.originTweet = tweet?.quotedStatus
32 |
33 | }
34 |
35 | private func addTapGesture() {
36 | let tapOnOriginTweet = UITapGestureRecognizer(
37 | target: self,
38 | action: #selector(originTweetTapped(byReactingTo:))
39 | )
40 | tapOnOriginTweet.numberOfTapsRequired = 1
41 | tapOnOriginTweet.numberOfTouchesRequired = 1
42 | originTweetView.addGestureRecognizer(tapOnOriginTweet)
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/TweeBox/RetweeterID.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RetweeterID.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/22.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 |
13 | class RetweeterID {
14 |
15 | // private var userIDList: [String]?
16 |
17 | public var nextCursor = "-1"
18 | public var previousCursor = "-1"
19 | // cursor == "0" indicates the corresponding direction is at the end
20 |
21 | public var fetchOlder = true
22 |
23 | public var params: RetweetersIDParams
24 | public var resourceURL: (String, String)
25 |
26 | init(resourceURL: (String, String), params: RetweetersIDParams, fetchOlder: Bool?, nextCursor: String?, previousCursor: String?) {
27 |
28 | self.resourceURL = resourceURL
29 |
30 | self.params = params
31 |
32 | if let fetchOlder = fetchOlder {
33 | self.fetchOlder = fetchOlder
34 | }
35 |
36 | if let nextCursor = nextCursor {
37 | self.nextCursor = nextCursor
38 | }
39 |
40 | if let previousCursor = previousCursor {
41 | self.previousCursor = previousCursor
42 | }
43 | }
44 |
45 |
46 | public func fetchData(_ handler: @escaping (String, String, [String]) -> Void) {
47 |
48 | if Constants.selfID != "-1" {
49 |
50 | if fetchOlder, previousCursor != "-1" {
51 | params.cursor = previousCursor
52 | }
53 |
54 | if !fetchOlder, nextCursor != "-1" {
55 | params.cursor = nextCursor
56 | }
57 |
58 | let client = RESTfulClient(resource: resourceURL, params: params.getParams())
59 | print(resourceURL)
60 | print(params.getParams())
61 | client.getData() { data in
62 | if let data = data {
63 | let json = JSON(data: data)
64 | print(">>> rt json >> \(json)")
65 |
66 | self.nextCursor = json["next_cursor_str"].stringValue
67 | self.previousCursor = json["previous_cursor_str"].stringValue
68 |
69 | let idList = json["ids"].arrayValue.map({ $0.stringValue })
70 |
71 | handler(self.nextCursor, self.previousCursor, idList)
72 | }
73 | }
74 | }
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/TweeBox/RetweetersIDParams.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RetweetersIDParams.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/22.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class RetweetersIDParams: ParamsWithCursorProtocol {
12 |
13 | public var id: String
14 | public var cursor: String? = "-1"
15 |
16 | // stringify_ids
17 |
18 | init(id: String, cursor: String?) {
19 | self.id = id
20 | if let cursor = cursor {
21 | self.cursor = cursor
22 | }
23 | }
24 |
25 | public func getParams() -> [String: Any] {
26 |
27 | var params = [String: String]()
28 |
29 | params["id"] = id
30 |
31 | if let cursor = cursor, cursor != "-1" {
32 | params["cursor"] = cursor
33 | }
34 |
35 |
36 | params["stringify_ids"] = "true"
37 |
38 | return params
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/TweeBox/SearchDialogView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchDialogView.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/25.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SnapKit
11 |
12 | class SearchDialogView: UIView {
13 |
14 | public var keyword: String? {
15 | didSet {
16 | setSearchButtons()
17 | }
18 | }
19 |
20 | @IBOutlet weak var inputTextField: UITextField!
21 | @IBOutlet weak var searchTweetButton: UIButton!
22 | @IBOutlet weak var searchHashtagButton: UIButton!
23 | @IBOutlet weak var findUserButton: UIButton!
24 | @IBOutlet weak var searchUserButton: UIButton!
25 |
26 | private var buttons: [UIButton]!
27 |
28 |
29 | func initSearchButtons() {
30 |
31 | buttons = [searchTweetButton, searchHashtagButton, findUserButton, searchUserButton]
32 |
33 | searchTweetButton.setTitle("Search Tweet", for: .disabled)
34 | // searchTweetButton.isEnabled = false
35 |
36 | searchHashtagButton.setTitle("Search Hashtag", for: .disabled)
37 | // searchHashtagButton.isEnabled = false
38 |
39 | findUserButton.setTitle("Find User", for: .disabled)
40 | // findUserButton.isEnabled = false
41 |
42 | searchUserButton.setTitle("Search User", for: .disabled)
43 | // searchUserButton.isEnabled = false
44 |
45 | for button in buttons {
46 |
47 | button.isEnabled = false
48 |
49 | button.cutToRound(radius: 5)
50 |
51 | button.layer.borderWidth = 1.0
52 | button.layer.borderColor = UIColor.lightGray.cgColor
53 |
54 | // button.backgroundColor = .gray
55 |
56 | button.setTitleColor(.darkGray, for: .normal)
57 | button.setTitleColor(.lightGray, for: .disabled)
58 |
59 | }
60 |
61 | }
62 |
63 |
64 | private func setSearchButtons() {
65 | if let keyword = keyword, !(keyword.isEmpty) {
66 |
67 | searchTweetButton.setTitle("Tweet With \"\(keyword)\"", for: .normal)
68 | if !(searchTweetButton.isEnabled) {
69 | searchTweetButton.isEnabled = true
70 | }
71 |
72 | if keyword.isLegit {
73 | searchHashtagButton.setTitle("Hashtag \"#\(keyword)\"", for: .normal)
74 | if !(searchHashtagButton.isEnabled) {
75 | searchHashtagButton.isEnabled = true
76 | }
77 |
78 | findUserButton.setTitle("User \"@\(keyword)\"", for: .normal)
79 | if !(findUserButton.isEnabled) {
80 | findUserButton.isEnabled = true
81 | }
82 |
83 | searchUserButton.setTitle("User With \"\(keyword)\"", for: .normal)
84 | if !(searchUserButton.isEnabled) {
85 | searchUserButton.isEnabled = true
86 | }
87 | } else {
88 |
89 | for index in 1...3 {
90 | let button = buttons[index]
91 | if button.isEnabled {
92 | button.isEnabled = false
93 | }
94 | }
95 | }
96 | } else {
97 |
98 | for button in buttons {
99 | if button.isEnabled {
100 | button.isEnabled = false
101 | }
102 | }
103 | }
104 | }
105 | }
106 |
107 |
--------------------------------------------------------------------------------
/TweeBox/SearchResultType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchResultType.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/24.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum SearchResultType: String {
12 | case mixed = "mixed"
13 | case recent = "recent"
14 | case popular = "popular"
15 | }
16 |
--------------------------------------------------------------------------------
/TweeBox/SearchTimeline.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchTimeline.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/26.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | class SearchTimeline: Timeline {
13 |
14 | override func appendTweet(from json: JSON) {
15 |
16 | for (_, tweetJSON) in json["statuses"] {
17 | if tweetJSON.null == nil {
18 | let tweet = Tweet(with: tweetJSON)
19 | addToTimeline(tweet)
20 | }
21 | }
22 | }
23 |
24 | func addToTimeline(_ tweet: Tweet) {
25 | self.timeline.append(tweet)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/TweeBox/SearchTimelineTableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by 4faramita on 2017/8/24.
3 | // Copyright (c) 2017 4faramita. All rights reserved.
4 | //
5 |
6 | import Foundation
7 | import UIKit
8 |
9 |
10 | class SearchTimelineTableViewController: TimelineTableViewController {
11 |
12 | var query: String? {
13 | didSet {
14 | refreshTimeline(handler: nil)
15 | }
16 | }
17 |
18 | var resultType: SearchResultType {
19 | return .recent
20 | }
21 |
22 | @IBAction func done(_ sender: UIBarButtonItem) {
23 | dismiss(animated: true, completion: nil)
24 | }
25 |
26 | override func setAndPerformSegueForHashtag() {
27 |
28 | let destinationViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SearchTimelineViewController")
29 |
30 | let segue = UIStoryboardSegue(identifier: "Show Tweets with Hashtag", source: self, destination: destinationViewController) {
31 | self.navigationController?.show(destinationViewController, sender: self)
32 | }
33 |
34 | self.prepare(for: segue, sender: self)
35 | segue.perform()
36 |
37 | }
38 |
39 |
40 | override func refreshTimeline(handler: (() -> Void)?) {
41 |
42 | let replyTimelineParams = SearchTweetParams(
43 | query: self.query ?? "",
44 | resultType: self.resultType,
45 | until: nil,
46 | sinceID: nil, // this two will be managed
47 | maxID: nil, // in timeline data retriever
48 | includeEntities: true,
49 | resourceURL: ResourceURL.search_tweets
50 | )
51 |
52 | let searchTimeline = SearchTimeline(
53 | maxID: maxID,
54 | sinceID: sinceID,
55 | fetchNewer: fetchNewer,
56 | resourceURL: replyTimelineParams.resourceURL,
57 | params: replyTimelineParams
58 | )
59 |
60 | searchTimeline.fetchData { [weak self] (maxID, sinceID, tweets) in
61 |
62 | if (self?.maxID == nil) && (self?.sinceID == nil) {
63 | if let sinceID = sinceID {
64 | self?.sinceID = sinceID
65 | }
66 | if let maxID = maxID {
67 | self?.maxID = maxID
68 | }
69 | } else {
70 | if (self?.fetchNewer)! {
71 | if let sinceID = sinceID {
72 | self?.sinceID = sinceID
73 | }
74 | } else {
75 | if let maxID = maxID {
76 | self?.maxID = maxID
77 | }
78 | }
79 |
80 | }
81 |
82 | print(">>> tweets >> \(tweets.count)")
83 |
84 | if tweets.count > 0 {
85 |
86 | self?.insertNewTweets(with: tweets)
87 |
88 | let cells = self?.tableView.visibleCells
89 | if cells != nil {
90 | for cell in cells! {
91 | let indexPath = self?.tableView.indexPath(for: cell)
92 | if let tweetCell = cell as? GeneralTweetTableViewCell {
93 | tweetCell.section = indexPath?.section
94 | }
95 | }
96 | }
97 | }
98 |
99 | if let handler = handler {
100 | handler()
101 | }
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/TweeBox/SearchTweetParams.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchTweetParams.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/24.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class SearchTweetParams: ParamsWithBoundsProtocol {
12 |
13 | public var query: String // qd
14 | // A UTF-8, URL-encoded search query of 500 characters maximum, including operators.
15 |
16 | // public var geocode
17 | // public var lang
18 | // public var locale
19 |
20 | public var resultType: SearchResultType
21 |
22 | public var count = Constants.tweetLimitPerRefresh
23 |
24 | public var until: Date?
25 | /*
26 | Returns tweets created before the given date.
27 | Date should be formatted as YYYY-MM-DD.
28 | Keep in mind that the search index has a 7-day limit.
29 | In other words, no tweets will be found for a date older than one week.
30 | */
31 |
32 | public var sinceID: String?
33 | public var maxID: String?
34 |
35 | public var includeEntities = true
36 |
37 | public var resourceURL: (String, String)!
38 |
39 |
40 | init(
41 | query: String,
42 | resultType: SearchResultType,
43 | until: Date?,
44 | sinceID: String?,
45 | maxID: String?,
46 | includeEntities: Bool?,
47 | resourceURL: (String, String)
48 | ) {
49 | self.query = query
50 | self.resultType = resultType
51 |
52 | if let until = until {
53 | self.until = until
54 | }
55 | if let sinceID = sinceID {
56 | self.sinceID = sinceID
57 | }
58 | if let maxID = maxID {
59 | self.maxID = maxID
60 | }
61 | if let includeEntities = includeEntities {
62 | self.includeEntities = includeEntities
63 | }
64 |
65 | self.resourceURL = resourceURL
66 | }
67 |
68 | public func getParams() -> [String: Any] {
69 |
70 | var params = [String: String]()
71 |
72 | params["q"] = query
73 |
74 | params["result_type"] = resultType.rawValue
75 |
76 | params["count"] = count
77 |
78 | // if let until = until {
79 | // do things about until
80 | // }
81 |
82 | if let sinceID = sinceID {
83 | params["since_id"] = sinceID
84 | }
85 |
86 | if let maxID = maxID {
87 | params["max_id"] = maxID
88 | }
89 |
90 | params["include_entities"] = includeEntities.description
91 |
92 | return params
93 |
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/TweeBox/SearchUsers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchUsers.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/26.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | class SearchUsers: UserListRetrieverProtocol {
13 |
14 | private var userList = [TwitterUser]()
15 |
16 | public var resourceURL: (String, String)
17 | public var params: UsersSearchParams
18 |
19 | init(params: UsersSearchParams, resourceURL: (String, String)) {
20 | self.resourceURL = resourceURL
21 | self.params = params
22 | }
23 |
24 | public func fetchData(_ handler: @escaping (String, String, [TwitterUser]) -> Void) {
25 | if Constants.selfID != "-1" {
26 | let client = RESTfulClient(resource: resourceURL, params: params.getParams())
27 |
28 | client.getData() { data in
29 | if let data = data {
30 |
31 | let json = JSON(data: data)
32 |
33 | for (_, userJSON) in json {
34 | if userJSON.null == nil {
35 | let user = TwitterUser(with: userJSON)
36 | self.userList.append(user) // mem cycle?
37 | }
38 | }
39 |
40 | handler("0", "0", self.userList)
41 | }
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/TweeBox/SimplePostParams.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SimplePostParams.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/28.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class SimplePostParams: ParamsProtocol {
12 |
13 | public var id: String
14 |
15 | // var trim_user = true
16 |
17 |
18 | init(id: String) {
19 | self.id = id
20 | }
21 |
22 |
23 | func getParams() -> [String : Any] {
24 |
25 | var params = [String: String]()
26 |
27 | params["id"] = id
28 |
29 | return params
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/TweeBox/SimpleSearchViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SimpleSearchViewController.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/27.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class SimpleSearchViewController: UIViewController {
12 |
13 | public var keywordToSet: String?
14 |
15 | fileprivate var keyword: String? {
16 | return keywordToSet
17 | }
18 |
19 | fileprivate var fetchedUser: TwitterUser?
20 |
21 | @IBAction func findUser(_ sender: UIButton) {
22 |
23 | fetchUser() { [weak self] (user) in
24 |
25 | if let user = user {
26 | self?.fetchedUser = user
27 | self?.performSegue(withIdentifier: "Show User", sender: self)
28 | } else {
29 | self?.alertForNoSuchUser()
30 | }
31 | }
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/TweeBox/SimpleTweetComposer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SimpleTweetComposer.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/29.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | struct SimpleTweetComposer {
13 |
14 | var id: String
15 |
16 | func retweet(handler: @escaping (Bool, Tweet?) -> Void) {
17 |
18 | let params = SimplePostParams(id: id)
19 |
20 | let dataRetriever = SimpleTweetPoster(params: params, resourceURL: ResourceURL.statuses_retweet_id)
21 |
22 | dataRetriever.postData { (retweet) in
23 | if self.id == retweet?.retweetedStatus?.id {
24 | handler(true, retweet)
25 | } else {
26 | handler(false, retweet)
27 | }
28 | }
29 | }
30 |
31 |
32 | func unRetweet(handler: @escaping (Bool, Tweet?) -> Void) {
33 |
34 | let params = SimplePostParams(id: id)
35 |
36 | let dataRetriever = SimpleTweetPoster(params: params, resourceURL: ResourceURL.statuses_unretweet_id)
37 |
38 | dataRetriever.postData { (retweet) in
39 | if self.id == retweet?.retweetedStatus?.id {
40 | handler(true, retweet)
41 | } else {
42 | handler(false, retweet)
43 | }
44 | }
45 | }
46 |
47 |
48 | func deleteTweet(handler: @escaping (Bool, Tweet?) -> Void) {
49 |
50 | let params = SimplePostParams(id: id)
51 |
52 | let dataRetriever = SimpleTweetPoster(params: params, resourceURL: ResourceURL.statuses_destroy_id)
53 |
54 | dataRetriever.postData { (tweet) in
55 | // if self.id == tweet?.id {
56 | // handler(true, tweet)
57 | // } else {
58 | // handler(false, tweet)
59 | // }
60 | }
61 | handler(true, nil)
62 | // FIX THIS!
63 |
64 | }
65 |
66 |
67 | func like(handler: @escaping (Bool, Tweet?) -> Void) {
68 |
69 | let params = SimplePostParams(id: id)
70 |
71 | let dataRetriever = FavoritePoster(params: params, resourceURL: ResourceURL.favorites_create)
72 |
73 | dataRetriever.postData { (tweet) in
74 | if self.id == tweet?.id {
75 | handler(true, tweet)
76 | } else {
77 | handler(false, tweet)
78 | }
79 | }
80 | }
81 |
82 |
83 | func dislike(handler: @escaping (Bool, Tweet?) -> Void) {
84 |
85 | let params = SimplePostParams(id: id)
86 |
87 | let dataRetriever = FavoritePoster(params: params, resourceURL: ResourceURL.favorites_destroy)
88 |
89 | dataRetriever.postData { (tweet) in
90 | if self.id == tweet?.id {
91 | handler(true, tweet)
92 | } else {
93 | handler(false, tweet)
94 | }
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/TweeBox/SimpleTweetPoster.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SimpleTweetPoster.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/28.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 |
13 | class SimpleTweetPoster {
14 |
15 | public var resourceURL: (String, String)
16 | public var params: SimplePostParams
17 |
18 | init(params: SimplePostParams, resourceURL: (String, String)) {
19 | self.resourceURL = resourceURL
20 | self.params = params
21 | }
22 |
23 |
24 | public func postData(_ handler: @escaping (Tweet?) -> Void) {
25 | if Constants.selfID != "-1" {
26 | let client = RESTfulClientWithID(resource: resourceURL, params: params.getParams())
27 |
28 | print(">>> composer >> \(params.getParams())")
29 |
30 | client.getData() { data in
31 | if let data = data {
32 | let json = JSON(data: data)
33 | if json.null == nil {
34 | let tweet = Tweet(with: json)
35 | handler(tweet)
36 | } else {
37 | handler(nil)
38 | }
39 | } else {
40 | handler(nil)
41 | }
42 | }
43 | }
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/TweeBox/SimpleUserParams.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SimpleUserParams.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/29.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class SimpleUserParams: UserParams {
12 |
13 | // public var follow: Bool
14 | // Enable notifications for the target user.
15 |
16 | override func getParams() -> [String : Any] {
17 |
18 | var params = [String: String]()
19 |
20 | if userID != nil {
21 | params["user_id"] = userID
22 | } else if screenName != nil {
23 | params["screen_name"] = screenName
24 | }
25 |
26 | return params
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/TweeBox/SingleTweet.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SingleTweet.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/21.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 |
13 | class SingleTweet {
14 |
15 | // private var user: TwitterUser!
16 |
17 | public var resourceURL: (String, String)
18 | public var params: TweetParams
19 |
20 | init(params: TweetParams, resourceURL: (String, String)) {
21 | self.resourceURL = resourceURL
22 | self.params = params
23 | }
24 |
25 | public func fetchData(_ handler: @escaping (Tweet?) -> Void) {
26 | if Constants.selfID != "-1" {
27 | let client = RESTfulClient(resource: resourceURL, params: params.getParams())
28 |
29 | client.getData() { data in
30 | if let data = data {
31 | let json = JSON(data: data)
32 | if json.null == nil {
33 | let tweet = Tweet(with: json)
34 | handler(tweet)
35 | }
36 | }
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/TweeBox/SingleUser.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SingleUser.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/20.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | class SingleUser {
13 |
14 | // private var user: TwitterUser!
15 |
16 | public var resourceURL: (String, String)
17 | public var params: UserParams
18 |
19 | init(params: UserParams, resourceURL: (String, String)) {
20 | self.resourceURL = resourceURL
21 | self.params = params
22 | }
23 |
24 | public func fetchData(_ handler: @escaping (TwitterUser?) -> Void) {
25 | if Constants.selfID != "-1" {
26 | let client = RESTfulClient(resource: resourceURL, params: params.getParams())
27 |
28 | client.getData() { data in
29 | if let data = data {
30 | let json = JSON(data: data)
31 | if json.null == nil {
32 | let user = TwitterUser(with: json)
33 | handler(user)
34 | } else {
35 | handler(nil)
36 | }
37 | } else {
38 | handler(nil)
39 | }
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/TweeBox/SpamUserParams.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SpamUserParams.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/29.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class SpamUserParams: SimpleUserParams {
12 |
13 | var performBlock = true
14 |
15 | init(userID: String?, screenName: String?, performBlock: Bool?) {
16 |
17 | if let performBlock = performBlock {
18 | self.performBlock = performBlock
19 | }
20 |
21 | super.init(userID: userID, screenName: screenName)
22 |
23 | }
24 |
25 | override func getParams() -> [String : Any] {
26 |
27 | var params = [String: String]()
28 |
29 | if userID != nil {
30 | params["user_id"] = userID
31 | } else if screenName != nil {
32 | params["screen_name"] = screenName
33 | }
34 |
35 | params["perform_block"] = performBlock.description
36 |
37 | return params
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/TweeBox/Timeline.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Timeline.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/7/28.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | class Timeline {
13 | public var timeline = [Tweet]()
14 | public var maxID: String?
15 | public var sinceID: String?
16 | public var fetchNewer: Bool
17 | public var resourceURL: (String, String)
18 | public var params: ParamsWithBoundsProtocol
19 |
20 | init(maxID: String?, sinceID: String?, fetchNewer: Bool = true, resourceURL: (String, String), params: ParamsWithBoundsProtocol) {
21 |
22 | self.maxID = maxID
23 | self.sinceID = sinceID
24 |
25 | self.fetchNewer = fetchNewer
26 |
27 | self.resourceURL = resourceURL
28 | self.params = params
29 | }
30 |
31 | func appendTweet(from json: JSON) {
32 |
33 | for (_, tweetJSON) in json {
34 | if tweetJSON.null == nil {
35 | let tweet = Tweet(with: tweetJSON)
36 | self.timeline.append(tweet) // mem cycle?
37 | }
38 | }
39 |
40 | }
41 |
42 | public func fetchData(_ handler: @escaping (String?, String?, [Tweet]) -> Void) {
43 |
44 | if Constants.selfID != "-1" {
45 |
46 | if fetchNewer, sinceID != nil {
47 | params.sinceID = String(Int(sinceID!)! + 1)
48 | }
49 |
50 | if !fetchNewer, maxID != nil {
51 | params.maxID = String(Int(maxID!)! - 1)
52 | }
53 |
54 | let client = RESTfulClient(resource: resourceURL, params: params.getParams())
55 | print(">>> params \(params.getParams())")
56 | client.getData() { data in
57 | if let data = data {
58 | let json = JSON(data: data)
59 |
60 | self.appendTweet(from: json)
61 |
62 | self.maxID = self.timeline.last?.id // if fetch tweets below this batch, the earliest one in this batch would be the max one for the next batch
63 | self.sinceID = self.timeline.first?.id // vice versa
64 |
65 | handler(self.maxID, self.sinceID, self.timeline)
66 | } else {
67 | handler(self.maxID, self.sinceID, self.timeline)
68 | }
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/TweeBox/TimelineParams.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TimelineParams.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/9.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class TimelineParams: ParamsWithBoundsProtocol {
12 |
13 | public var sinceID: String?
14 |
15 | // count: no need to define here
16 |
17 | public var maxID: String?
18 |
19 | // trimUser
20 |
21 | public var excludeReplies = false
22 |
23 | public var includeRetweets = true
24 |
25 | public var resourceURL = ResourceURL.search_tweets
26 |
27 |
28 | init(sinceID: String? = nil, maxID: String? = nil, excludeReplies: Bool?, includeRetweets: Bool?) {
29 |
30 | self.sinceID = sinceID
31 | self.maxID = maxID
32 | if let excludeReplies = excludeReplies {
33 | self.excludeReplies = excludeReplies
34 | }
35 | if let includeRetweets = includeRetweets {
36 | self.includeRetweets = includeRetweets
37 | }
38 | }
39 |
40 | public func getParams() -> [String: Any] {
41 |
42 | var params = [String: String]()
43 |
44 | params["count"] = Constants.tweetLimitPerRefresh
45 |
46 | if sinceID != nil {
47 | params["since_id"] = sinceID
48 | }
49 |
50 | if maxID != nil {
51 | params["max_id"] = maxID
52 | }
53 |
54 | if excludeReplies {
55 | params["exclude_replies"] = "true"
56 | }
57 |
58 | if !includeRetweets {
59 | params["include_rts"] = "false"
60 | }
61 |
62 | return params
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/TweeBox/TweeBox.xcdatamodeld/.xccurrentversion:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | _XCCurrentVersionName
6 | TweeBox.xcdatamodel
7 |
8 |
9 |
--------------------------------------------------------------------------------
/TweeBox/TweeBox.xcdatamodeld/TweeBox.xcdatamodel/contents:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/TweeBox/TweetClickableContentProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TweetClickableContentProtocol.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/27.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | protocol TweetClickableContentProtocol {
13 |
14 | var keyword: String? { get set }
15 | var fetchedUser: TwitterUser? { get set }
16 |
17 | func performSegue(withIdentifier: String, sender: Any?)
18 | func alertForNoSuchUser(viewController: UIViewController)
19 | func setAndPerformSegueForHashtag()
20 | }
21 |
--------------------------------------------------------------------------------
/TweeBox/TweetComposer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TweetComposer.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/30.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | class TweetComposer {
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/TweeBox/TweetEntity.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TweetEntity.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/7.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | class TweetEntity {
13 |
14 | var indices: [Int]
15 |
16 | init(with json: JSON) {
17 | indices = json["indices"].arrayValue.map({ $0.intValue })
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/TweeBox/TweetLabel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TweetLabel.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/27.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class TweetLabel: YYLabel {
12 |
13 | /*
14 | // Only override draw() if you perform custom drawing.
15 | // An empty implementation adversely affects performance during animation.
16 | override func draw(_ rect: CGRect) {
17 | // Drawing code
18 | }
19 | */
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/TweeBox/TweetMedia.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TweetMedia.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/7.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | class TweetMedia: TweetEntity {
13 |
14 | public var id: String // id_str
15 |
16 | public var mediaURLHTTP: URL? // media_url
17 | public var mediaURL: URL? // media_url_https
18 | public var url: URL?
19 | // The media URL that was extracted
20 | public var displayURL: String
21 | // Not a URL but a string to display instead of the media URL
22 | public var expandedURL: URL?
23 | // The fully resolved media URL
24 |
25 | public var sizes: TweetPhotoSize
26 | /*
27 | We support different sizes: thumb, small, medium and large.
28 | The media_url defaults to medium but you can retrieve the media in different sizes by appending a colon + the size key
29 | (for example: http://pbs.twimg.com/media/A7EiDWcCYAAZT1D.jpg:thumb ).
30 | Each available size comes with three attributes that describe it:
31 | w : the width (in pixels) of the media in this particular size;
32 | h : the height (in pixels) of the media in this particular size;
33 | and resize : how we resized the media to this particular size (can be crop or fit )
34 | */
35 |
36 | public var type: String
37 |
38 | public var extAltText: String?
39 |
40 | public var sourceStatusID: String?
41 | // For Tweets containing media that was originally associated with a different tweet,
42 | // this string-based ID points to the original Tweet.
43 |
44 | // only for video
45 | public var videoInfo: TweetVideoInfo?
46 |
47 |
48 | init(with json: JSON, quality: MediaSize) {
49 |
50 | id = json["id_str"].stringValue
51 | mediaURLHTTP = URL(string: json["media_url"].stringValue, quality: quality)
52 | mediaURL = URL(string: json["media_url_https"].stringValue, quality: quality)
53 | url = URL(string: json["url"].stringValue)
54 | displayURL = json["display_url"].stringValue
55 | expandedURL = URL(string: json["expanded_url"].stringValue)
56 | sizes = TweetPhotoSize(with: json["sizes"])
57 | type = json["type"].stringValue
58 | extAltText = json["ext_alt_text"].string
59 | sourceStatusID = json["source_status_id_str"].string
60 |
61 | if json["video_info"].null == nil {
62 | // video exists
63 | videoInfo = TweetVideoInfo(with: json["video_info"])
64 | }
65 |
66 | super.init(with: json)
67 | }
68 | }
69 |
70 | extension TweetMedia {
71 |
72 | func getCutSize(with ratio: CGFloat, at quality: MediaSize) -> CGSize {
73 |
74 | let picWidth: CGFloat
75 | let picHeight: CGFloat
76 |
77 | let actualSize: TweetPhotoSize.sizeObject
78 |
79 |
80 | switch quality {
81 | case .thumb:
82 | actualSize = self.sizes.thumb
83 | case .small:
84 | actualSize = self.sizes.small
85 | case .medium:
86 | actualSize = self.sizes.medium
87 | case .large:
88 | actualSize = self.sizes.large
89 | default:
90 | actualSize = self.sizes.large
91 | }
92 | let actualHeight = CGFloat(actualSize.h)
93 | let actualWidth = CGFloat(actualSize.w)
94 |
95 | if actualHeight / actualWidth >= ratio {
96 | // too long
97 | picWidth = actualWidth
98 | picHeight = picWidth * ratio
99 | } else {
100 | // too wide
101 | picHeight = actualHeight
102 | picWidth = picHeight / ratio
103 | }
104 |
105 | return CGSize(width: picWidth, height: picHeight)
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/TweeBox/TweetParams.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TweetParams.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/21.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class TweetParams: ParamsProtocol {
12 |
13 | public var tweetID: String?
14 |
15 | public var trimUser = false
16 |
17 | public var includeMyRetweet = true
18 |
19 | public var includeEntities = true
20 |
21 | public var includeExtAltText = true
22 |
23 |
24 | init(of tweetID: String) { self.tweetID = tweetID }
25 |
26 |
27 | public func getParams() -> [String: Any] {
28 |
29 | var params = [String: String]()
30 |
31 | guard tweetID != nil else { return params }
32 |
33 | params["id"] = tweetID!
34 |
35 | if trimUser {
36 | params["trim_user"] = "true"
37 | }
38 |
39 | if !includeMyRetweet {
40 | params["include_my_retweet"] = "false"
41 | }
42 |
43 | if !includeEntities {
44 | params["include_entities"] = "false"
45 | }
46 |
47 | if !includeExtAltText {
48 | params["include_ext_alt_text"] = "false"
49 | }
50 |
51 | return params
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/TweeBox/TweetPhoto.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TweetPhoto.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/7.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | class TweetPhoto: TweetEntity {
13 |
14 | public var id: String
15 |
16 | public var media_url: String
17 | public var media_url_https: String
18 | public var url: String
19 | // The media URL that was extracted
20 | public var display_url: String
21 | // Not a URL but a string to display instead of the media URL
22 | public var expanded_url: String
23 | // The fully resolved media URL
24 |
25 | public var sizes: TweetMediaSize
26 | /*
27 | We support different sizes: thumb, small, medium and large.
28 | The media_url defaults to medium but you can retrieve the media in different sizes by appending a colon + the size key
29 | (for example: http://pbs.twimg.com/media/A7EiDWcCYAAZT1D.jpg:thumb ).
30 | Each available size comes with three attributes that describe it:
31 | w : the width (in pixels) of the media in this particular size;
32 | h : the height (in pixels) of the media in this particular size;
33 | and resize : how we resized the media to this particular size (can be crop or fit )
34 | */
35 |
36 | public var type: String
37 |
38 | public var ext_alt_text: String?
39 |
40 | public var source_status_id: String?
41 | // For Tweets containing media that was originally associated with a different tweet,
42 | // this string-based ID points to the original Tweet.
43 |
44 |
45 | // for video
46 |
47 | public var video_info: String?
48 | // Contains information about aspect ratio.
49 | // Typical values are [4, 3] or [16, 9].
50 | // This field is present only when there is a video in the payload.
51 |
52 | public var duration_millis: Int?
53 |
54 | public var variants:
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/TweeBox/TweetPhotoSize.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TweetPhotoSize.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/7.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | struct TweetPhotoSize {
13 |
14 | public var thumb: sizeObject
15 | public var small: sizeObject
16 | public var medium: sizeObject
17 | public var large: sizeObject
18 |
19 | init(with json: JSON) {
20 | thumb = sizeObject(with: json["thumb"])
21 | small = sizeObject(with: json["small"])
22 | medium = sizeObject(with: json["medium"])
23 | large = sizeObject(with: json["large"])
24 | }
25 |
26 | struct sizeObject {
27 |
28 | public var w: Int
29 | public var h: Int
30 | public var resize: String
31 |
32 | init(with json: JSON) {
33 | w = json["w"].intValue
34 | h = json["h"].intValue
35 | resize = json["resize"].stringValue
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TweeBox/TweetPostParams.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TweetPostParams.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/30.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class TweetPostParams: ParamsProtocol {
12 |
13 | var text: String // status
14 |
15 | var inReplyToStatusID: String?
16 |
17 | var possiblySensitive: Bool
18 |
19 | // lat
20 | // long
21 | // place_id
22 | // display_coordinates
23 |
24 | var trimUser = false
25 |
26 | var mediaIDs: String? // [String]()
27 |
28 | var enableDMCommands = false
29 |
30 | var failDMCommands = false
31 |
32 |
33 | init(text: String, inReplyToStatusID: String?, possiblySensitive: Bool, mediaIDs: String?) {
34 | self.text = text
35 |
36 | if let inReplyToStatusID = inReplyToStatusID {
37 | self.inReplyToStatusID = inReplyToStatusID
38 | }
39 |
40 | self.possiblySensitive = possiblySensitive
41 |
42 | if let mediaIDs = mediaIDs {
43 | self.mediaIDs = mediaIDs
44 | }
45 | }
46 |
47 | func getParams() -> [String : Any] {
48 |
49 | var params = [String : Any]()
50 |
51 | params["status"] = text
52 |
53 | if let inReplyToStatusID = inReplyToStatusID {
54 | params["in_reply_to_status_id"] = inReplyToStatusID
55 | }
56 |
57 | params["possibly_sensitive"] = possiblySensitive.description
58 |
59 | if let mediaIDs = mediaIDs {
60 | params["media_ids"] = mediaIDs
61 | }
62 |
63 | params["enable_dm_commands"] = enableDMCommands.description
64 |
65 | params["fail_dm_commands"] = failDMCommands.description
66 |
67 | return params
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/TweeBox/TweetPoster.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TweetPoster.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/30.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 |
13 | class TweetPoster {
14 |
15 | private let resourceURL = ResourceURL.statuses_update
16 | public var tweetParams: TweetPostParams
17 |
18 |
19 | init(tweetParams: TweetPostParams) {
20 | self.tweetParams = tweetParams
21 | }
22 |
23 |
24 | func postData(_ handler: @escaping (Tweet?) -> Void) {
25 |
26 | if Constants.selfID != "-1" {
27 | let client = RESTfulClient(resource: resourceURL, params: tweetParams.getParams())
28 |
29 | print(">>> TweetPoster >> \(tweetParams.getParams())")
30 |
31 | client.getData() { data in
32 | if let data = data {
33 | let json = JSON(data: data)
34 | if json.null == nil {
35 | let tweet = Tweet(with: json)
36 | handler(tweet)
37 | } else {
38 | handler(nil)
39 | }
40 | } else {
41 | handler(nil)
42 | }
43 | }
44 | }
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/TweeBox/TweetSymbol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TweetSymbol.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/27.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | //class TweetSymbol: Hashtag { }
12 |
--------------------------------------------------------------------------------
/TweeBox/TweetTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TweetTableViewCell.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/8.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Kingfisher
11 | import DateToolsSwift
12 |
13 |
14 | protocol TweetTableViewCellProtocol: class {
15 |
16 | func profileImageTapped(section: Int, row: Int)
17 |
18 | func imageTapped(section: Int, row: Int, mediaIndex: Int, media: [TweetMedia])
19 |
20 | func originTweetTapped(section: Int, row: Int)
21 | }
22 |
23 | class TweetTableViewCell: UITableViewCell {
24 |
25 | // tap to segue
26 | weak var delegate: TweetTableViewCellProtocol?
27 |
28 | var section: Int?
29 | var row: Int?
30 | var mediaIndex: Int?
31 |
32 | var retweet: Tweet?
33 |
34 | var tweet: Tweet? {
35 | didSet {
36 | if let originTweet = tweet?.retweetedStatus, let retweetText = tweet?.text, retweetText.hasPrefix("RT @") {
37 | retweet = tweet
38 | tweet = originTweet
39 | }
40 | updateUI()
41 | }
42 | }
43 |
44 |
45 | @IBOutlet weak var tweetUserProfilePic: UIImageView!
46 | @IBOutlet weak var tweetCreatedTime: UILabel!
47 | @IBOutlet weak var tweetUserName: UILabel!
48 | @IBOutlet weak var tweetUserID: UILabel!
49 |
50 | @IBOutlet weak var retweetCount: UILabel!
51 | @IBOutlet weak var likeCount: UILabel!
52 |
53 | @IBOutlet weak var replyImage: UIImageView!
54 | @IBOutlet weak var retweetImage: UIImageView!
55 | @IBOutlet weak var retweeterProfileImage: UIImageView!
56 | @IBOutlet weak var likeImage: UIImageView!
57 |
58 |
59 | @IBAction func profileImageTapped(byReactingTo: UIGestureRecognizer) {
60 | guard let section = section, let row = row else { return }
61 | delegate?.profileImageTapped(section: section, row: row)
62 | }
63 |
64 |
65 | func updateUI() {
66 |
67 | // if let mainTweetID = tweet?.inReplyToStatusID {
68 | // self.backgroundColor = UIColor(red: 0, green: 1, blue: 0, alpha: 0.05)
69 | // }
70 |
71 | if let userProfileImageURL = tweet?.user.profileImageURL, let picView = self.tweetUserProfilePic {
72 |
73 | picView.kf.setImage(
74 | with: userProfileImageURL,
75 | // placeholder: placeholder,
76 | options: [
77 | .transition(.fade(Constants.picFadeInDuration)),
78 | ]
79 | )
80 |
81 | // picView.layer.borderWidth = 3.0
82 | // picView.layer.borderColor = UIColor.lightGray.cgColor
83 |
84 | picView.cutToRound(radius: nil)
85 |
86 | // tap to segue
87 | let tapOnProfileImage = UITapGestureRecognizer(
88 | target: self,
89 | action: #selector(profileImageTapped(byReactingTo:))
90 | )
91 | tapOnProfileImage.numberOfTapsRequired = 1
92 | picView.addGestureRecognizer(tapOnProfileImage)
93 | picView.isUserInteractionEnabled = true
94 | } else {
95 | tweetUserProfilePic?.image = nil
96 | }
97 |
98 | if let created = tweet?.createdTime {
99 | self.tweetCreatedTime?.text = created.shortTimeAgoSinceNow
100 | } else {
101 | tweetCreatedTime?.text = nil
102 | }
103 |
104 | tweetUserName.text = tweet?.user.name
105 | if let id = tweet?.user.screenName {
106 | tweetUserID.text = "@" + id
107 | }
108 | if (tweet?.inReplyToStatusID) != nil {
109 | replyImage.image = UIImage(named: "reply_true")
110 | } else {
111 | replyImage.image = UIImage(named: "reply_false")
112 | }
113 |
114 |
115 | let retweetedTweet = tweet?.retweeted ?? false
116 | let retweetedRetweet = retweet?.retweeted ?? false
117 | let retweeted = (retweetedTweet || retweetedRetweet)
118 |
119 | if retweeted {
120 | retweeterProfileImage?.removeFromSuperview()
121 | retweetImage.image = UIImage(named: "retweet_true")
122 | retweetCount.text = tweet?.retweetCount.shortExpression
123 | } else {
124 | retweetImage.image = UIImage(named: "retweet_false")
125 |
126 | if let retweet = retweet {
127 |
128 | retweetCount.text = retweet.user.name
129 | retweeterProfileImage?.kf.setImage(with: retweet.user.profileImageURL)
130 | retweeterProfileImage?.cutToRound(radius: 8)
131 |
132 | } else {
133 | retweeterProfileImage?.removeFromSuperview()
134 | retweetCount.text = tweet?.retweetCount.shortExpression
135 | }
136 | }
137 |
138 | likeCount.text = (tweet?.favoriteCount ?? 0).shortExpression
139 |
140 |
141 | let likedTweet = tweet?.favorited ?? false
142 | let likedRetweet = retweet?.favorited ?? false
143 | let liked = (likedTweet || likedRetweet)
144 |
145 | if liked {
146 | likeImage.image = UIImage(named: "like_true")
147 | } else {
148 | likeImage.image = UIImage(named: "like_false")
149 | }
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/TweeBox/TweetURL.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TweetURL.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/27.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | class TweetURL: TweetEntity {
13 |
14 | var url: URL?
15 | // The t.co URL that was extracted from the Tweet text
16 | var displayURL: URL?
17 | var expandedURL: URL?
18 | // The resolved URL
19 |
20 | override init(with json: JSON) {
21 |
22 | url = URL(string: json["url"].stringValue)
23 | displayURL = URL(string: json["display_url"].stringValue)
24 | expandedURL = URL(string: json["expanded_url"].stringValue)
25 |
26 | super.init(with: json)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/TweeBox/TweetVideoInfo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TweetVideoInfo.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/7.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | struct TweetVideoInfo {
13 |
14 | public var aspectRatio: [Int]
15 | // Contains information about aspect ratio.
16 | // Typical values are [4, 3] or [16, 9].
17 | // This field is present only when there is a video in the payload.
18 |
19 | public var duration: Int?
20 |
21 | public var variants: [TweetVideoVariant]?
22 |
23 | init(with json: JSON) {
24 | aspectRatio = json["aspect_ratio"].arrayValue.map { $0.intValue }
25 | duration = json["duration_millis"].int
26 | variants = json["variants"].arrayValue.map { TweetVideoVariant(with: $0) }
27 | }
28 |
29 | struct TweetVideoVariant {
30 |
31 | public var bitrate: String
32 | public var contentType: String
33 | public var url: URL?
34 |
35 | init(with json: JSON) {
36 | bitrate = json["bitrate"].stringValue
37 | contentType = json["content_type"].stringValue
38 | url = URL(string: json["url"].stringValue)
39 |
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/TweeBox/TweetWithPicAndTextTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TweetWithPicAndTextTableViewCell.swift
3 | // SmashTag
4 | //
5 | // Created by 4faramita on 2017/7/5.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import YYText
11 |
12 |
13 | class TweetWithPicAndTextTableViewCell: TweetWithPicTableViewCell {
14 |
15 | @IBOutlet weak var tweetTextContent: YYLabel!
16 |
17 | override func updateUI() {
18 |
19 | super.updateUI()
20 |
21 | if let tweet = tweet {
22 | tweetTextContent?.attributedText = TwitterAttributedContent(tweet).attributedString
23 | }
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/TweeBox/TweetWithPicAndTextUsingButtonTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TweetWithPicAndTextUsingButtonTableViewCell.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/10.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Kingfisher
11 | import SKPhotoBrowser
12 |
13 | class TweetWithPicAndTextUsingButtonTableViewCell: TweetTableViewCell {
14 |
15 | @IBOutlet weak var tweetPicContent: UIImageView!
16 |
17 | @IBOutlet weak var secondPic: UIImageView!
18 |
19 | @IBOutlet weak var thirdPic: UIImageView!
20 |
21 | @IBOutlet weak var fourthPic: UIImageView!
22 |
23 | private func setPic(at position: Int, of total: Int) {
24 |
25 | let media = tweet!.entities!.media!
26 |
27 | let pics = [tweetPicContent, secondPic, thirdPic, fourthPic] // pointer or copy?
28 |
29 | var aspect: CGFloat {
30 | if (total == 2) || (total == 3 && position == 0) {
31 | return Constants.thinAspect
32 | } else {
33 | return Constants.normalAspect
34 | }
35 | }
36 |
37 | let pic = media[position]
38 | let tweetPicURL = pic.mediaURL
39 |
40 | let picWidth: CGFloat
41 | let picHeight: CGFloat
42 | let cutPoint = CGPoint(x: 0.5, y: 0.5)
43 | // means cut from middle out
44 |
45 | let actualHeight = CGFloat(pic.sizes.small.h)
46 | let actualWidth = CGFloat(pic.sizes.small.w)
47 |
48 | if actualHeight / actualWidth >= aspect {
49 | // too long
50 | picWidth = actualWidth
51 | picHeight = picWidth * aspect
52 | } else {
53 | // too wide
54 | picHeight = actualHeight
55 | picWidth = picHeight / aspect
56 | }
57 |
58 | // Kingfisher
59 | let placeholder = UIImage(named: "picPlaceholder")!.kf.image(withRoundRadius: Constants.picCornerRadius, fit: CGSize(width: picWidth, height: picHeight))
60 |
61 | let processor = CroppingImageProcessor(size: CGSize(width: picWidth, height: picHeight), anchor: cutPoint) >> RoundCornerImageProcessor(cornerRadius: Constants.picCornerRadius)
62 |
63 | if let picView = pics[position] {
64 | // picView.kf.indicatorType = .activity
65 | picView.kf.setImage(
66 | with: tweetPicURL,
67 | placeholder: placeholder,
68 | options: [.transition(.fade(Constants.picFadeInDuration)), .processor(processor)]
69 | )
70 | }
71 | }
72 |
73 | override func updateUI() {
74 |
75 | super.updateUI()
76 |
77 | if let total = tweet?.entities?.media?.count {
78 | for i in 0..= 2 ? 1 : 0
38 | case (true, false): // down left
39 | mediaIndex = totalMediaNum == 4 ? 2 : 0
40 | case (false, false): // down right
41 | mediaIndex = ((totalMediaNum == 4) ? 3 : ((totalMediaNum == 3) ? 2 : ((totalMediaNum == 2) ? 1 : 0)))
42 | }
43 |
44 |
45 | guard let section = section, let row = row, let mediaIndex = mediaIndex else { return }
46 | delegate?.imageTapped(section: section, row: row, mediaIndex: mediaIndex, media: (tweet?.entities?.media)!)
47 | }
48 |
49 |
50 | @IBOutlet weak var tweetPicContent: UIImageView!
51 |
52 | @IBOutlet weak var secondPic: UIImageView!
53 |
54 | @IBOutlet weak var thirdPic: UIImageView!
55 |
56 | @IBOutlet weak var fourthPic: UIImageView!
57 |
58 | private func setPic(at position: Int, of total: Int) {
59 |
60 | totalMediaNum = total
61 |
62 | let media = tweet!.entities!.media!
63 |
64 | images = [tweetPicContent, secondPic, thirdPic, fourthPic]
65 |
66 | var ratio: CGFloat {
67 | if (total == 2) || (total == 3 && position == 0) {
68 | return Constants.thinAspectRatio
69 | } else {
70 | return Constants.normalAspectRatio
71 | }
72 | }
73 |
74 | let pic = media[position]
75 | let tweetPicURL = pic.mediaURL
76 |
77 | if pic.type != "photo" {
78 |
79 | let visualEffectView = VisualEffectView(frame: tweetPicContent.bounds)
80 | visualEffectView.blurRadius = 2
81 | visualEffectView.colorTint = .white
82 | visualEffectView.colorTintAlpha = 0.2
83 | tweetPicContent.addSubview(visualEffectView)
84 |
85 | let play = UIImageView(image: UIImage(named: "play_video"))
86 | tweetPicContent.addSubview(play)
87 | play.snp.makeConstraints { (make) in
88 | make.center.equalTo(tweetPicContent)
89 | make.height.equalTo(32)
90 | make.width.equalTo(32)
91 | }
92 |
93 | if pic.type == "animated_gif" {
94 | let gif = UIImageView(image: UIImage(named: "play_gif"))
95 | tweetPicContent.addSubview(gif)
96 | gif.snp.makeConstraints { (make) in
97 | make.trailing.equalTo(tweetPicContent).offset(-10)
98 | make.bottom.equalTo(tweetPicContent)
99 | make.height.equalTo(32)
100 | make.width.equalTo(32)
101 | }
102 | }
103 | }
104 |
105 | let cutSize = pic.getCutSize(with: ratio, at: .small)
106 | let cutPoint = CGPoint(x: 0.5, y: 0.5)
107 | // means cut from middle out
108 |
109 | // Kingfisher
110 | let placeholder = UIImage(named: "picPlaceholder")!.kf.image(withRoundRadius: Constants.picCornerRadius, fit: cutSize)
111 |
112 | let processor = CroppingImageProcessor(size: cutSize, anchor: cutPoint)
113 |
114 | if let picView = images[position] {
115 | // picView.kf.indicatorType = .activity
116 | picView.kf.setImage(
117 | with: tweetPicURL,
118 | placeholder: placeholder,
119 | options: [
120 | .transition(.fade(Constants.picFadeInDuration)),
121 | .processor(processor)
122 | ]
123 | )
124 | picView.layer.borderWidth = 1
125 | picView.layer.borderColor = UIColor.white.cgColor
126 | picView.cutToRound(radius: Constants.picCornerRadius)
127 |
128 |
129 | // tap to segue
130 | let ptap = UITapGestureRecognizer(
131 | target: self,
132 | action: #selector(imageTapped(byReactingTo:))
133 | )
134 | ptap.numberOfTapsRequired = 1
135 | picView.addGestureRecognizer(ptap)
136 | picView.isUserInteractionEnabled = true
137 | }
138 | }
139 |
140 | override func updateUI() {
141 |
142 | super.updateUI()
143 |
144 | if let total = tweet?.entities?.media?.count {
145 | for i in 0.. Void) {
49 |
50 | if Constants.selfID != "-1" {
51 |
52 | if fetchOlder, nextCursor != "0" {
53 | params.cursor = nextCursor
54 | }
55 |
56 | if !fetchOlder, previousCursor != "0" {
57 | params.cursor = previousCursor
58 | }
59 |
60 |
61 | let client = RESTfulClient(resource: resourceURL, params: params.getParams())
62 |
63 | print(">>> params >> \(params.getParams())")
64 |
65 | client.getData() { data in
66 | if let data = data {
67 | let json = JSON(data: data)
68 |
69 | self.nextCursor = json["next_cursor_str"].stringValue
70 | self.previousCursor = json["previous_cursor_str"].stringValue
71 |
72 | for (_, userJSON) in json["users"] {
73 | if userJSON.null == nil {
74 | let user = TwitterUser(with: userJSON)
75 | self.userList.append(user) // mem cycle?
76 | }
77 | }
78 |
79 | if self.fetchOlder, let tailID = self.tailID {
80 | for index in stride(from: self.userList.endIndex - 1, through: self.userList.startIndex, by: -1) {
81 | if self.userList[index].id == tailID {
82 | // self.userList = self.userList[(index + 1)..<(self.userList.endIndex)]
83 | self.userList = Array(self.userList.suffix(self.userList.endIndex - index - 1))
84 | break
85 | }
86 | }
87 | }
88 |
89 | if !(self.fetchOlder), let headID = self.headID {
90 | for index in self.userList.startIndex.. [String: Any] {
23 |
24 | var params = [String: String]()
25 |
26 | params["user_id"] = userID
27 |
28 | params["cursor"] = cursor
29 |
30 | params["count"] = count
31 |
32 | params["skip_status"] = skipStatus ? "true" : "false"
33 |
34 | params["include_user_entities"] = includeUserEntities ? "true" : "false"
35 |
36 | return params
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/TweeBox/UserListRetrieverProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserListRetrieverProtocol.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/26.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol UserListRetrieverProtocol {
12 |
13 | func fetchData(_ handler: @escaping (String, String, [TwitterUser]) -> Void)
14 | }
15 |
--------------------------------------------------------------------------------
/TweeBox/UserParams.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserParams.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/20.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class UserParams: ParamsProtocol {
12 |
13 | public var userID: String?
14 |
15 | public var screenName: String?
16 |
17 | public var includeEntities = true
18 |
19 | init(userID: String?, screenName: String?) {
20 | self.userID = userID
21 | self.screenName = screenName
22 | }
23 |
24 |
25 | public func getParams() -> [String: Any] {
26 |
27 | var params = [String: String]()
28 |
29 | guard (userID != nil || screenName != nil) else {
30 | return params
31 | }
32 |
33 | if userID != nil {
34 | params["user_id"] = userID
35 | } else {
36 | params["screen_name"] = screenName
37 | }
38 |
39 | params["include_entities"] = includeEntities.description
40 |
41 | return params
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/TweeBox/UserProfileViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserProfileViewController.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/10.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import TwitterKit
11 |
12 | class UserProfileViewController: UIViewController {
13 |
14 | public var userID: String?
15 |
16 | @IBOutlet weak var profilePic: UIImageView!
17 | @IBOutlet weak var nameTextLabel: UILabel!
18 | @IBOutlet weak var screenNameTextLabel: UILabel!
19 |
20 | override func viewDidLoad() {
21 | profilePic.image = UIImage(named: "picPlaceholder")
22 | nameTextLabel.text = "我的阿波罗鸡盒"
23 | screenNameTextLabel.text = "@4faramita"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/TweeBox/UserTimelineParams.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserTimelineParams.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/7/31.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class UserTimelineParams: TimelineParams {
12 |
13 | public var userID: String?
14 |
15 | // screenName
16 |
17 | init(of userID: String, sinceID: String? = nil, maxID: String? = nil, excludeReplies: Bool = false, includeRetweets: Bool = true) {
18 |
19 | self.userID = userID
20 |
21 | super.init(excludeReplies: nil, includeRetweets: nil)
22 | resourceURL = ResourceURL.statuses_user_timeline
23 | }
24 |
25 | override public func getParams() -> [String: Any] {
26 | var params = super.getParams()
27 |
28 | if userID != nil {
29 | params["user_id"] = userID
30 | }
31 |
32 | return params
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/TweeBox/UsersSearchParams.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UsersSearchParams.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/26.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class UsersSearchParams: ParamsProtocol {
12 |
13 | public var query: String
14 |
15 | public var page: Int {
16 | // Specifies the page of results to retrieve.
17 | return Int(Int(Constants.tweetLimitPerRefresh)! / 20)
18 | }
19 |
20 | public var count = 20
21 | // The number of potential user results to retrieve per page.
22 | // This value has a maximum of 20.
23 |
24 | public var includeEntities = true
25 |
26 |
27 | init(query: String) {
28 | self.query = query
29 | }
30 |
31 | func getParams() -> [String : Any] {
32 |
33 | var params = [String: String]()
34 |
35 | params["q"] = query
36 |
37 | params["page"] = "\(page)"
38 |
39 | params["count"] = "\(count)"
40 |
41 | params["include_entities"] = includeEntities.description
42 |
43 | return params
44 |
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/TweeBox/VideoViewerViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // VideoViewerViewController.swift
3 | // TweeBox
4 | //
5 | // Created by 4faramita on 2017/8/13.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import BMPlayer
11 |
12 | class VideoViewerViewController: PannableViewController {
13 |
14 | private var isInLandscapeMode: Bool!
15 |
16 | public var tweetMedia: TweetMedia! {
17 | didSet {
18 | // BMPlayerConf.enableBrightnessGestures = false
19 | // BMPlayerConf.enableVolumeGestures = false
20 |
21 | let player = BMPlayer()
22 | player.center = view.center
23 | view.addSubview(player)
24 | player.snp.makeConstraints { (make) in
25 | // make.centerY.equalTo(self.view.snp.centerY)
26 | // make.left.right.equalTo(self.view)
27 | make.center.equalTo(self.view)
28 | make.leading.greaterThanOrEqualTo(0)
29 | make.trailing.greaterThanOrEqualTo(0)
30 | make.top.greaterThanOrEqualTo(0)
31 | make.bottom.greaterThanOrEqualTo(0)
32 | let aspectRatio = CGFloat((tweetMedia.videoInfo?.aspectRatio[1])!) / CGFloat((tweetMedia.videoInfo?.aspectRatio[0])!)
33 | make.height.equalTo(player.snp.width).multipliedBy(aspectRatio).priority(750)
34 | }
35 | player.backBlock = { [unowned self] (wtf) in
36 | let _ = self.presentingViewController?.dismiss(animated: true)
37 | }
38 |
39 |
40 | let asset = BMPlayerResource(url: (tweetMedia.videoInfo?.variants?[0].url)!, name: tweetMedia.extAltText ?? "")
41 | player.setVideo(resource: asset)
42 | }
43 | }
44 |
45 | // override var prefersStatusBarHidden: Bool {
46 | // return true
47 | // }
48 |
49 | // override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
50 | // return .slide
51 | // }
52 |
53 | override func viewWillAppear(_ animated: Bool) {
54 | let size: CGSize = UIScreen.main.bounds.size
55 | if size.width / size.height > 1 {
56 | isInLandscapeMode = true
57 | } else {
58 | isInLandscapeMode = false
59 | }
60 | }
61 |
62 | override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
63 | if (size.width / size.height > 1) {
64 | isInLandscapeMode = true
65 | } else {
66 | isInLandscapeMode = false
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/TweeBoxTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/TweeBoxTests/TweeBoxTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TweeBoxTests.swift
3 | // TweeBoxTests
4 | //
5 | // Created by 4faramita on 2017/7/27.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import TweeBox
11 |
12 | class TweeBoxTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testExample() {
25 | // This is an example of a functional test case.
26 | // Use XCTAssert and related functions to verify your tests produce the correct results.
27 | }
28 |
29 | func testPerformanceExample() {
30 | // This is an example of a performance test case.
31 | self.measure {
32 | // Put the code you want to measure the time of here.
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/TweeBoxUITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/TweeBoxUITests/TweeBoxUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TweeBoxUITests.swift
3 | // TweeBoxUITests
4 | //
5 | // Created by 4faramita on 2017/7/27.
6 | // Copyright © 2017年 4faramita. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class TweeBoxUITests: XCTestCase {
12 |
13 | override func setUp() {
14 | super.setUp()
15 |
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 |
18 | // In UI tests it is usually best to stop immediately when a failure occurs.
19 | continueAfterFailure = false
20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
21 | XCUIApplication().launch()
22 |
23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
24 | }
25 |
26 | override func tearDown() {
27 | // Put teardown code here. This method is called after the invocation of each test method in the class.
28 | super.tearDown()
29 | }
30 |
31 | func testExample() {
32 | // Use recording to get started writing UI tests.
33 | // Use XCTAssert and related functions to verify your tests produce the correct results.
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------