diff --git a/elpha-ios.xcodeproj/project.pbxproj b/elpha-ios.xcodeproj/project.pbxproj index 47926ac..c798b51 100644 --- a/elpha-ios.xcodeproj/project.pbxproj +++ b/elpha-ios.xcodeproj/project.pbxproj @@ -11,12 +11,10 @@ 15131EF2216D8D570092B252 /* StatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15131EF1216D8D570092B252 /* StatusView.swift */; }; 15131EF4216DB8B90092B252 /* AccountTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15131EF3216DB8B90092B252 /* AccountTableViewController.swift */; }; 15131EF6216DBA820092B252 /* AccountNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15131EF5216DBA820092B252 /* AccountNavigationController.swift */; }; - 1517A6142182B87E00B33735 /* AttachmentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1517A6132182B87E00B33735 /* AttachmentView.xib */; }; 151AD4D9216899AD00F07403 /* AlamofireImage.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 1517EA842159D72200DE80D6 /* AlamofireImage.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 151AD4DD216899E000F07403 /* OAuthSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15A79B13215B438C007A326E /* OAuthSwift.framework */; }; - 151AD4DE216899E000F07403 /* OAuthSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 15A79B13215B438C007A326E /* OAuthSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 151AD4E621689A0F00F07403 /* Alamofire.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 157405C3215890BC00EEAAEB /* Alamofire.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 152734D22186DC74003DB3C8 /* TimelinesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 152734D12186DC74003DB3C8 /* TimelinesViewController.swift */; }; + 1539509121894A38009BA6E7 /* AlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1539509021894A38009BA6E7 /* AlertManager.swift */; }; 156FF015217289380074D9CA /* AccountTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 156FF014217289380074D9CA /* AccountTableViewCell.swift */; }; 156FF0312174797E0074D9CA /* StatusTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 156FF0302174797E0074D9CA /* StatusTableViewController.swift */; }; 156FF04F2175CDBC0074D9CA /* MainStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 156FF04E2175CDBC0074D9CA /* MainStatusTableViewCell.swift */; }; @@ -47,8 +45,8 @@ 15A79B2E215C63B6007A326E /* AlamofireImage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1517EA842159D72200DE80D6 /* AlamofireImage.framework */; }; 15A79B43215EB959007A326E /* CoreDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15A79B42215EB959007A326E /* CoreDataManager.swift */; }; 15BB72AB2171A8D4002F1FA4 /* TimelinesTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15BB72AA2171A8D4002F1FA4 /* TimelinesTableViewCell.swift */; }; - 15C91A02216AB2D600D97DC3 /* NewStatusesView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 15C91A01216AB2D600D97DC3 /* NewStatusesView.xib */; }; - 15C91A04216AB32500D97DC3 /* NewStatusesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15C91A03216AB32500D97DC3 /* NewStatusesView.swift */; }; + 15C91A02216AB2D600D97DC3 /* AlertView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 15C91A01216AB2D600D97DC3 /* AlertView.xib */; }; + 15C91A04216AB32500D97DC3 /* AlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15C91A03216AB32500D97DC3 /* AlertView.swift */; }; 15F9981721629965009E58DA /* TimelineTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15F9981621629965009E58DA /* TimelineTableViewController.swift */; }; 15F998352162C0E8009E58DA /* MastodonDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15F998342162C0E8009E58DA /* MastodonDataManager.swift */; }; /* End PBXBuildFile section */ @@ -117,13 +115,6 @@ remoteGlobalIDString = 4C9043761AABBFC5001B4E60; remoteInfo = "AlamofireImage iOS"; }; - 151AD4DF216899E000F07403 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 15A79B08215B438C007A326E /* OAuthSwift.xcodeproj */; - proxyType = 1; - remoteGlobalIDString = F43502791A6791B200038A29; - remoteInfo = OAuthSwift; - }; 151AD4E721689A0F00F07403 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 157405B7215890BC00EEAAEB /* Alamofire.xcodeproj */; @@ -180,55 +171,6 @@ remoteGlobalIDString = E4202FE01B667AA100C997FB; remoteInfo = "Alamofire watchOS"; }; - 15A79B12215B438C007A326E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 15A79B08215B438C007A326E /* OAuthSwift.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = F435027A1A6791B200038A29; - remoteInfo = OAuthSwift; - }; - 15A79B14215B438C007A326E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 15A79B08215B438C007A326E /* OAuthSwift.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = C48B28021AFA598D00C7DEF6; - remoteInfo = OAuthSwiftOSX; - }; - 15A79B16215B438C007A326E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 15A79B08215B438C007A326E /* OAuthSwift.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = C4B6EE1E1BF74CE300443596; - remoteInfo = OAuthSwiftTVOS; - }; - 15A79B18215B438C007A326E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 15A79B08215B438C007A326E /* OAuthSwift.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = C40890C01C11B37000E3146A; - remoteInfo = OAuthSwiftWatchOS; - }; - 15A79B1A215B438C007A326E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 15A79B08215B438C007A326E /* OAuthSwift.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = F451E3C5195B8CD80051434C; - remoteInfo = OAuthSwiftDemo; - }; - 15A79B1C215B438C007A326E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 15A79B08215B438C007A326E /* OAuthSwift.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = C49FD5241AFB5DF500791E1A; - remoteInfo = OAuthSwiftOSXDemo; - }; - 15A79B1E215B438C007A326E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 15A79B08215B438C007A326E /* OAuthSwift.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = C4D50DF71BFB693F0053B624; - remoteInfo = OAuthSwiftTests; - }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -238,7 +180,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 151AD4DE216899E000F07403 /* OAuthSwift.framework in Embed Frameworks */, 151AD4D9216899AD00F07403 /* AlamofireImage.framework in Embed Frameworks */, 151AD4E621689A0F00F07403 /* Alamofire.framework in Embed Frameworks */, ); @@ -252,9 +193,9 @@ 15131EF1216D8D570092B252 /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = ""; }; 15131EF3216DB8B90092B252 /* AccountTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountTableViewController.swift; sourceTree = ""; }; 15131EF5216DBA820092B252 /* AccountNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountNavigationController.swift; sourceTree = ""; }; - 1517A6132182B87E00B33735 /* AttachmentView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AttachmentView.xib; sourceTree = ""; }; 1517EA6F2159D72200DE80D6 /* AlamofireImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = AlamofireImage.xcodeproj; path = Frameworks/AlamofireImage/AlamofireImage.xcodeproj; sourceTree = ""; }; 152734D12186DC74003DB3C8 /* TimelinesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinesViewController.swift; sourceTree = ""; }; + 1539509021894A38009BA6E7 /* AlertManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertManager.swift; sourceTree = ""; }; 156FF014217289380074D9CA /* AccountTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountTableViewCell.swift; sourceTree = ""; }; 156FF0302174797E0074D9CA /* StatusTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewController.swift; sourceTree = ""; }; 156FF04E2175CDBC0074D9CA /* MainStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainStatusTableViewCell.swift; sourceTree = ""; }; @@ -284,11 +225,10 @@ 15960E7D21329FED00C38CE9 /* AuthenticateViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticateViewController.swift; sourceTree = ""; }; 15960E812136668500C38CE9 /* TimelinesNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinesNavigationController.swift; sourceTree = ""; }; 15960E83213774FC00C38CE9 /* InstancesTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesTableViewController.swift; sourceTree = ""; }; - 15A79B08215B438C007A326E /* OAuthSwift.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = OAuthSwift.xcodeproj; path = Frameworks/OAuthSwift/OAuthSwift.xcodeproj; sourceTree = ""; }; 15A79B42215EB959007A326E /* CoreDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataManager.swift; sourceTree = ""; }; 15BB72AA2171A8D4002F1FA4 /* TimelinesTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinesTableViewCell.swift; sourceTree = ""; }; - 15C91A01216AB2D600D97DC3 /* NewStatusesView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NewStatusesView.xib; sourceTree = ""; }; - 15C91A03216AB32500D97DC3 /* NewStatusesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewStatusesView.swift; sourceTree = ""; }; + 15C91A01216AB2D600D97DC3 /* AlertView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AlertView.xib; sourceTree = ""; }; + 15C91A03216AB32500D97DC3 /* AlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertView.swift; sourceTree = ""; }; 15F9981621629965009E58DA /* TimelineTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTableViewController.swift; sourceTree = ""; }; 15F998342162C0E8009E58DA /* MastodonDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonDataManager.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -299,7 +239,6 @@ buildActionMask = 2147483647; files = ( 15A79B2E215C63B6007A326E /* AlamofireImage.framework in Frameworks */, - 151AD4DD216899E000F07403 /* OAuthSwift.framework in Frameworks */, 157405D1215890D700EEAAEB /* Alamofire.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -334,6 +273,7 @@ 151AD4AC2166DD0200F07403 /* Managers */ = { isa = PBXGroup; children = ( + 1539509021894A38009BA6E7 /* AlertManager.swift */, 1574148C2169AD0100C841BD /* AttachmentsManager.swift */, 15960E7B213272CD00C38CE9 /* AuthenticationManager.swift */, 15A79B42215EB959007A326E /* CoreDataManager.swift */, @@ -346,9 +286,8 @@ 151AD4AD2166DD1B00F07403 /* Reusable Views */ = { isa = PBXGroup; children = ( - 1517A6132182B87E00B33735 /* AttachmentView.xib */, - 15C91A03216AB32500D97DC3 /* NewStatusesView.swift */, - 15C91A01216AB2D600D97DC3 /* NewStatusesView.xib */, + 15C91A03216AB32500D97DC3 /* AlertView.swift */, + 15C91A01216AB2D600D97DC3 /* AlertView.xib */, 15131EF1216D8D570092B252 /* StatusView.swift */, 15131ED7216D8C680092B252 /* StatusView.xib */, ); @@ -412,7 +351,6 @@ 157405AF2151A5DA00EEAAEB /* README.md */, 157405B7215890BC00EEAAEB /* Alamofire.xcodeproj */, 1517EA6F2159D72200DE80D6 /* AlamofireImage.xcodeproj */, - 15A79B08215B438C007A326E /* OAuthSwift.xcodeproj */, 15960E59213145E100C38CE9 /* elpha-ios */, 15960E58213145E100C38CE9 /* Products */, 157405D0215890D700EEAAEB /* Frameworks */, @@ -467,26 +405,12 @@ 15960E792132387A00C38CE9 /* MainTabBarController.swift */, 156FF0302174797E0074D9CA /* StatusTableViewController.swift */, 15960E812136668500C38CE9 /* TimelinesNavigationController.swift */, - 15F9981621629965009E58DA /* TimelineTableViewController.swift */, 152734D12186DC74003DB3C8 /* TimelinesViewController.swift */, + 15F9981621629965009E58DA /* TimelineTableViewController.swift */, ); name = "View Controllers"; sourceTree = ""; }; - 15A79B09215B438C007A326E /* Products */ = { - isa = PBXGroup; - children = ( - 15A79B13215B438C007A326E /* OAuthSwift.framework */, - 15A79B15215B438C007A326E /* OAuthSwift.framework */, - 15A79B17215B438C007A326E /* OAuthSwift.framework */, - 15A79B19215B438C007A326E /* OAuthSwift.framework */, - 15A79B1B215B438C007A326E /* OAuthSwiftDemo.app */, - 15A79B1D215B438C007A326E /* OAuthSwiftOSXDemo.app */, - 15A79B1F215B438C007A326E /* OAuthSwiftTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -503,7 +427,6 @@ ); dependencies = ( 151AD4DB216899AD00F07403 /* PBXTargetDependency */, - 151AD4E0216899E000F07403 /* PBXTargetDependency */, 151AD4E821689A0F00F07403 /* PBXTargetDependency */, ); name = "elpha-ios"; @@ -547,10 +470,6 @@ ProductGroup = 1517EA702159D72200DE80D6 /* Products */; ProjectRef = 1517EA6F2159D72200DE80D6 /* AlamofireImage.xcodeproj */; }, - { - ProductGroup = 15A79B09215B438C007A326E /* Products */; - ProjectRef = 15A79B08215B438C007A326E /* OAuthSwift.xcodeproj */; - }, ); projectRoot = ""; targets = ( @@ -665,55 +584,6 @@ remoteRef = 157405CE215890BC00EEAAEB /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 15A79B13215B438C007A326E /* OAuthSwift.framework */ = { - isa = PBXReferenceProxy; - fileType = wrapper.framework; - path = OAuthSwift.framework; - remoteRef = 15A79B12215B438C007A326E /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 15A79B15215B438C007A326E /* OAuthSwift.framework */ = { - isa = PBXReferenceProxy; - fileType = wrapper.framework; - path = OAuthSwift.framework; - remoteRef = 15A79B14215B438C007A326E /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 15A79B17215B438C007A326E /* OAuthSwift.framework */ = { - isa = PBXReferenceProxy; - fileType = wrapper.framework; - path = OAuthSwift.framework; - remoteRef = 15A79B16215B438C007A326E /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 15A79B19215B438C007A326E /* OAuthSwift.framework */ = { - isa = PBXReferenceProxy; - fileType = wrapper.framework; - path = OAuthSwift.framework; - remoteRef = 15A79B18215B438C007A326E /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 15A79B1B215B438C007A326E /* OAuthSwiftDemo.app */ = { - isa = PBXReferenceProxy; - fileType = wrapper.application; - path = OAuthSwiftDemo.app; - remoteRef = 15A79B1A215B438C007A326E /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 15A79B1D215B438C007A326E /* OAuthSwiftOSXDemo.app */ = { - isa = PBXReferenceProxy; - fileType = wrapper.application; - path = OAuthSwiftOSXDemo.app; - remoteRef = 15A79B1C215B438C007A326E /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 15A79B1F215B438C007A326E /* OAuthSwiftTests.xctest */ = { - isa = PBXReferenceProxy; - fileType = wrapper.cfbundle; - path = OAuthSwiftTests.xctest; - remoteRef = 15A79B1E215B438C007A326E /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ @@ -722,12 +592,11 @@ buildActionMask = 2147483647; files = ( 15960E67213145E200C38CE9 /* LaunchScreen.storyboard in Resources */, - 15C91A02216AB2D600D97DC3 /* NewStatusesView.xib in Resources */, + 15C91A02216AB2D600D97DC3 /* AlertView.xib in Resources */, 15960E64213145E200C38CE9 /* Assets.xcassets in Resources */, 157405B12151A5DA00EEAAEB /* README.md in Resources */, 15960E62213145E100C38CE9 /* Main.storyboard in Resources */, 15131ED8216D8C680092B252 /* StatusView.xib in Resources */, - 1517A6142182B87E00B33735 /* AttachmentView.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -738,6 +607,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1539509121894A38009BA6E7 /* AlertManager.swift in Sources */, 159048AF214F5015004F4014 /* InstancesTableViewCell.swift in Sources */, 15960E84213774FC00C38CE9 /* InstancesTableViewController.swift in Sources */, 15960E7021321FA500C38CE9 /* Elpha.xcdatamodeld in Sources */, @@ -755,7 +625,7 @@ 156FF07221779C650074D9CA /* InstanceRequest.swift in Sources */, 15960E7721322C6F00C38CE9 /* Configuration.swift in Sources */, 156FF07021779C570074D9CA /* MastodonAPI.swift in Sources */, - 15C91A04216AB32500D97DC3 /* NewStatusesView.swift in Sources */, + 15C91A04216AB32500D97DC3 /* AlertView.swift in Sources */, 15131EF6216DBA820092B252 /* AccountNavigationController.swift in Sources */, 15960E7521322BF800C38CE9 /* KeychainWrapper.swift in Sources */, 157405A82150588A00EEAAEB /* InstanceViewController.swift in Sources */, @@ -780,11 +650,6 @@ name = "AlamofireImage iOS"; targetProxy = 151AD4DA216899AD00F07403 /* PBXContainerItemProxy */; }; - 151AD4E0216899E000F07403 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = OAuthSwift; - targetProxy = 151AD4DF216899E000F07403 /* PBXContainerItemProxy */; - }; 151AD4E821689A0F00F07403 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = "Alamofire iOS"; diff --git a/elpha-ios/AccountTableViewController.swift b/elpha-ios/AccountTableViewController.swift index 0e52328..0036a92 100644 --- a/elpha-ios/AccountTableViewController.swift +++ b/elpha-ios/AccountTableViewController.swift @@ -359,10 +359,16 @@ extension AccountTableViewController: StatusViewDelegate { } } - func loadMoreTapped(pagination: PaginationItem) { - fetchStatuses(withPagination: pagination) { error in - if error != nil { - print("\(String(describing: error))") + func loadMoreTapped(status: StatusMO, direction: PaginationDirection) { + if let markers = status.markers { + markers.forEach { marker in + if marker.context == self.currentPaginationContext && marker.item.direction == direction { + fetchStatuses(withPagination: marker.item) { error in + if error != nil { + print("\(String(describing: error))") + } + } + } } } } diff --git a/elpha-ios/AlertManager.swift b/elpha-ios/AlertManager.swift new file mode 100644 index 0000000..31f39d5 --- /dev/null +++ b/elpha-ios/AlertManager.swift @@ -0,0 +1,113 @@ +// +// AlertManager.swift +// elpha-ios +// +// Created by Dwayne Harris on 10/30/18. +// Copyright © 2018 Elpha. All rights reserved. +// + +import UIKit + +enum AlertCategory { + case normal, newStatuses, noConnection, error +} + +class Alert { + let category: AlertCategory + let message: String + + init(category: AlertCategory, message: String) { + self.category = category + self.message = message + } +} + +class AlertManager { + static let shared = AlertManager() + static let alertStartPosition: CGFloat = 100.0 + + var done: Bool = true + var alertView: AlertView? = nil + var bottomLayoutConstraint: NSLayoutConstraint? = nil + var alerts: [Alert] = [] + + func createAlertView() { + guard alertView == nil else { + return + } + + alertView = AlertView() + alertView!.translatesAutoresizingMaskIntoConstraints = false + + let blurEffectView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffect.Style.prominent)) + blurEffectView.translatesAutoresizingMaskIntoConstraints = false + blurEffectView.contentView.addSubview(alertView!) + blurEffectView.layer.cornerRadius = 10 + blurEffectView.layer.masksToBounds = true + + NSLayoutConstraint.activate([ + alertView!.leadingAnchor.constraint(equalTo: blurEffectView.leadingAnchor), + alertView!.trailingAnchor.constraint(equalTo: blurEffectView.trailingAnchor), + alertView!.topAnchor.constraint(equalTo: blurEffectView.topAnchor), + alertView!.bottomAnchor.constraint(equalTo: blurEffectView.bottomAnchor), + ]) + + let view = UIApplication.shared.keyWindow! + + view.addSubview(blurEffectView) + + bottomLayoutConstraint = blurEffectView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: AlertManager.alertStartPosition) + + NSLayoutConstraint.activate([ + blurEffectView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), + blurEffectView.widthAnchor.constraint(equalToConstant: 200), + blurEffectView.heightAnchor.constraint(equalToConstant: 50), + bottomLayoutConstraint!, + ]) + } + + private func start() { + guard done else { + return + } + + done = false + + UIView.animate(withDuration: 1.0, animations: { + self.bottomLayoutConstraint?.constant = AlertManager.alertStartPosition + }) { _ in + self.alertView?.setAlert(self.alerts.first!) + + UIView.animate(withDuration: 1.0, animations: { + self.bottomLayoutConstraint?.constant = 20 + }) { _ in + _ = self.alerts.remove(at: 0) + + DispatchQueue.main.asyncAfter(deadline: .now() + 5) { + UIView.animate(withDuration: 1.0, animations: { + self.bottomLayoutConstraint?.constant = AlertManager.alertStartPosition + }) { _ in + self.done = true + + if self.alerts.count > 0 { + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + self.start() + } + } + } + } + } + } + } + + func show(message: String, category: AlertCategory = .normal) { + if let alert = alerts.last { + if alert.category == category { + return + } + } + + alerts.append(Alert(category: category, message: message)) + start() + } +} diff --git a/elpha-ios/NewStatusesView.swift b/elpha-ios/AlertView.swift similarity index 50% rename from elpha-ios/NewStatusesView.swift rename to elpha-ios/AlertView.swift index 0391cf7..599a563 100644 --- a/elpha-ios/NewStatusesView.swift +++ b/elpha-ios/AlertView.swift @@ -8,9 +8,12 @@ import UIKit -@IBDesignable class NewStatusesView: UIView { +@IBDesignable class AlertView: UIView { @IBOutlet var contentView: UIView! - @IBOutlet var mainLabel: UILabel! + @IBOutlet var alertImageView: UIImageView! + @IBOutlet var alertLabel: UILabel! + + var alert: Alert? = nil override init(frame: CGRect) { super.init(frame: frame) @@ -23,18 +26,20 @@ import UIKit } private func setup() { - Bundle.main.loadNibNamed("NewStatusesView", owner: self, options: nil) + Bundle.main.loadNibNamed("AlertView", owner: self, options: nil) addSubview(contentView) contentView.frame = self.bounds contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight] } - public func setCount(_ count: Int) { - switch count { - case 1: - mainLabel.text = "1 New Toot" - default: - mainLabel.text = "\(count) New Toots" + public func setAlert(_ alert: Alert) { + switch alert.category { + case .newStatuses: + alertImageView.image = UIImage(named: "Comments") + alertLabel.text = alert.message + default: + alertImageView.image = UIImage(named: "Alert") + alertLabel.text = alert.message } } } diff --git a/elpha-ios/NewStatusesView.xib b/elpha-ios/AlertView.xib similarity index 83% rename from elpha-ios/NewStatusesView.xib rename to elpha-ios/AlertView.xib index decd370..865003e 100644 --- a/elpha-ios/NewStatusesView.xib +++ b/elpha-ios/AlertView.xib @@ -1,18 +1,19 @@ - + - + - + + + - @@ -20,13 +21,13 @@ - - - - diff --git a/elpha-ios/AppDelegate.swift b/elpha-ios/AppDelegate.swift index 4157add..50f58e5 100644 --- a/elpha-ios/AppDelegate.swift +++ b/elpha-ios/AppDelegate.swift @@ -7,7 +7,6 @@ // import CoreData -import OAuthSwift import UIKit @UIApplicationMain @@ -20,7 +19,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { if (url.host == "oauth") { - OAuthSwift.handle(url: url) + AuthenticationManager.handle(url: url) } return true diff --git a/elpha-ios/AttachmentView.xib b/elpha-ios/AttachmentView.xib deleted file mode 100644 index ea70694..0000000 --- a/elpha-ios/AttachmentView.xib +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/elpha-ios/AttachmentsManager.swift b/elpha-ios/AttachmentsManager.swift index b1659e3..ee2e705 100644 --- a/elpha-ios/AttachmentsManager.swift +++ b/elpha-ios/AttachmentsManager.swift @@ -133,13 +133,6 @@ class AttachmentsManager { view.addSubview(imageView) imageView.translatesAutoresizingMaskIntoConstraints = false - - /* - NSLayoutConstraint.activate([ - imageView.widthAnchor.constraint(equalToConstant: halfWidth), - imageView.heightAnchor.constraint(equalToConstant: halfWidth), - ]) - */ } NSLayoutConstraint.activate([ diff --git a/elpha-ios/AuthenticateViewController.swift b/elpha-ios/AuthenticateViewController.swift index fc72652..2dafdc3 100644 --- a/elpha-ios/AuthenticateViewController.swift +++ b/elpha-ios/AuthenticateViewController.swift @@ -8,90 +8,60 @@ import Alamofire import CoreData -import OAuthSwift import UIKit +import SafariServices -class AuthenticateViewController: UIViewController { +class AuthenticateViewController: UIViewController, SFSafariViewControllerDelegate, AuthenticationDelegate { @IBOutlet var signInButton: UIButton! @IBOutlet var instanceTextField: UITextField! - var oauthswift: OAuthSwift? - let defaultInstanceName = "mastodon.social" - override func viewDidLoad() { super.viewDidLoad() signInButton.layer.cornerRadius = 10 signInButton.clipsToBounds = true - randomInstanceName { instance in - self.instanceTextField.attributedPlaceholder = NSAttributedString(string: instance, attributes: [NSAttributedString.Key.foregroundColor: UIColor.init(red: 0.9, green: 0.9, blue: 0.9, alpha: 1)]) - } + instanceTextField.attributedPlaceholder = NSAttributedString(string: "mastodon.social", attributes: [NSAttributedString.Key.foregroundColor: UIColor.init(red: 0.9, green: 0.9, blue: 0.9, alpha: 1)]) } override open var shouldAutorotate: Bool { return false } - func randomInstanceName(completion: @escaping (String) -> Void) { - let requestURL = "\(Config.instancesServiceURL)\(Config.instancesServiceRandomEndpoint)?count=1" - let headers: HTTPHeaders = ["Authorization": "Bearer \(Config.instancesServiceSecret)"] - - Alamofire.request(requestURL, headers: headers).validate().responseJSON { response in - switch response.result { - case .success(let value): - guard let result = value as? [String: Any], - let instances = result["instances"] as? [Any], - let instance = instances.first as? [String: Any], - let name = instance["name"] as? String else { - completion(self.defaultInstanceName) - return - } - - completion(name) - case .failure(let error): - print("\(error)") - completion(self.defaultInstanceName) - } - } + func authenticationSuccess() { + self.dismiss(animated: true) + } + + func authenticationFailure(error: Error?) { + AlertManager.shared.show(message: error?.localizedDescription ?? "Authentication error", category: .error) } func authorize(client: ClientMO) { - let oauthswift = OAuth2Swift( - consumerKey: client.clientID!, - consumerSecret: client.clientSecret!, - authorizeUrl: "https://\(client.host!)/oauth/authorize", - accessTokenUrl: "https://\(client.host!)/oauth/token", - responseType: "code" - ) + AuthenticationManager.authenticationState = NSUUID().uuidString + AuthenticationManager.authenticationClient = client + AuthenticationManager.authenticationDelegate = self - oauthswift.authorizeURLHandler = OAuthSwiftOpenURLExternally.sharedInstance - self.oauthswift = oauthswift + let parameters = [ + "client_id": client.clientID!, + "response_type": "code", + "redirect_uri": Config.clientRedirectURI, + "scope": "read write follow".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!, + ] - let _ = oauthswift.authorize( - withCallbackURL: URL(string: "elpha://oauth")!, - scope: "read write follow", - state: NSUUID().uuidString, - success: { credential, _, _ in - let token = credential.oauthToken - let serverURL = URL(string: "https://\(client.host!)") - - MastodonAPI.currentUser(token: token, serverURL: serverURL!) { data, error in - guard let data = data, error == nil else { - print("\(String(describing: error))") - return - } - - let account = MastodonDataManager.upsertAccount(data) - _ = AuthenticationManager.saveSession(client: client, account: account!, token: credential.oauthToken) - - self.dismiss(animated: true) - } - }, - failure: { error in - print("\(error)") - } - ) + let parameterString = parameters.map { "\($0.key)=\($0.value)" }.joined(separator: "&") + let authorizeURL = URL(string: "https://\(client.host!)/oauth/authorize?\(parameterString)") + + let controller = SFSafariViewController(url: authorizeURL!) + controller.delegate = self + controller.dismissButtonStyle = .cancel + controller.preferredBarTintColor = UIColor(named: "Secondary") + controller.preferredControlTintColor = UIColor(named: "Text") + + present(controller, animated: true) + } + + func safariViewControllerDidFinish(_ controller: SFSafariViewController) { + controller.dismiss(animated: true) } @IBAction func signIn(_ sender: Any) { @@ -145,7 +115,7 @@ class AuthenticateViewController: UIViewController { } } } catch { - print("\(error)") + AlertManager.shared.show(message: error.localizedDescription, category: .error) } } } diff --git a/elpha-ios/AuthenticationManager.swift b/elpha-ios/AuthenticationManager.swift index 234d506..97a50e8 100644 --- a/elpha-ios/AuthenticationManager.swift +++ b/elpha-ios/AuthenticationManager.swift @@ -6,10 +6,20 @@ // Copyright © 2018 Elpha. All rights reserved. // +import Alamofire import CoreData import UIKit +protocol AuthenticationDelegate { + func authenticationSuccess() + func authenticationFailure(error: Error?) +} + class AuthenticationManager { + static var authenticationState: String? = nil + static var authenticationClient: ClientMO? = nil + static var authenticationDelegate: AuthenticationDelegate? = nil + static var sessions: [SessionMO] { get { do { @@ -64,4 +74,70 @@ class AuthenticationManager { return session } + + static func handle(url: URL) { + guard let state = authenticationState, let client = authenticationClient else { + return + } + + if let components = URLComponents(url: url, resolvingAgainstBaseURL: false), let queryItems = components.queryItems { + var code: String? + + for item in queryItems { + switch item.name.lowercased() { + case "code": + code = item.value + case "state": + if item.value != state { + if let delegate = self.authenticationDelegate { + delegate.authenticationFailure(error: nil) + } + + return + } + default: + continue + } + } + + let parameters: Parameters = [ + "client_id": client.clientID!, + "client_secret": client.clientSecret!, + "grant_type": "authorization_code", + "code": code!, + "redirect_uri": Config.clientRedirectURI, + ] + + Alamofire.request( + "https://\(client.host!)/oauth/token", + method: .post, + parameters: parameters, + encoding: URLEncoding.default + ).validate().responseJSON { response in + switch response.result { + case .success(let data): + let serverURL = URL(string: "https://\(client.host!)") + if let json = data as? [String: Any], let token = json["access_token"] as? String { + MastodonAPI.currentUser(token: token, serverURL: serverURL!) { data, error in + guard let data = data, error == nil else { + AlertManager.shared.show(message: error!.localizedDescription, category: .error) + return + } + + let account = MastodonDataManager.upsertAccount(data) + _ = AuthenticationManager.saveSession(client: client, account: account!, token: token) + + if let delegate = self.authenticationDelegate { + delegate.authenticationSuccess() + } + } + } + case .failure(let error): + if let delegate = self.authenticationDelegate { + delegate.authenticationFailure(error: error) + } + } + } + } + } } diff --git a/elpha-ios/Base.lproj/Main.storyboard b/elpha-ios/Base.lproj/Main.storyboard index ca40383..e71e235 100644 --- a/elpha-ios/Base.lproj/Main.storyboard +++ b/elpha-ios/Base.lproj/Main.storyboard @@ -786,7 +786,6 @@ - @@ -1266,9 +1265,6 @@ - - - diff --git a/elpha-ios/Configuration.swift b/elpha-ios/Configuration.swift index 0c96578..263982c 100644 --- a/elpha-ios/Configuration.swift +++ b/elpha-ios/Configuration.swift @@ -12,6 +12,7 @@ struct Config { static let clientName = "xyz.elpha.elpha-ios" static let clientDisplayName = "Elpha" static let clientWebsite = "https://elpha.xyz" + static let clientRedirectURI = "elpha://oauth" static let instancesServiceURL = "https://instances.social" static let instancesServiceListEndpoint = "/api/1.0/instances/list" diff --git a/elpha-ios/MastodonAPI.swift b/elpha-ios/MastodonAPI.swift index 36e0355..d3679f7 100644 --- a/elpha-ios/MastodonAPI.swift +++ b/elpha-ios/MastodonAPI.swift @@ -342,7 +342,7 @@ class MastodonAPI { let requestURL = serverURL.appendingPathComponent("api/v1/apps") let parameters: Parameters = [ "client_name": Config.clientDisplayName, - "redirect_uris": "elpha://oauth", + "redirect_uris": Config.clientRedirectURI, "scopes": "read write follow", "website": Config.clientWebsite, ] diff --git a/elpha-ios/StatusTableViewController.swift b/elpha-ios/StatusTableViewController.swift index b11be69..36ff590 100644 --- a/elpha-ios/StatusTableViewController.swift +++ b/elpha-ios/StatusTableViewController.swift @@ -317,7 +317,7 @@ extension StatusTableViewController: StatusViewDelegate { } } - func loadMoreTapped(pagination: PaginationItem) { + func loadMoreTapped(status: StatusMO, direction: PaginationDirection) { } } diff --git a/elpha-ios/StatusView.swift b/elpha-ios/StatusView.swift index e031330..f4e0ff4 100644 --- a/elpha-ios/StatusView.swift +++ b/elpha-ios/StatusView.swift @@ -14,7 +14,7 @@ protocol StatusViewDelegate { func statusTapped(status: StatusMO) func favoriteTapped(status: StatusMO) func reblogTapped(status: StatusMO) - func loadMoreTapped(pagination: PaginationItem) + func loadMoreTapped(status: StatusMO, direction: PaginationDirection) } class StatusView: UIView { @@ -93,8 +93,17 @@ class StatusView: UIView { } } + @IBAction func topLoadMoreViewTapped(_ sender: Any) { + if let delegate = delegate, let status = status { + delegate.loadMoreTapped(status: status, direction: .prev) + } + } - + @IBAction func bottomLoadMoreViewTapped(_ sender: Any) { + if let delegate = delegate, let status = status { + delegate.loadMoreTapped(status: status, direction: .next) + } + } @objc func reveal(sender: UITapGestureRecognizer) { if let status = status { diff --git a/elpha-ios/StatusView.xib b/elpha-ios/StatusView.xib index f792cdc..bf66159 100644 --- a/elpha-ios/StatusView.xib +++ b/elpha-ios/StatusView.xib @@ -75,6 +75,7 @@ + @@ -82,6 +83,9 @@ + + + @@ -456,6 +460,7 @@ + @@ -463,6 +468,9 @@ + + + @@ -516,6 +524,16 @@ + + + + + + + + + + diff --git a/elpha-ios/TimelineTableViewController.swift b/elpha-ios/TimelineTableViewController.swift index 2d2cfb1..a15e132 100644 --- a/elpha-ios/TimelineTableViewController.swift +++ b/elpha-ios/TimelineTableViewController.swift @@ -79,9 +79,10 @@ class TimelineTableViewController: UITableViewController, TimelinesViewControlle } @objc func compose() { - let alertController = UIAlertController(title: "Compose", message: "Toot", preferredStyle: .alert) - alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - present(alertController, animated: true) +// let alertController = UIAlertController(title: "Compose", message: "Toot", preferredStyle: .alert) +// alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) +// present(alertController, animated: true) + AlertManager.shared.show(message: "Test message") } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { @@ -424,44 +425,17 @@ extension TimelineTableViewController: StatusViewDelegate { } } - func loadMoreTapped(pagination: PaginationItem) { - fetchTimeline(withPagination: pagination) { error in - if error != nil { - print("\(String(describing: error))") + func loadMoreTapped(status: StatusMO, direction: PaginationDirection) { + if let markers = status.markers { + markers.forEach { marker in + if marker.context == self.currentPaginationContext && marker.item.direction == direction { + fetchTimeline(withPagination: marker.item) { error in + if error != nil { + print("\(String(describing: error))") + } + } + } } } } } - -extension TimelineTableViewController { - func newStatusView() -> NewStatusesView { - let newStatusesView = NewStatusesView() - newStatusesView.translatesAutoresizingMaskIntoConstraints = false - - let blurEffectView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffect.Style.prominent)) - blurEffectView.translatesAutoresizingMaskIntoConstraints = false - blurEffectView.contentView.addSubview(newStatusesView) - blurEffectView.layer.cornerRadius = 10 - blurEffectView.layer.masksToBounds = true - - NSLayoutConstraint.activate([ - newStatusesView.leadingAnchor.constraint(equalTo: blurEffectView.leadingAnchor), - newStatusesView.trailingAnchor.constraint(equalTo: blurEffectView.trailingAnchor), - newStatusesView.topAnchor.constraint(equalTo: blurEffectView.topAnchor), - newStatusesView.bottomAnchor.constraint(equalTo: blurEffectView.bottomAnchor), - ]) - - view.addSubview(blurEffectView) - - let bottomLayoutConstraint = blurEffectView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 100) - - NSLayoutConstraint.activate([ - blurEffectView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), - blurEffectView.widthAnchor.constraint(equalToConstant: 200), - blurEffectView.heightAnchor.constraint(equalToConstant: 50), - bottomLayoutConstraint, - ]) - - return newStatusesView - } -}