From 086ddce121496743620179291a9047851fad8887 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Tue, 13 Apr 2021 20:49:42 -0700 Subject: [PATCH 001/224] Remove Core Data stack --- Sluggo.xcodeproj/project.pbxproj | 17 -------- Sluggo/AppDelegate.swift | 46 -------------------- Sluggo/SceneDelegate.swift | 2 - Sluggo/Sluggo.xcdatamodeld/.xccurrentversion | 5 +-- 4 files changed, 1 insertion(+), 69 deletions(-) diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 25cd389..74a0617 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -11,7 +11,6 @@ 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */; }; 7D9963AA261EF3A4002338E7 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A9261EF3A4002338E7 /* ViewController.swift */; }; 7D9963AD261EF3A4002338E7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7D9963AB261EF3A4002338E7 /* Main.storyboard */; }; - 7D9963B0261EF3A4002338E7 /* Sluggo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963AE261EF3A4002338E7 /* Sluggo.xcdatamodeld */; }; 7D9963B2261EF3A5002338E7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7D9963B1261EF3A5002338E7 /* Assets.xcassets */; }; 7D9963B5261EF3A5002338E7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7D9963B3261EF3A5002338E7 /* LaunchScreen.storyboard */; }; 7D9963C0261EF3A5002338E7 /* SluggoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963BF261EF3A5002338E7 /* SluggoTests.swift */; }; @@ -41,7 +40,6 @@ 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 7D9963A9261EF3A4002338E7 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 7D9963AC261EF3A4002338E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 7D9963AF261EF3A4002338E7 /* Sluggo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Sluggo.xcdatamodel; sourceTree = ""; }; 7D9963B1261EF3A5002338E7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 7D9963B4261EF3A5002338E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 7D9963B6261EF3A5002338E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -108,7 +106,6 @@ 7D9963B1261EF3A5002338E7 /* Assets.xcassets */, 7D9963B3261EF3A5002338E7 /* LaunchScreen.storyboard */, 7D9963B6261EF3A5002338E7 /* Info.plist */, - 7D9963AE261EF3A4002338E7 /* Sluggo.xcdatamodeld */, ); path = Sluggo; sourceTree = ""; @@ -261,7 +258,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7D9963B0261EF3A4002338E7 /* Sluggo.xcdatamodeld in Sources */, 7D9963AA261EF3A4002338E7 /* ViewController.swift in Sources */, 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */, 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */, @@ -599,19 +595,6 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ - -/* Begin XCVersionGroup section */ - 7D9963AE261EF3A4002338E7 /* Sluggo.xcdatamodeld */ = { - isa = XCVersionGroup; - children = ( - 7D9963AF261EF3A4002338E7 /* Sluggo.xcdatamodel */, - ); - currentVersion = 7D9963AF261EF3A4002338E7 /* Sluggo.xcdatamodel */; - path = Sluggo.xcdatamodeld; - sourceTree = ""; - versionGroupType = wrapper.xcdatamodel; - }; -/* End XCVersionGroup section */ }; rootObject = 7D99639A261EF3A4002338E7 /* Project object */; } diff --git a/Sluggo/AppDelegate.swift b/Sluggo/AppDelegate.swift index 4b394ae..31d7f59 100644 --- a/Sluggo/AppDelegate.swift +++ b/Sluggo/AppDelegate.swift @@ -6,7 +6,6 @@ // import UIKit -import CoreData @main class AppDelegate: UIResponder, UIApplicationDelegate { @@ -32,50 +31,5 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } - // MARK: - Core Data stack - - lazy var persistentContainer: NSPersistentContainer = { - /* - The persistent container for the application. This implementation - creates and returns a container, having loaded the store for the - application to it. This property is optional since there are legitimate - error conditions that could cause the creation of the store to fail. - */ - let container = NSPersistentContainer(name: "Sluggo") - container.loadPersistentStores(completionHandler: { (storeDescription, error) in - if let error = error as NSError? { - // Replace this implementation with code to handle the error appropriately. - // 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. - - /* - Typical reasons for an error here include: - * The parent directory does not exist, cannot be created, or disallows writing. - * The persistent store is not accessible, due to permissions or data protection when the device is locked. - * The device is out of space. - * The store could not be migrated to the current model version. - Check the error message to determine what the actual problem was. - */ - fatalError("Unresolved error \(error), \(error.userInfo)") - } - }) - return container - }() - - // MARK: - Core Data Saving support - - func saveContext () { - let context = persistentContainer.viewContext - if context.hasChanges { - do { - try context.save() - } catch { - // Replace this implementation with code to handle the error appropriately. - // 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. - let nserror = error as NSError - fatalError("Unresolved error \(nserror), \(nserror.userInfo)") - } - } - } - } diff --git a/Sluggo/SceneDelegate.swift b/Sluggo/SceneDelegate.swift index 045323e..21e4809 100644 --- a/Sluggo/SceneDelegate.swift +++ b/Sluggo/SceneDelegate.swift @@ -46,8 +46,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // Use this method to save data, release shared resources, and store enough scene-specific state information // to restore the scene back to its current state. - // Save changes in the application's managed object context when the application transitions to the background. - (UIApplication.shared.delegate as? AppDelegate)?.saveContext() } diff --git a/Sluggo/Sluggo.xcdatamodeld/.xccurrentversion b/Sluggo/Sluggo.xcdatamodeld/.xccurrentversion index 7927900..0c67376 100644 --- a/Sluggo/Sluggo.xcdatamodeld/.xccurrentversion +++ b/Sluggo/Sluggo.xcdatamodeld/.xccurrentversion @@ -1,8 +1,5 @@ - - _XCCurrentVersionName - Sluggo.xcdatamodel - + From 1fc556e716c77f3bc009728e4f32cc149e2ea783 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Thu, 15 Apr 2021 18:35:25 -0700 Subject: [PATCH 002/224] Added codable models for Assignment --- Sluggo.xcodeproj/project.pbxproj | 24 ++++++++++++++++++++++++ Sluggo/Models/Member.swift | 22 ++++++++++++++++++++++ Sluggo/Models/Team.swift | 21 +++++++++++++++++++++ Sluggo/Models/Token.swift | 13 +++++++++++++ Sluggo/Models/User.swift | 17 +++++++++++++++++ Sluggo/ViewController.swift | 7 +++++++ 6 files changed, 104 insertions(+) create mode 100644 Sluggo/Models/Member.swift create mode 100644 Sluggo/Models/Team.swift create mode 100644 Sluggo/Models/Token.swift create mode 100644 Sluggo/Models/User.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 74a0617..23a6f96 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 2F2247AC262920AC006C6EE6 /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2247AB262920AC006C6EE6 /* Token.swift */; }; + 2F3AD7A32627C86A00B61A97 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A22627C86A00B61A97 /* User.swift */; }; + 2F3AD7A82627C89B00B61A97 /* Member.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A72627C89B00B61A97 /* Member.swift */; }; + 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7AC2627C8EF00B61A97 /* Team.swift */; }; 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */; }; 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */; }; 7D9963AA261EF3A4002338E7 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A9261EF3A4002338E7 /* ViewController.swift */; }; @@ -35,6 +39,10 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 2F2247AB262920AC006C6EE6 /* Token.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = ""; }; + 2F3AD7A22627C86A00B61A97 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; + 2F3AD7A72627C89B00B61A97 /* Member.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Member.swift; sourceTree = ""; }; + 2F3AD7AC2627C8EF00B61A97 /* Team.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Team.swift; sourceTree = ""; }; 7D9963A2261EF3A4002338E7 /* Sluggo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sluggo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -76,6 +84,17 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 2F81E6E62627E85000B6D86D /* Models */ = { + isa = PBXGroup; + children = ( + 2F3AD7A22627C86A00B61A97 /* User.swift */, + 2F3AD7A72627C89B00B61A97 /* Member.swift */, + 2F3AD7AC2627C8EF00B61A97 /* Team.swift */, + 2F2247AB262920AC006C6EE6 /* Token.swift */, + ); + path = Models; + sourceTree = ""; + }; 7D996399261EF3A4002338E7 = { isa = PBXGroup; children = ( @@ -99,6 +118,7 @@ 7D9963A4261EF3A4002338E7 /* Sluggo */ = { isa = PBXGroup; children = ( + 2F81E6E62627E85000B6D86D /* Models */, 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */, 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */, 7D9963A9261EF3A4002338E7 /* ViewController.swift */, @@ -258,9 +278,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */, + 2F2247AC262920AC006C6EE6 /* Token.swift in Sources */, + 2F3AD7A32627C86A00B61A97 /* User.swift in Sources */, 7D9963AA261EF3A4002338E7 /* ViewController.swift in Sources */, 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */, 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */, + 2F3AD7A82627C89B00B61A97 /* Member.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sluggo/Models/Member.swift b/Sluggo/Models/Member.swift new file mode 100644 index 0000000..0424c30 --- /dev/null +++ b/Sluggo/Models/Member.swift @@ -0,0 +1,22 @@ +// +// Member.swift +// Sluggo +// +// Created by Andrew Gavgavian on 4/14/21. +// + +import Foundation + + +struct MemberRecord: Codable { + var id: String + var owner: UserRecord + var team_id: Int + var object_uuid: UUID + var role: String + var bio: String? + var created: Date + var activated: Date? + var deactivated: Date? + +} diff --git a/Sluggo/Models/Team.swift b/Sluggo/Models/Team.swift new file mode 100644 index 0000000..a5412ef --- /dev/null +++ b/Sluggo/Models/Team.swift @@ -0,0 +1,21 @@ +// +// Team.swift +// Sluggo +// +// Created by Andrew Gavgavian on 4/14/21. +// + +import Foundation + + +struct TeamRecord: Codable { + var id: Int + var name: String + var description: String + var object_uuid: UUID + var ticket_head: Int + var created: Date + var activated: Date? + var deactivated: Date? + +} diff --git a/Sluggo/Models/Token.swift b/Sluggo/Models/Token.swift new file mode 100644 index 0000000..53d5696 --- /dev/null +++ b/Sluggo/Models/Token.swift @@ -0,0 +1,13 @@ +// +// Token.swift +// Sluggo +// +// Created by Andrew Gavgavian on 4/15/21. +// + +import Foundation + + +struct TokenRecord: Codable { + var key: String +} diff --git a/Sluggo/Models/User.swift b/Sluggo/Models/User.swift new file mode 100644 index 0000000..5210120 --- /dev/null +++ b/Sluggo/Models/User.swift @@ -0,0 +1,17 @@ +// +// User.swift +// Sluggo +// +// Created by Andrew Gavgavian on 4/14/21. +// + +import Foundation + + +struct UserRecord: Codable { + var id: Int + var email: String + var first_name: String? + var last_name: String? + var username: String +} diff --git a/Sluggo/ViewController.swift b/Sluggo/ViewController.swift index 2430c7a..e787b68 100644 --- a/Sluggo/ViewController.swift +++ b/Sluggo/ViewController.swift @@ -9,8 +9,15 @@ import UIKit class ViewController: UIViewController { + + let decoder = JSONDecoder() + + override func viewDidLoad() { super.viewDidLoad() + + // Set the proper date Decoding Strategy to be ISO8601 (YYYY-MM-dd'T'HH:mm:ss'Z) NOTE NO MILLISECONDS SUPPORTED + decoder.dateDecodingStrategy = .iso8601 // Do any additional setup after loading the view. } From 748343517b602b8b3d449ebaba84c6011096e615 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Thu, 15 Apr 2021 20:53:46 -0700 Subject: [PATCH 003/224] Added general structure sans sidebar --- Sluggo.xcodeproj/project.pbxproj | 12 ++++++ Sluggo/Base.lproj/Main.storyboard | 60 +++++++++++++++++++++++------- Sluggo/Info.plist | 2 + Sluggo/Storyboards/Home.storyboard | 34 +++++++++++++++++ 4 files changed, 94 insertions(+), 14 deletions(-) create mode 100644 Sluggo/Storyboards/Home.storyboard diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 23a6f96..c45f26e 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ 7D9963B5261EF3A5002338E7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7D9963B3261EF3A5002338E7 /* LaunchScreen.storyboard */; }; 7D9963C0261EF3A5002338E7 /* SluggoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963BF261EF3A5002338E7 /* SluggoTests.swift */; }; 7D9963CB261EF3A5002338E7 /* SluggoUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963CA261EF3A5002338E7 /* SluggoUITests.swift */; }; + 7DC2A21726293F360002CEE6 /* Home.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DC2A21626293F360002CEE6 /* Home.storyboard */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -57,6 +58,7 @@ 7D9963C6261EF3A5002338E7 /* SluggoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SluggoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 7D9963CA261EF3A5002338E7 /* SluggoUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SluggoUITests.swift; sourceTree = ""; }; 7D9963CC261EF3A5002338E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 7DC2A21626293F360002CEE6 /* Home.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Home.storyboard; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -118,6 +120,7 @@ 7D9963A4261EF3A4002338E7 /* Sluggo */ = { isa = PBXGroup; children = ( + 7DC2A21526293F1E0002CEE6 /* Storyboards */, 2F81E6E62627E85000B6D86D /* Models */, 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */, 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */, @@ -148,6 +151,14 @@ path = SluggoUITests; sourceTree = ""; }; + 7DC2A21526293F1E0002CEE6 /* Storyboards */ = { + isa = PBXGroup; + children = ( + 7DC2A21626293F360002CEE6 /* Home.storyboard */, + ); + path = Storyboards; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -254,6 +265,7 @@ 7D9963B5261EF3A5002338E7 /* LaunchScreen.storyboard in Resources */, 7D9963B2261EF3A5002338E7 /* Assets.xcassets in Resources */, 7D9963AD261EF3A4002338E7 /* Main.storyboard in Resources */, + 7DC2A21726293F360002CEE6 /* Home.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sluggo/Base.lproj/Main.storyboard b/Sluggo/Base.lproj/Main.storyboard index 25a7638..8bc683d 100644 --- a/Sluggo/Base.lproj/Main.storyboard +++ b/Sluggo/Base.lproj/Main.storyboard @@ -1,24 +1,56 @@ - + + - - + - - + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sluggo/Info.plist b/Sluggo/Info.plist index e45a487..bc9d90c 100644 --- a/Sluggo/Info.plist +++ b/Sluggo/Info.plist @@ -55,6 +55,8 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIUserInterfaceStyle + Light UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait diff --git a/Sluggo/Storyboards/Home.storyboard b/Sluggo/Storyboards/Home.storyboard new file mode 100644 index 0000000..e0fca65 --- /dev/null +++ b/Sluggo/Storyboards/Home.storyboard @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 67a402bb81dcb86246b4d6663ae2a9e26aca6e6a Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Thu, 15 Apr 2021 21:19:53 -0700 Subject: [PATCH 004/224] Sidebar skeleton --- Sluggo.xcodeproj/project.pbxproj | 16 +++ Sluggo/Base.lproj/Main.storyboard | 58 ++++++++- Sluggo/Storyboards/Sidebar.storyboard | 112 ++++++++++++++++++ .../SluggoSidebarTableViewController.swift | 87 ++++++++++++++ 4 files changed, 271 insertions(+), 2 deletions(-) create mode 100644 Sluggo/Storyboards/Sidebar.storyboard create mode 100644 Sluggo/View Controllers/SluggoSidebarTableViewController.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index c45f26e..28522d1 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -20,6 +20,8 @@ 7D9963C0261EF3A5002338E7 /* SluggoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963BF261EF3A5002338E7 /* SluggoTests.swift */; }; 7D9963CB261EF3A5002338E7 /* SluggoUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963CA261EF3A5002338E7 /* SluggoUITests.swift */; }; 7DC2A21726293F360002CEE6 /* Home.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DC2A21626293F360002CEE6 /* Home.storyboard */; }; + 7DC2A21C262943A70002CEE6 /* Sidebar.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DC2A21B262943A70002CEE6 /* Sidebar.storyboard */; }; + 7DC2A2222629447D0002CEE6 /* SluggoSidebarTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC2A2212629447D0002CEE6 /* SluggoSidebarTableViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -59,6 +61,8 @@ 7D9963CA261EF3A5002338E7 /* SluggoUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SluggoUITests.swift; sourceTree = ""; }; 7D9963CC261EF3A5002338E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7DC2A21626293F360002CEE6 /* Home.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Home.storyboard; sourceTree = ""; }; + 7DC2A21B262943A70002CEE6 /* Sidebar.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Sidebar.storyboard; sourceTree = ""; }; + 7DC2A2212629447D0002CEE6 /* SluggoSidebarTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SluggoSidebarTableViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -120,6 +124,7 @@ 7D9963A4261EF3A4002338E7 /* Sluggo */ = { isa = PBXGroup; children = ( + 7DC2A2202629442B0002CEE6 /* View Controllers */, 7DC2A21526293F1E0002CEE6 /* Storyboards */, 2F81E6E62627E85000B6D86D /* Models */, 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */, @@ -155,10 +160,19 @@ isa = PBXGroup; children = ( 7DC2A21626293F360002CEE6 /* Home.storyboard */, + 7DC2A21B262943A70002CEE6 /* Sidebar.storyboard */, ); path = Storyboards; sourceTree = ""; }; + 7DC2A2202629442B0002CEE6 /* View Controllers */ = { + isa = PBXGroup; + children = ( + 7DC2A2212629447D0002CEE6 /* SluggoSidebarTableViewController.swift */, + ); + path = "View Controllers"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -266,6 +280,7 @@ 7D9963B2261EF3A5002338E7 /* Assets.xcassets in Resources */, 7D9963AD261EF3A4002338E7 /* Main.storyboard in Resources */, 7DC2A21726293F360002CEE6 /* Home.storyboard in Resources */, + 7DC2A21C262943A70002CEE6 /* Sidebar.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -293,6 +308,7 @@ 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */, 2F2247AC262920AC006C6EE6 /* Token.swift in Sources */, 2F3AD7A32627C86A00B61A97 /* User.swift in Sources */, + 7DC2A2222629447D0002CEE6 /* SluggoSidebarTableViewController.swift in Sources */, 7D9963AA261EF3A4002338E7 /* ViewController.swift in Sources */, 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */, 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */, diff --git a/Sluggo/Base.lproj/Main.storyboard b/Sluggo/Base.lproj/Main.storyboard index 8bc683d..5da1a78 100644 --- a/Sluggo/Base.lproj/Main.storyboard +++ b/Sluggo/Base.lproj/Main.storyboard @@ -1,11 +1,60 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -21,7 +70,7 @@ - + @@ -53,4 +102,9 @@ + + + + + diff --git a/Sluggo/Storyboards/Sidebar.storyboard b/Sluggo/Storyboards/Sidebar.storyboard new file mode 100644 index 0000000..b247918 --- /dev/null +++ b/Sluggo/Storyboards/Sidebar.storyboard @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sluggo/View Controllers/SluggoSidebarTableViewController.swift b/Sluggo/View Controllers/SluggoSidebarTableViewController.swift new file mode 100644 index 0000000..689c92a --- /dev/null +++ b/Sluggo/View Controllers/SluggoSidebarTableViewController.swift @@ -0,0 +1,87 @@ +// +// SluggoSidebarTableViewController.swift +// Sluggo +// +// Created by Isaac Trimble-Pederson on 4/15/21. +// + +import UIKit + +class SluggoSidebarTableViewController: UITableViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + // Uncomment the following line to preserve selection between presentations + // self.clearsSelectionOnViewWillAppear = false + + // Uncomment the following line to display an Edit button in the navigation bar for this view controller. + // self.navigationItem.rightBarButtonItem = self.editButtonItem + } + + // MARK: - Table view data source + + override func numberOfSections(in tableView: UITableView) -> Int { + // #warning Incomplete implementation, return the number of sections + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + // #warning Incomplete implementation, return the number of rows + return 1 + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "SLGSidebarCell", for: indexPath) + + cell.textLabel?.text = "Sluggo Team!" + + return cell + } + + /* + // Override to support conditional editing of the table view. + override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { + // Return false if you do not want the specified item to be editable. + return true + } + */ + + /* + // Override to support editing the table view. + override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { + if editingStyle == .delete { + // Delete the row from the data source + tableView.deleteRows(at: [indexPath], with: .fade) + } else if editingStyle == .insert { + // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view + } + } + */ + + /* + // Override to support rearranging the table view. + override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) { + + } + */ + + /* + // Override to support conditional rearranging of the table view. + override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { + // Return false if you do not want the item to be re-orderable. + return true + } + */ + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + +} From 5615ceb49f160e8d5e9bb7dbe183fec60ff71f8b Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Thu, 15 Apr 2021 22:12:13 -0700 Subject: [PATCH 005/224] Added sidebar with test trigger and animations --- Sluggo.xcodeproj/project.pbxproj | 8 ++ Sluggo/Base.lproj/Main.storyboard | 2 +- Sluggo/Storyboards/Home.storyboard | 15 +++- Sluggo/Storyboards/Sidebar.storyboard | 28 ++++--- .../View Controllers/HomeViewController.swift | 33 ++++++++ ...SluggoSidebarContainerViewController.swift | 78 +++++++++++++++++++ 6 files changed, 151 insertions(+), 13 deletions(-) create mode 100644 Sluggo/View Controllers/HomeViewController.swift create mode 100644 Sluggo/View Controllers/SluggoSidebarContainerViewController.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 28522d1..1305603 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -22,6 +22,8 @@ 7DC2A21726293F360002CEE6 /* Home.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DC2A21626293F360002CEE6 /* Home.storyboard */; }; 7DC2A21C262943A70002CEE6 /* Sidebar.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DC2A21B262943A70002CEE6 /* Sidebar.storyboard */; }; 7DC2A2222629447D0002CEE6 /* SluggoSidebarTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC2A2212629447D0002CEE6 /* SluggoSidebarTableViewController.swift */; }; + 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */; }; + 7DC2A22F26294F9E0002CEE6 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC2A22E26294F9E0002CEE6 /* HomeViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -63,6 +65,8 @@ 7DC2A21626293F360002CEE6 /* Home.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Home.storyboard; sourceTree = ""; }; 7DC2A21B262943A70002CEE6 /* Sidebar.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Sidebar.storyboard; sourceTree = ""; }; 7DC2A2212629447D0002CEE6 /* SluggoSidebarTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SluggoSidebarTableViewController.swift; sourceTree = ""; }; + 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SluggoSidebarContainerViewController.swift; sourceTree = ""; }; + 7DC2A22E26294F9E0002CEE6 /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -169,6 +173,8 @@ isa = PBXGroup; children = ( 7DC2A2212629447D0002CEE6 /* SluggoSidebarTableViewController.swift */, + 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */, + 7DC2A22E26294F9E0002CEE6 /* HomeViewController.swift */, ); path = "View Controllers"; sourceTree = ""; @@ -306,9 +312,11 @@ buildActionMask = 2147483647; files = ( 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */, + 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */, 2F2247AC262920AC006C6EE6 /* Token.swift in Sources */, 2F3AD7A32627C86A00B61A97 /* User.swift in Sources */, 7DC2A2222629447D0002CEE6 /* SluggoSidebarTableViewController.swift in Sources */, + 7DC2A22F26294F9E0002CEE6 /* HomeViewController.swift in Sources */, 7D9963AA261EF3A4002338E7 /* ViewController.swift in Sources */, 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */, 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */, diff --git a/Sluggo/Base.lproj/Main.storyboard b/Sluggo/Base.lproj/Main.storyboard index 5da1a78..aaca08d 100644 --- a/Sluggo/Base.lproj/Main.storyboard +++ b/Sluggo/Base.lproj/Main.storyboard @@ -30,7 +30,7 @@ - + diff --git a/Sluggo/Storyboards/Home.storyboard b/Sluggo/Storyboards/Home.storyboard index e0fca65..f694114 100644 --- a/Sluggo/Storyboards/Home.storyboard +++ b/Sluggo/Storyboards/Home.storyboard @@ -11,12 +11,25 @@ - + + + + + + + + diff --git a/Sluggo/Storyboards/Sidebar.storyboard b/Sluggo/Storyboards/Sidebar.storyboard index b247918..8dce6ac 100644 --- a/Sluggo/Storyboards/Sidebar.storyboard +++ b/Sluggo/Storyboards/Sidebar.storyboard @@ -13,19 +13,19 @@ - + - + - + - + - - + + @@ -58,8 +58,8 @@ - - + + @@ -69,7 +69,7 @@ - + @@ -80,6 +80,12 @@ + + + + + + @@ -91,7 +97,7 @@ - + diff --git a/Sluggo/View Controllers/HomeViewController.swift b/Sluggo/View Controllers/HomeViewController.swift new file mode 100644 index 0000000..900c8ac --- /dev/null +++ b/Sluggo/View Controllers/HomeViewController.swift @@ -0,0 +1,33 @@ +// +// HomeViewController.swift +// Sluggo +// +// Created by Isaac Trimble-Pederson on 4/15/21. +// + +import UIKit + +class HomeViewController: UIViewController { + @IBAction func pushDaButton(_ sender: Any) { + print("We are in fact doing things") + NotificationCenter.default.post(Notification(name: .onSidebarTrigger)) + } + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + } + + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + +} diff --git a/Sluggo/View Controllers/SluggoSidebarContainerViewController.swift b/Sluggo/View Controllers/SluggoSidebarContainerViewController.swift new file mode 100644 index 0000000..4942513 --- /dev/null +++ b/Sluggo/View Controllers/SluggoSidebarContainerViewController.swift @@ -0,0 +1,78 @@ +// +// SluggoSidebarContainerViewController.swift +// Sluggo +// +// Created by Isaac Trimble-Pederson on 4/15/21. +// + +import UIKit + +class SluggoSidebarContainerViewController: UIViewController { + // MARK: Outlets + @IBOutlet weak var backgroundView: UIView! + @IBOutlet weak var sidebarContainerView: UIView! + @IBOutlet weak var sidebarWidthConstraint: NSLayoutConstraint! + @IBOutlet weak var sidebarContainerLeadingConstraint: NSLayoutConstraint! + + // MARK: Properties + var sidebarPresenting = false + + // MARK: Computed + var sidebarLeadingConstant: CGFloat { + get { + let width = sidebarWidthConstraint.constant + return sidebarPresenting ? 0 : -1 * width; + } + } + + var backgroundOpacity: CGFloat { + get { + return sidebarPresenting ? 0.4 : 0.0; + } + } + + // MARK: Functions + @objc func triggerSidebar() { + sidebarPresenting = !sidebarPresenting + + updateSidebar() + } + + func updateSidebar() { + sidebarContainerLeadingConstraint.constant = sidebarLeadingConstant + view.isUserInteractionEnabled = sidebarPresenting + + UIView.animate(withDuration: 0.25) { + self.backgroundView.alpha = self.backgroundOpacity + self.view.layoutIfNeeded() + } + } + + // MARK: VC Overrides + override func viewDidLoad() { + super.viewDidLoad() + + updateSidebar() + + // Register for notification of sidebar changes + NotificationCenter.default.addObserver(self, selector: #selector(triggerSidebar), name: .onSidebarTrigger, object: nil) + + // Do any additional setup after loading the view. + } + + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + +} + +extension Notification.Name { + static let onSidebarTrigger = Notification.Name(rawValue: "SLGSidebarTriggerNotification") +} From 1d978f1926c6deb9ad15e63719957bc1ee42d5a3 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Thu, 15 Apr 2021 22:13:40 -0700 Subject: [PATCH 006/224] Moved Main storyboard to Storyboards folder --- Sluggo.xcodeproj/project.pbxproj | 2 +- Sluggo/{ => Storyboards}/Base.lproj/Main.storyboard | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename Sluggo/{ => Storyboards}/Base.lproj/Main.storyboard (100%) diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 1305603..41296b8 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -134,7 +134,6 @@ 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */, 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */, 7D9963A9261EF3A4002338E7 /* ViewController.swift */, - 7D9963AB261EF3A4002338E7 /* Main.storyboard */, 7D9963B1261EF3A5002338E7 /* Assets.xcassets */, 7D9963B3261EF3A5002338E7 /* LaunchScreen.storyboard */, 7D9963B6261EF3A5002338E7 /* Info.plist */, @@ -163,6 +162,7 @@ 7DC2A21526293F1E0002CEE6 /* Storyboards */ = { isa = PBXGroup; children = ( + 7D9963AB261EF3A4002338E7 /* Main.storyboard */, 7DC2A21626293F360002CEE6 /* Home.storyboard */, 7DC2A21B262943A70002CEE6 /* Sidebar.storyboard */, ); diff --git a/Sluggo/Base.lproj/Main.storyboard b/Sluggo/Storyboards/Base.lproj/Main.storyboard similarity index 100% rename from Sluggo/Base.lproj/Main.storyboard rename to Sluggo/Storyboards/Base.lproj/Main.storyboard From d6d8bdb498b5a12b83dbbc7a23906d545991ad84 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Thu, 15 Apr 2021 22:22:50 -0700 Subject: [PATCH 007/224] Remove extraneous print --- Sluggo/View Controllers/HomeViewController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sluggo/View Controllers/HomeViewController.swift b/Sluggo/View Controllers/HomeViewController.swift index 900c8ac..570e094 100644 --- a/Sluggo/View Controllers/HomeViewController.swift +++ b/Sluggo/View Controllers/HomeViewController.swift @@ -9,7 +9,6 @@ import UIKit class HomeViewController: UIViewController { @IBAction func pushDaButton(_ sender: Any) { - print("We are in fact doing things") NotificationCenter.default.post(Notification(name: .onSidebarTrigger)) } From d5a44df6fb28fcf81adae8aadbcfa9926b22349d Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Thu, 15 Apr 2021 22:44:12 -0700 Subject: [PATCH 008/224] Removed description from team --- Sluggo/Models/Team.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sluggo/Models/Team.swift b/Sluggo/Models/Team.swift index a5412ef..d948013 100644 --- a/Sluggo/Models/Team.swift +++ b/Sluggo/Models/Team.swift @@ -11,7 +11,6 @@ import Foundation struct TeamRecord: Codable { var id: Int var name: String - var description: String var object_uuid: UUID var ticket_head: Int var created: Date From b31ef7e10e6919612e628cdb470a74d9b19eddbe Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Sat, 17 Apr 2021 14:48:53 -0700 Subject: [PATCH 009/224] Attempted to fix problems and realizing this is a bad approach This should be done with a custom presentation, not with a floating view. Whoops. I'll try to investigate how to do this later. --- Sluggo/Models/SidebarData.swift | 8 +++++ .../View Controllers/RootViewController.swift | 29 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 Sluggo/Models/SidebarData.swift create mode 100644 Sluggo/View Controllers/RootViewController.swift diff --git a/Sluggo/Models/SidebarData.swift b/Sluggo/Models/SidebarData.swift new file mode 100644 index 0000000..a2a1dbc --- /dev/null +++ b/Sluggo/Models/SidebarData.swift @@ -0,0 +1,8 @@ +// +// SidebarData.swift +// Sluggo +// +// Created by Isaac Trimble-Pederson on 4/17/21. +// + +import Foundation diff --git a/Sluggo/View Controllers/RootViewController.swift b/Sluggo/View Controllers/RootViewController.swift new file mode 100644 index 0000000..bf72257 --- /dev/null +++ b/Sluggo/View Controllers/RootViewController.swift @@ -0,0 +1,29 @@ +// +// RootViewController.swift +// Sluggo +// +// Created by Isaac Trimble-Pederson on 4/17/21. +// + +import UIKit + +class RootViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + } + + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + +} From 470730f4776475d5be6517072c1d5b894ab2530b Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Sat, 17 Apr 2021 15:01:13 -0700 Subject: [PATCH 010/224] More fixes to janky sidebar --- Sluggo.xcodeproj/project.pbxproj | 8 +++++ Sluggo/Models/SidebarData.swift | 9 ++++++ Sluggo/Storyboards/Base.lproj/Main.storyboard | 16 ++++++---- Sluggo/Storyboards/Sidebar.storyboard | 13 ++++++-- .../View Controllers/HomeViewController.swift | 2 +- .../View Controllers/RootViewController.swift | 19 ++++++++++-- ...SluggoSidebarContainerViewController.swift | 31 ++++++++++++++----- 7 files changed, 80 insertions(+), 18 deletions(-) diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 41296b8..257218d 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -11,6 +11,8 @@ 2F3AD7A32627C86A00B61A97 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A22627C86A00B61A97 /* User.swift */; }; 2F3AD7A82627C89B00B61A97 /* Member.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A72627C89B00B61A97 /* Member.swift */; }; 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7AC2627C8EF00B61A97 /* Team.swift */; }; + 7D1DA6AE262B82C200E7C12D /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */; }; + 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */; }; 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */; }; 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */; }; 7D9963AA261EF3A4002338E7 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A9261EF3A4002338E7 /* ViewController.swift */; }; @@ -48,6 +50,8 @@ 2F3AD7A22627C86A00B61A97 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; 2F3AD7A72627C89B00B61A97 /* Member.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Member.swift; sourceTree = ""; }; 2F3AD7AC2627C8EF00B61A97 /* Team.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Team.swift; sourceTree = ""; }; + 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RootViewController.swift; path = "Sluggo/View Controllers/RootViewController.swift"; sourceTree = SOURCE_ROOT; }; + 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarData.swift; sourceTree = ""; }; 7D9963A2261EF3A4002338E7 /* Sluggo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sluggo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -101,6 +105,7 @@ 2F3AD7A72627C89B00B61A97 /* Member.swift */, 2F3AD7AC2627C8EF00B61A97 /* Team.swift */, 2F2247AB262920AC006C6EE6 /* Token.swift */, + 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */, ); path = Models; sourceTree = ""; @@ -173,6 +178,7 @@ isa = PBXGroup; children = ( 7DC2A2212629447D0002CEE6 /* SluggoSidebarTableViewController.swift */, + 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */, 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */, 7DC2A22E26294F9E0002CEE6 /* HomeViewController.swift */, ); @@ -311,9 +317,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */, 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */, 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */, 2F2247AC262920AC006C6EE6 /* Token.swift in Sources */, + 7D1DA6AE262B82C200E7C12D /* RootViewController.swift in Sources */, 2F3AD7A32627C86A00B61A97 /* User.swift in Sources */, 7DC2A2222629447D0002CEE6 /* SluggoSidebarTableViewController.swift in Sources */, 7DC2A22F26294F9E0002CEE6 /* HomeViewController.swift in Sources */, diff --git a/Sluggo/Models/SidebarData.swift b/Sluggo/Models/SidebarData.swift index a2a1dbc..d6ee905 100644 --- a/Sluggo/Models/SidebarData.swift +++ b/Sluggo/Models/SidebarData.swift @@ -6,3 +6,12 @@ // import Foundation + +enum SidebarStatus { + case open + case closed +} + +class Sidebar { + static let USER_INFO_KEY = "sidebar" +} diff --git a/Sluggo/Storyboards/Base.lproj/Main.storyboard b/Sluggo/Storyboards/Base.lproj/Main.storyboard index aaca08d..7d49af9 100644 --- a/Sluggo/Storyboards/Base.lproj/Main.storyboard +++ b/Sluggo/Storyboards/Base.lproj/Main.storyboard @@ -16,10 +16,10 @@ - + - + @@ -40,16 +40,20 @@ - + - + - - + + + + + + diff --git a/Sluggo/Storyboards/Sidebar.storyboard b/Sluggo/Storyboards/Sidebar.storyboard index 8dce6ac..41eec11 100644 --- a/Sluggo/Storyboards/Sidebar.storyboard +++ b/Sluggo/Storyboards/Sidebar.storyboard @@ -57,6 +57,10 @@ + + + + @@ -72,10 +76,10 @@ - + - + @@ -88,6 +92,11 @@ + + + + + diff --git a/Sluggo/View Controllers/HomeViewController.swift b/Sluggo/View Controllers/HomeViewController.swift index 570e094..4ae564c 100644 --- a/Sluggo/View Controllers/HomeViewController.swift +++ b/Sluggo/View Controllers/HomeViewController.swift @@ -9,7 +9,7 @@ import UIKit class HomeViewController: UIViewController { @IBAction func pushDaButton(_ sender: Any) { - NotificationCenter.default.post(Notification(name: .onSidebarTrigger)) + NotificationCenter.default.post(Notification(name: .onSidebarTrigger, userInfo: [Sidebar.USER_INFO_KEY : SidebarStatus.open])) } override func viewDidLoad() { diff --git a/Sluggo/View Controllers/RootViewController.swift b/Sluggo/View Controllers/RootViewController.swift index bf72257..986cb33 100644 --- a/Sluggo/View Controllers/RootViewController.swift +++ b/Sluggo/View Controllers/RootViewController.swift @@ -8,13 +8,28 @@ import UIKit class RootViewController: UIViewController { - + @IBOutlet weak var mainContainerView: UIView! // contains the main app and tab bar controller + @IBOutlet weak var sidebarContainerView: UIView! // contains the sidebar container view controller + + @objc func onSidebarNotificationRecieved(_notification: Notification) { + guard let status = _notification.userInfo?[Sidebar.USER_INFO_KEY] as? SidebarStatus else { + return; + } + + updateForSidebarStatus(status: status) + } + + func updateForSidebarStatus(status: SidebarStatus) { + sidebarContainerView.isUserInteractionEnabled = (status == .open) ? true : false + } + override func viewDidLoad() { super.viewDidLoad() + + NotificationCenter.default.addObserver(self, selector: #selector(onSidebarNotificationRecieved), name: .onSidebarTrigger, object: nil) // Do any additional setup after loading the view. } - /* // MARK: - Navigation diff --git a/Sluggo/View Controllers/SluggoSidebarContainerViewController.swift b/Sluggo/View Controllers/SluggoSidebarContainerViewController.swift index 4942513..f5e1179 100644 --- a/Sluggo/View Controllers/SluggoSidebarContainerViewController.swift +++ b/Sluggo/View Controllers/SluggoSidebarContainerViewController.swift @@ -15,35 +15,52 @@ class SluggoSidebarContainerViewController: UIViewController { @IBOutlet weak var sidebarContainerLeadingConstraint: NSLayoutConstraint! // MARK: Properties - var sidebarPresenting = false + var sidebarPresenting: SidebarStatus = .closed // MARK: Computed var sidebarLeadingConstant: CGFloat { get { let width = sidebarWidthConstraint.constant - return sidebarPresenting ? 0 : -1 * width; + return (sidebarPresenting == .open) ? 0 : -1 * width; } } var backgroundOpacity: CGFloat { get { - return sidebarPresenting ? 0.4 : 0.0; + return (sidebarPresenting == .open) ? 0.4 : 0.0; + } + } + + var foregroundOpacity: CGFloat { + get { + return (sidebarPresenting == .open) ? 1.0 : 0.0; } } // MARK: Functions - @objc func triggerSidebar() { - sidebarPresenting = !sidebarPresenting + @objc func triggerSidebar(_notification: Notification) { + guard let sidebarState = _notification.userInfo?[Sidebar.USER_INFO_KEY] as? SidebarStatus else { + print("Sidebar triggered without explicit state.") + return // TODO log this as an error + } + + sidebarPresenting = sidebarState updateSidebar() } + @IBAction func backgroundTapGestureRecognized(_ sender: UITapGestureRecognizer) { + print("RECOGNIZED") + NotificationCenter.default.post(name: .onSidebarTrigger, object: self, userInfo: [Sidebar.USER_INFO_KEY: SidebarStatus.closed]) + + } + func updateSidebar() { sidebarContainerLeadingConstraint.constant = sidebarLeadingConstant - view.isUserInteractionEnabled = sidebarPresenting UIView.animate(withDuration: 0.25) { self.backgroundView.alpha = self.backgroundOpacity + self.view.alpha = self.foregroundOpacity self.view.layoutIfNeeded() } } @@ -51,7 +68,7 @@ class SluggoSidebarContainerViewController: UIViewController { // MARK: VC Overrides override func viewDidLoad() { super.viewDidLoad() - + updateSidebar() // Register for notification of sidebar changes From 17933d2513f70ef2659739ce4e4b696cd3133ab6 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Sun, 18 Apr 2021 17:16:40 -0700 Subject: [PATCH 011/224] Working but potentially inefficient approach --- Sluggo.xcodeproj/project.pbxproj | 4 +++ Sluggo/Models/User.swift | 38 ++++++++++++++++++++++ SluggoTests/SluggoTests.swift | 54 ++++++++++++++++---------------- SluggoTests/UserTests.swift | 21 +++++++++++++ 4 files changed, 90 insertions(+), 27 deletions(-) create mode 100644 SluggoTests/UserTests.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 23a6f96..2f8ca55 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 2F3AD7A32627C86A00B61A97 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A22627C86A00B61A97 /* User.swift */; }; 2F3AD7A82627C89B00B61A97 /* Member.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A72627C89B00B61A97 /* Member.swift */; }; 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7AC2627C8EF00B61A97 /* Team.swift */; }; + 7D1354FC262D026E00D38EE0 /* UserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1354FB262D026E00D38EE0 /* UserTests.swift */; }; 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */; }; 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */; }; 7D9963AA261EF3A4002338E7 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A9261EF3A4002338E7 /* ViewController.swift */; }; @@ -43,6 +44,7 @@ 2F3AD7A22627C86A00B61A97 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; 2F3AD7A72627C89B00B61A97 /* Member.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Member.swift; sourceTree = ""; }; 2F3AD7AC2627C8EF00B61A97 /* Team.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Team.swift; sourceTree = ""; }; + 7D1354FB262D026E00D38EE0 /* UserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTests.swift; sourceTree = ""; }; 7D9963A2261EF3A4002338E7 /* Sluggo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sluggo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -135,6 +137,7 @@ children = ( 7D9963BF261EF3A5002338E7 /* SluggoTests.swift */, 7D9963C1261EF3A5002338E7 /* Info.plist */, + 7D1354FB262D026E00D38EE0 /* UserTests.swift */, ); path = SluggoTests; sourceTree = ""; @@ -292,6 +295,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7D1354FC262D026E00D38EE0 /* UserTests.swift in Sources */, 7D9963C0261EF3A5002338E7 /* SluggoTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sluggo/Models/User.swift b/Sluggo/Models/User.swift index 5210120..c3d869d 100644 --- a/Sluggo/Models/User.swift +++ b/Sluggo/Models/User.swift @@ -14,4 +14,42 @@ struct UserRecord: Codable { var first_name: String? var last_name: String? var username: String + + init(id: Int, email: String, first_name: String?, last_name: String?, username: String) { + self.id = id + self.email = email + self.first_name = first_name + self.last_name = last_name + self.username = username + } + + init?(fromJSONString: String) { // Initializer for data from JSON + // Get data + guard let jsonData = fromJSONString.data(using: .utf8) else { return nil } + + // Attempt decoding + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601 + + guard let userRecord = try? decoder.decode(UserRecord.self, from: jsonData) else { return nil } + + // Initialize using standard initializer + self.init(id: userRecord.id, email: userRecord.email, first_name: userRecord.first_name, last_name: userRecord.last_name, username: userRecord.username) + } + + var jsonString: String? { + get { + // obtain an encoder + + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = .iso8601 + encoder.outputFormatting = .prettyPrinted + + // Attempt encoding + guard let jsonData = try? encoder.encode(self) else { return nil } + + // Attempt stringifying the data, this is failable, which is fine since property is optional. + return String(data: jsonData, encoding: .utf8) + } + } } diff --git a/SluggoTests/SluggoTests.swift b/SluggoTests/SluggoTests.swift index d3b401b..2ec0e3e 100644 --- a/SluggoTests/SluggoTests.swift +++ b/SluggoTests/SluggoTests.swift @@ -4,30 +4,30 @@ // // Created by Isaac Trimble-Pederson on 4/8/21. // - -import XCTest -@testable import Sluggo - -class SluggoTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testPerformanceExample() throws { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -} +// +//import XCTest +//@testable import Sluggo +// +//class SluggoTests: XCTestCase { +// +// override func setUpWithError() throws { +// // Put setup code here. This method is called before the invocation of each test method in the class. +// } +// +// override func tearDownWithError() throws { +// // Put teardown code here. This method is called after the invocation of each test method in the class. +// } +// +// func testExample() throws { +// // This is an example of a functional test case. +// // Use XCTAssert and related functions to verify your tests produce the correct results. +// } +// +// func testPerformanceExample() throws { +// // This is an example of a performance test case. +// self.measure { +// // Put the code you want to measure the time of here. +// } +// } +// +//} diff --git a/SluggoTests/UserTests.swift b/SluggoTests/UserTests.swift new file mode 100644 index 0000000..bc9b501 --- /dev/null +++ b/SluggoTests/UserTests.swift @@ -0,0 +1,21 @@ +// +// UserTests.swift +// SluggoTests +// +// Created by Isaac Trimble-Pederson on 4/18/21. +// + +import XCTest +@testable import Sluggo + +class UserTests: XCTestCase { + let testWorkingJson = """ + { + + } + """ + + func testUserDoesDeserialize() { + + } +} From 03c6a468563e5cce2f64ce847969564c06970a7e Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Sun, 18 Apr 2021 17:41:12 -0700 Subject: [PATCH 012/224] Fixed tests --- Sluggo/Models/User.swift | 16 +++++-- SluggoTests/UserTests.swift | 51 +++++++++++++++++++++- SluggoUITests/SluggoUITests.swift | 72 +++++++++++++++---------------- 3 files changed, 99 insertions(+), 40 deletions(-) diff --git a/Sluggo/Models/User.swift b/Sluggo/Models/User.swift index c3d869d..3c85533 100644 --- a/Sluggo/Models/User.swift +++ b/Sluggo/Models/User.swift @@ -25,13 +25,19 @@ struct UserRecord: Codable { init?(fromJSONString: String) { // Initializer for data from JSON // Get data - guard let jsonData = fromJSONString.data(using: .utf8) else { return nil } + guard let jsonData = fromJSONString.data(using: .utf8) else { + print("Failed to decode provided JSON string into data for user object intialization.") + return nil + } // Attempt decoding let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 - guard let userRecord = try? decoder.decode(UserRecord.self, from: jsonData) else { return nil } + guard let userRecord = try? decoder.decode(UserRecord.self, from: jsonData) else { + print("Failed to decode JSON data into object representation for user object initialization.") + return nil + } // Initialize using standard initializer self.init(id: userRecord.id, email: userRecord.email, first_name: userRecord.first_name, last_name: userRecord.last_name, username: userRecord.username) @@ -46,7 +52,11 @@ struct UserRecord: Codable { encoder.outputFormatting = .prettyPrinted // Attempt encoding - guard let jsonData = try? encoder.encode(self) else { return nil } + guard let jsonData = try? encoder.encode(self) else { + print("Failed to encode user object into JSON data.") + return nil + + } // Attempt stringifying the data, this is failable, which is fine since property is optional. return String(data: jsonData, encoding: .utf8) diff --git a/SluggoTests/UserTests.swift b/SluggoTests/UserTests.swift index bc9b501..e01909d 100644 --- a/SluggoTests/UserTests.swift +++ b/SluggoTests/UserTests.swift @@ -11,11 +11,60 @@ import XCTest class UserTests: XCTestCase { let testWorkingJson = """ { - + "username": "wtpisaac", + "email": "wtpisaac@icloud.com", + "id": 1 + } + """ + + let testWorkingJsonWithExtraProps = """ + { + "username": "wtpisaac", + "email": "wtpisaac@icloud.com", + "id": 1, + "first_name": "Isaac", + "last_name": "Trimble-Pederson" } """ func testUserDoesDeserialize() { + let user = UserRecord(fromJSONString: testWorkingJson) + XCTAssertNotNil(user) + + XCTAssertEqual(user!.username, "wtpisaac") + XCTAssertEqual(user!.email, "wtpisaac@icloud.com") + XCTAssertEqual(user!.id, 1) + + XCTAssertNil(user!.first_name) + XCTAssertNil(user!.last_name) + } + + func testUserDeserializesWithExtraProps() { + let user = UserRecord(fromJSONString: testWorkingJsonWithExtraProps) + XCTAssertNotNil(user) + + XCTAssertEqual(user!.username, "wtpisaac") + XCTAssertEqual(user!.email, "wtpisaac@icloud.com") + XCTAssertEqual(user!.id, 1) + + XCTAssertEqual(user!.first_name, "Isaac") + XCTAssertEqual(user!.last_name, "Trimble-Pederson") + } + + func testUserDoesSerialize() { + let user = UserRecord(id: 1, email: "wtpisaac@icloud.com", first_name: "Isaac", last_name: "Trimble-Pederson", username: "wtpisaac") + + let json = user.jsonString + XCTAssertNotNil(json) + print(json) + + let userDuplicate = UserRecord(fromJSONString: json!) + XCTAssertNotNil(userDuplicate) + XCTAssertEqual(user.username, userDuplicate!.username) + XCTAssertEqual(user.email, userDuplicate!.email) + XCTAssertEqual(user.first_name, userDuplicate!.first_name) + XCTAssertEqual(user.last_name, userDuplicate!.last_name) + XCTAssertEqual(user.id, userDuplicate!.id) } } diff --git a/SluggoUITests/SluggoUITests.swift b/SluggoUITests/SluggoUITests.swift index 81b14d6..9deaf24 100644 --- a/SluggoUITests/SluggoUITests.swift +++ b/SluggoUITests/SluggoUITests.swift @@ -4,39 +4,39 @@ // // Created by Isaac Trimble-Pederson on 4/8/21. // - -import XCTest - -class SluggoUITests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - - // In UI tests it is usually best to stop immediately when a failure occurs. - continueAfterFailure = false - - // 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. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // UI tests must launch the application that they test. - let app = XCUIApplication() - app.launch() - - // Use recording to get started writing UI tests. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testLaunchPerformance() throws { - if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { - // This measures how long it takes to launch your application. - measure(metrics: [XCTApplicationLaunchMetric()]) { - XCUIApplication().launch() - } - } - } -} +// +//import XCTest +// +//class SluggoUITests: XCTestCase { +// +// override func setUpWithError() throws { +// // Put setup code here. This method is called before the invocation of each test method in the class. +// +// // In UI tests it is usually best to stop immediately when a failure occurs. +// continueAfterFailure = false +// +// // 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. +// } +// +// override func tearDownWithError() throws { +// // Put teardown code here. This method is called after the invocation of each test method in the class. +// } +// +// func testExample() throws { +// // UI tests must launch the application that they test. +// let app = XCUIApplication() +// app.launch() +// +// // Use recording to get started writing UI tests. +// // Use XCTAssert and related functions to verify your tests produce the correct results. +// } +// +// func testLaunchPerformance() throws { +// if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { +// // This measures how long it takes to launch your application. +// measure(metrics: [XCTApplicationLaunchMetric()]) { +// XCUIApplication().launch() +// } +// } +// } +//} From ea6aae293a1e53a0cca295f20376be0cf81fddd0 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Sun, 18 Apr 2021 17:42:12 -0700 Subject: [PATCH 013/224] Temporarily disabled UI tests --- .../xcshareddata/xcschemes/Sluggo.xcscheme | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 Sluggo.xcodeproj/xcshareddata/xcschemes/Sluggo.xcscheme diff --git a/Sluggo.xcodeproj/xcshareddata/xcschemes/Sluggo.xcscheme b/Sluggo.xcodeproj/xcshareddata/xcschemes/Sluggo.xcscheme new file mode 100644 index 0000000..57e9213 --- /dev/null +++ b/Sluggo.xcodeproj/xcshareddata/xcschemes/Sluggo.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From c0af3175af01e70712cc4bccf89588f4a0d46ac2 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Sun, 18 Apr 2021 18:05:21 -0700 Subject: [PATCH 014/224] Added PaginatedList Struct for codables --- Sluggo.xcodeproj/project.pbxproj | 4 ++++ Sluggo/Models/PaginatedList.swift | 15 +++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 Sluggo/Models/PaginatedList.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 2f8ca55..71d9781 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 2F3AD7A32627C86A00B61A97 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A22627C86A00B61A97 /* User.swift */; }; 2F3AD7A82627C89B00B61A97 /* Member.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A72627C89B00B61A97 /* Member.swift */; }; 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7AC2627C8EF00B61A97 /* Team.swift */; }; + 2FEF5DA9262D0E7C00434763 /* PaginatedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */; }; 7D1354FC262D026E00D38EE0 /* UserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1354FB262D026E00D38EE0 /* UserTests.swift */; }; 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */; }; 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */; }; @@ -44,6 +45,7 @@ 2F3AD7A22627C86A00B61A97 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; 2F3AD7A72627C89B00B61A97 /* Member.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Member.swift; sourceTree = ""; }; 2F3AD7AC2627C8EF00B61A97 /* Team.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Team.swift; sourceTree = ""; }; + 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginatedList.swift; sourceTree = ""; }; 7D1354FB262D026E00D38EE0 /* UserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTests.swift; sourceTree = ""; }; 7D9963A2261EF3A4002338E7 /* Sluggo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sluggo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -93,6 +95,7 @@ 2F3AD7A72627C89B00B61A97 /* Member.swift */, 2F3AD7AC2627C8EF00B61A97 /* Team.swift */, 2F2247AB262920AC006C6EE6 /* Token.swift */, + 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */, ); path = Models; sourceTree = ""; @@ -288,6 +291,7 @@ 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */, 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */, 2F3AD7A82627C89B00B61A97 /* Member.swift in Sources */, + 2FEF5DA9262D0E7C00434763 /* PaginatedList.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sluggo/Models/PaginatedList.swift b/Sluggo/Models/PaginatedList.swift new file mode 100644 index 0000000..6fd0cd6 --- /dev/null +++ b/Sluggo/Models/PaginatedList.swift @@ -0,0 +1,15 @@ +// +// PaginatedList.swift +// Sluggo +// +// Created by Andrew Gavgavian on 4/18/21. +// + +import Foundation + +struct PaginatedList : Codable { + var count: Int + var next: String? + var previous: String? + let results: [T] +} From c705e6fc8a3c62e1e32dc6f39aa3088e6d8b6240 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Sun, 18 Apr 2021 19:17:22 -0700 Subject: [PATCH 015/224] Implemented Member creation and tests. Updated User model to match. --- Sluggo.xcodeproj/project.pbxproj | 4 ++ Sluggo/Models/Member.swift | 39 +++++++++++++- Sluggo/Models/User.swift | 24 +++------ SluggoTests/MemberTests.swift | 92 ++++++++++++++++++++++++++++++++ SluggoTests/UserTests.swift | 6 +-- 5 files changed, 144 insertions(+), 21 deletions(-) create mode 100644 SluggoTests/MemberTests.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 71d9781..744b804 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 2F3AD7A82627C89B00B61A97 /* Member.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A72627C89B00B61A97 /* Member.swift */; }; 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7AC2627C8EF00B61A97 /* Team.swift */; }; 2FEF5DA9262D0E7C00434763 /* PaginatedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */; }; + 2FEF5DAE262D1C4900434763 /* MemberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DAD262D1C4900434763 /* MemberTests.swift */; }; 7D1354FC262D026E00D38EE0 /* UserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1354FB262D026E00D38EE0 /* UserTests.swift */; }; 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */; }; 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */; }; @@ -46,6 +47,7 @@ 2F3AD7A72627C89B00B61A97 /* Member.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Member.swift; sourceTree = ""; }; 2F3AD7AC2627C8EF00B61A97 /* Team.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Team.swift; sourceTree = ""; }; 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginatedList.swift; sourceTree = ""; }; + 2FEF5DAD262D1C4900434763 /* MemberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberTests.swift; sourceTree = ""; }; 7D1354FB262D026E00D38EE0 /* UserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTests.swift; sourceTree = ""; }; 7D9963A2261EF3A4002338E7 /* Sluggo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sluggo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -141,6 +143,7 @@ 7D9963BF261EF3A5002338E7 /* SluggoTests.swift */, 7D9963C1261EF3A5002338E7 /* Info.plist */, 7D1354FB262D026E00D38EE0 /* UserTests.swift */, + 2FEF5DAD262D1C4900434763 /* MemberTests.swift */, ); path = SluggoTests; sourceTree = ""; @@ -300,6 +303,7 @@ buildActionMask = 2147483647; files = ( 7D1354FC262D026E00D38EE0 /* UserTests.swift in Sources */, + 2FEF5DAE262D1C4900434763 /* MemberTests.swift in Sources */, 7D9963C0261EF3A5002338E7 /* SluggoTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sluggo/Models/Member.swift b/Sluggo/Models/Member.swift index 0424c30..df98dbb 100644 --- a/Sluggo/Models/Member.swift +++ b/Sluggo/Models/Member.swift @@ -18,5 +18,42 @@ struct MemberRecord: Codable { var created: Date var activated: Date? var deactivated: Date? - + + static func createFromJSON(_ string: String) -> MemberRecord? { + // Get data + guard let jsonData = string.data(using: .utf8) else { + print("Failed to decode provided JSON string into data for user object intialization.") + return nil + } + + // Attempt decoding + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601 + + guard let memberRecord = try? decoder.decode(MemberRecord.self, from: jsonData) else { + print("Failed to decode JSON data into object representation for user object initialization.") + return nil + } + return memberRecord + } + + var jsonString: String? { + get { + // obtain an encoder + + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = .iso8601 + encoder.outputFormatting = .prettyPrinted + + // Attempt encoding + guard let jsonData = try? encoder.encode(self) else { + print("Failed to encode user object into JSON data.") + return nil + + } + + // Attempt stringifying the data, this is failable, which is fine since property is optional. + return String(data: jsonData, encoding: .utf8) + } + } } diff --git a/Sluggo/Models/User.swift b/Sluggo/Models/User.swift index 3c85533..59aa24c 100644 --- a/Sluggo/Models/User.swift +++ b/Sluggo/Models/User.swift @@ -14,34 +14,24 @@ struct UserRecord: Codable { var first_name: String? var last_name: String? var username: String - - init(id: Int, email: String, first_name: String?, last_name: String?, username: String) { - self.id = id - self.email = email - self.first_name = first_name - self.last_name = last_name - self.username = username - } - - init?(fromJSONString: String) { // Initializer for data from JSON + + static func createFromJSON(_ string: String) -> UserRecord? { // Get data - guard let jsonData = fromJSONString.data(using: .utf8) else { + guard let jsonData = string.data(using: .utf8) else { print("Failed to decode provided JSON string into data for user object intialization.") return nil } - + // Attempt decoding let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 - + guard let userRecord = try? decoder.decode(UserRecord.self, from: jsonData) else { print("Failed to decode JSON data into object representation for user object initialization.") return nil } - - // Initialize using standard initializer - self.init(id: userRecord.id, email: userRecord.email, first_name: userRecord.first_name, last_name: userRecord.last_name, username: userRecord.username) - } + return userRecord + } var jsonString: String? { get { diff --git a/SluggoTests/MemberTests.swift b/SluggoTests/MemberTests.swift new file mode 100644 index 0000000..45d4283 --- /dev/null +++ b/SluggoTests/MemberTests.swift @@ -0,0 +1,92 @@ +// +// MemberTests.swift +// SluggoTests +// +// Created by Andrew Gavgavian on 4/18/21. +// + +import XCTest +@testable import Sluggo + +class MemberTests: XCTestCase { + let testWorkingJson = """ + { + "id": "c4ca4238a0b923820dcc509a6f75849bfce3644a63f700b351c5afeb783c1cc2", + "owner": { + "username": "wtpisaac", + "email": "wtpisaac@icloud.com", + "id": 1 + }, + "team_id": 1, + "object_uuid": "2823f95d-aa66-43da-acab-34ce2bedfe13", + "role": "UA", + "bio": null, + "pronouns": "he/him", + "created": "2021-04-13T23:17:20+0000", + "activated": null, + "deactivated": null + } + """ + + let testWorkingJsonWithExtraProps = """ + { + "id": "c4ca4238a0b923820dcc509a6f75849bfce3644a63f700b351c5afeb783c1cc2", + "owner": { + "username": "wtpisaac", + "email": "wtpisaac@icloud.com", + "id": 1 + }, + "team_id": 1, + "object_uuid": "2823f95d-aa66-43da-acab-34ce2bedfe13", + "role": "UA", + "bio": "I work on things very very well", + "pronouns": "he/him", + "created": "2021-04-13T23:17:20+0000", + "activated": "2021-04-13T23:17:21+0000", + "deactivated": "2021-04-13T23:17:22+0000" + } + """ + + func testMemberDoesDeserialize() { + let member = MemberRecord.createFromJSON(testWorkingJson) + XCTAssertNotNil(member) + + XCTAssertEqual(member!.team_id, 1) + XCTAssertEqual(member!.role, "UA") + XCTAssertEqual(member!.id, "c4ca4238a0b923820dcc509a6f75849bfce3644a63f700b351c5afeb783c1cc2") + + XCTAssertNil(member!.bio) + XCTAssertNil(member!.activated) + XCTAssertNil(member!.deactivated) + } + + func testUserDeserializesWithExtraProps() { + let member = MemberRecord.createFromJSON(testWorkingJsonWithExtraProps) + XCTAssertNotNil(member) + + XCTAssertEqual(member!.team_id, 1) + XCTAssertEqual(member!.role, "UA") + XCTAssertEqual(member!.id, "c4ca4238a0b923820dcc509a6f75849bfce3644a63f700b351c5afeb783c1cc2") + + XCTAssertEqual(member!.bio, "I work on things very very well") + } + + func testUserDoesSerialize() { + let user = UserRecord(id: 1, email: "wtpisaac@icloud.com", first_name: "Isaac", last_name: "Trimble-Pederson", username: "wtpisaac") + + let member = MemberRecord(id: "this is an md5 eventually", owner: user, team_id: 1, object_uuid: UUID(), role: "UA", bio: nil, created: Date(), activated: nil, deactivated: nil) + + let json = member.jsonString + XCTAssertNotNil(json) + print(json) + + let memberDuplicate = MemberRecord.createFromJSON(json!) + XCTAssertNotNil(memberDuplicate) + + XCTAssertEqual(member.object_uuid, memberDuplicate!.object_uuid) + XCTAssertEqual(member.team_id, memberDuplicate!.team_id) + XCTAssertEqual(member.role, memberDuplicate!.role) + XCTAssertEqual(member.bio, memberDuplicate?.bio) + XCTAssertEqual(member.id, memberDuplicate!.id) + } +} diff --git a/SluggoTests/UserTests.swift b/SluggoTests/UserTests.swift index e01909d..e0bfb2c 100644 --- a/SluggoTests/UserTests.swift +++ b/SluggoTests/UserTests.swift @@ -28,7 +28,7 @@ class UserTests: XCTestCase { """ func testUserDoesDeserialize() { - let user = UserRecord(fromJSONString: testWorkingJson) + let user = UserRecord.createFromJSON(testWorkingJson) XCTAssertNotNil(user) XCTAssertEqual(user!.username, "wtpisaac") @@ -40,7 +40,7 @@ class UserTests: XCTestCase { } func testUserDeserializesWithExtraProps() { - let user = UserRecord(fromJSONString: testWorkingJsonWithExtraProps) + let user = UserRecord.createFromJSON(testWorkingJsonWithExtraProps) XCTAssertNotNil(user) XCTAssertEqual(user!.username, "wtpisaac") @@ -58,7 +58,7 @@ class UserTests: XCTestCase { XCTAssertNotNil(json) print(json) - let userDuplicate = UserRecord(fromJSONString: json!) + let userDuplicate = UserRecord.createFromJSON(json!) XCTAssertNotNil(userDuplicate) XCTAssertEqual(user.username, userDuplicate!.username) From 31405ec4ea14fb777057725886170ac57a2b9b9d Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Sun, 18 Apr 2021 23:29:53 -0700 Subject: [PATCH 016/224] Added create static functions for all other classes. --- Sluggo/Models/Member.swift | 6 ++--- Sluggo/Models/PaginatedList.swift | 38 +++++++++++++++++++++++++++++++ Sluggo/Models/Team.swift | 38 +++++++++++++++++++++++++++++++ Sluggo/Models/Token.swift | 38 +++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+), 3 deletions(-) diff --git a/Sluggo/Models/Member.swift b/Sluggo/Models/Member.swift index df98dbb..9308f8c 100644 --- a/Sluggo/Models/Member.swift +++ b/Sluggo/Models/Member.swift @@ -22,7 +22,7 @@ struct MemberRecord: Codable { static func createFromJSON(_ string: String) -> MemberRecord? { // Get data guard let jsonData = string.data(using: .utf8) else { - print("Failed to decode provided JSON string into data for user object intialization.") + print("Failed to decode provided JSON string into data for Member object intialization.") return nil } @@ -31,7 +31,7 @@ struct MemberRecord: Codable { decoder.dateDecodingStrategy = .iso8601 guard let memberRecord = try? decoder.decode(MemberRecord.self, from: jsonData) else { - print("Failed to decode JSON data into object representation for user object initialization.") + print("Failed to decode JSON data into object representation for Member object initialization.") return nil } return memberRecord @@ -47,7 +47,7 @@ struct MemberRecord: Codable { // Attempt encoding guard let jsonData = try? encoder.encode(self) else { - print("Failed to encode user object into JSON data.") + print("Failed to encode Member object into JSON data.") return nil } diff --git a/Sluggo/Models/PaginatedList.swift b/Sluggo/Models/PaginatedList.swift index 6fd0cd6..b350f18 100644 --- a/Sluggo/Models/PaginatedList.swift +++ b/Sluggo/Models/PaginatedList.swift @@ -12,4 +12,42 @@ struct PaginatedList : Codable { var next: String? var previous: String? let results: [T] + + static func createFromJSON(_ string: String) -> PaginatedList? { + // Get data + guard let jsonData = string.data(using: .utf8) else { + print("Failed to decode provided JSON string into data for PaginatedList object intialization.") + return nil + } + + // Attempt decoding + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601 + + guard let pagRec = try? decoder.decode(PaginatedList.self, from: jsonData) else { + print("Failed to decode JSON data into object representation for PaginatedList object initialization.") + return nil + } + return pagRec + } + + var jsonString: String? { + get { + // obtain an encoder + + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = .iso8601 + encoder.outputFormatting = .prettyPrinted + + // Attempt encoding + guard let jsonData = try? encoder.encode(self) else { + print("Failed to encode PaginatedList object into JSON data.") + return nil + + } + + // Attempt stringifying the data, this is failable, which is fine since property is optional. + return String(data: jsonData, encoding: .utf8) + } + } } diff --git a/Sluggo/Models/Team.swift b/Sluggo/Models/Team.swift index d948013..6aa2399 100644 --- a/Sluggo/Models/Team.swift +++ b/Sluggo/Models/Team.swift @@ -17,4 +17,42 @@ struct TeamRecord: Codable { var activated: Date? var deactivated: Date? + static func createFromJSON(_ string: String) -> TeamRecord? { + // Get data + guard let jsonData = string.data(using: .utf8) else { + print("Failed to decode provided JSON string into data for Team object intialization.") + return nil + } + + // Attempt decoding + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601 + + guard let teamRec = try? decoder.decode(TeamRecord.self, from: jsonData) else { + print("Failed to decode JSON data into object representation for Team object initialization.") + return nil + } + return teamRec + } + + var jsonString: String? { + get { + // obtain an encoder + + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = .iso8601 + encoder.outputFormatting = .prettyPrinted + + // Attempt encoding + guard let jsonData = try? encoder.encode(self) else { + print("Failed to encode Team object into JSON data.") + return nil + + } + + // Attempt stringifying the data, this is failable, which is fine since property is optional. + return String(data: jsonData, encoding: .utf8) + } + } + } diff --git a/Sluggo/Models/Token.swift b/Sluggo/Models/Token.swift index 53d5696..069ca8a 100644 --- a/Sluggo/Models/Token.swift +++ b/Sluggo/Models/Token.swift @@ -10,4 +10,42 @@ import Foundation struct TokenRecord: Codable { var key: String + + static func createFromJSON(_ string: String) -> TokenRecord? { + // Get data + guard let jsonData = string.data(using: .utf8) else { + print("Failed to decode provided JSON string into data for Token object intialization.") + return nil + } + + // Attempt decoding + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601 + + guard let tokenRec = try? decoder.decode(TokenRecord.self, from: jsonData) else { + print("Failed to decode JSON data into object representation for Token object initialization.") + return nil + } + return tokenRec + } + + var jsonString: String? { + get { + // obtain an encoder + + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = .iso8601 + encoder.outputFormatting = .prettyPrinted + + // Attempt encoding + guard let jsonData = try? encoder.encode(self) else { + print("Failed to encode Token object into JSON data.") + return nil + + } + + // Attempt stringifying the data, this is failable, which is fine since property is optional. + return String(data: jsonData, encoding: .utf8) + } + } } From a3d5a2f9b9b22272c8ce4073fa8228b93ebfc9e2 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Mon, 19 Apr 2021 08:18:39 -0700 Subject: [PATCH 017/224] Replaced duplicated code with a singular JsonLoader class for encoding and decoding. --- Sluggo.xcodeproj/project.pbxproj | 4 +++ Sluggo/Models/JsonLoader.swift | 44 +++++++++++++++++++++++++++++++ Sluggo/Models/Member.swift | 38 -------------------------- Sluggo/Models/PaginatedList.swift | 37 -------------------------- Sluggo/Models/Team.swift | 38 -------------------------- Sluggo/Models/Token.swift | 38 -------------------------- Sluggo/Models/User.swift | 38 -------------------------- SluggoTests/MemberTests.swift | 10 +++---- SluggoTests/UserTests.swift | 10 +++---- 9 files changed, 58 insertions(+), 199 deletions(-) create mode 100644 Sluggo/Models/JsonLoader.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 744b804..5ab8270 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 2F3AD7A32627C86A00B61A97 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A22627C86A00B61A97 /* User.swift */; }; 2F3AD7A82627C89B00B61A97 /* Member.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A72627C89B00B61A97 /* Member.swift */; }; 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7AC2627C8EF00B61A97 /* Team.swift */; }; + 2F3F66E2262DD3BA0040404C /* JsonLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3F66E1262DD3BA0040404C /* JsonLoader.swift */; }; 2FEF5DA9262D0E7C00434763 /* PaginatedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */; }; 2FEF5DAE262D1C4900434763 /* MemberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DAD262D1C4900434763 /* MemberTests.swift */; }; 7D1354FC262D026E00D38EE0 /* UserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1354FB262D026E00D38EE0 /* UserTests.swift */; }; @@ -46,6 +47,7 @@ 2F3AD7A22627C86A00B61A97 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; 2F3AD7A72627C89B00B61A97 /* Member.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Member.swift; sourceTree = ""; }; 2F3AD7AC2627C8EF00B61A97 /* Team.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Team.swift; sourceTree = ""; }; + 2F3F66E1262DD3BA0040404C /* JsonLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JsonLoader.swift; sourceTree = ""; }; 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginatedList.swift; sourceTree = ""; }; 2FEF5DAD262D1C4900434763 /* MemberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberTests.swift; sourceTree = ""; }; 7D1354FB262D026E00D38EE0 /* UserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTests.swift; sourceTree = ""; }; @@ -98,6 +100,7 @@ 2F3AD7AC2627C8EF00B61A97 /* Team.swift */, 2F2247AB262920AC006C6EE6 /* Token.swift */, 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */, + 2F3F66E1262DD3BA0040404C /* JsonLoader.swift */, ); path = Models; sourceTree = ""; @@ -288,6 +291,7 @@ buildActionMask = 2147483647; files = ( 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */, + 2F3F66E2262DD3BA0040404C /* JsonLoader.swift in Sources */, 2F2247AC262920AC006C6EE6 /* Token.swift in Sources */, 2F3AD7A32627C86A00B61A97 /* User.swift in Sources */, 7D9963AA261EF3A4002338E7 /* ViewController.swift in Sources */, diff --git a/Sluggo/Models/JsonLoader.swift b/Sluggo/Models/JsonLoader.swift new file mode 100644 index 0000000..be5ba80 --- /dev/null +++ b/Sluggo/Models/JsonLoader.swift @@ -0,0 +1,44 @@ +// +// JsonLoader.swift +// Sluggo +// +// Created by Andrew Gavgavian on 4/19/21. +// + +import Foundation + +class JsonLoader { + static func decode(_ string: String) -> T? { + guard let jsonData = string.data(using: .utf8) else { + print("Failed to decode provided JSON string into data for object intialization.") + return nil + } + + // Attempt decoding + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601 + + guard let pagRec = try? decoder.decode(T.self, from: jsonData) else { + print("Failed to decode JSON data into object representation for object initialization.") + return nil + } + + return pagRec + } + + static func encode(_ data: T) -> String? { + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = .iso8601 + encoder.outputFormatting = .prettyPrinted + + // Attempt encoding + guard let jsonData = try? encoder.encode(data) else { + print("Failed to encode object into JSON data.") + return nil + + } + + // Attempt stringifying the data, this is failable, which is fine since property is optional. + return String(data: jsonData, encoding: .utf8) + } +} diff --git a/Sluggo/Models/Member.swift b/Sluggo/Models/Member.swift index 9308f8c..54eb4ca 100644 --- a/Sluggo/Models/Member.swift +++ b/Sluggo/Models/Member.swift @@ -18,42 +18,4 @@ struct MemberRecord: Codable { var created: Date var activated: Date? var deactivated: Date? - - static func createFromJSON(_ string: String) -> MemberRecord? { - // Get data - guard let jsonData = string.data(using: .utf8) else { - print("Failed to decode provided JSON string into data for Member object intialization.") - return nil - } - - // Attempt decoding - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 - - guard let memberRecord = try? decoder.decode(MemberRecord.self, from: jsonData) else { - print("Failed to decode JSON data into object representation for Member object initialization.") - return nil - } - return memberRecord - } - - var jsonString: String? { - get { - // obtain an encoder - - let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 - encoder.outputFormatting = .prettyPrinted - - // Attempt encoding - guard let jsonData = try? encoder.encode(self) else { - print("Failed to encode Member object into JSON data.") - return nil - - } - - // Attempt stringifying the data, this is failable, which is fine since property is optional. - return String(data: jsonData, encoding: .utf8) - } - } } diff --git a/Sluggo/Models/PaginatedList.swift b/Sluggo/Models/PaginatedList.swift index b350f18..c89f7f5 100644 --- a/Sluggo/Models/PaginatedList.swift +++ b/Sluggo/Models/PaginatedList.swift @@ -13,41 +13,4 @@ struct PaginatedList : Codable { var previous: String? let results: [T] - static func createFromJSON(_ string: String) -> PaginatedList? { - // Get data - guard let jsonData = string.data(using: .utf8) else { - print("Failed to decode provided JSON string into data for PaginatedList object intialization.") - return nil - } - - // Attempt decoding - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 - - guard let pagRec = try? decoder.decode(PaginatedList.self, from: jsonData) else { - print("Failed to decode JSON data into object representation for PaginatedList object initialization.") - return nil - } - return pagRec - } - - var jsonString: String? { - get { - // obtain an encoder - - let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 - encoder.outputFormatting = .prettyPrinted - - // Attempt encoding - guard let jsonData = try? encoder.encode(self) else { - print("Failed to encode PaginatedList object into JSON data.") - return nil - - } - - // Attempt stringifying the data, this is failable, which is fine since property is optional. - return String(data: jsonData, encoding: .utf8) - } - } } diff --git a/Sluggo/Models/Team.swift b/Sluggo/Models/Team.swift index 6aa2399..d948013 100644 --- a/Sluggo/Models/Team.swift +++ b/Sluggo/Models/Team.swift @@ -17,42 +17,4 @@ struct TeamRecord: Codable { var activated: Date? var deactivated: Date? - static func createFromJSON(_ string: String) -> TeamRecord? { - // Get data - guard let jsonData = string.data(using: .utf8) else { - print("Failed to decode provided JSON string into data for Team object intialization.") - return nil - } - - // Attempt decoding - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 - - guard let teamRec = try? decoder.decode(TeamRecord.self, from: jsonData) else { - print("Failed to decode JSON data into object representation for Team object initialization.") - return nil - } - return teamRec - } - - var jsonString: String? { - get { - // obtain an encoder - - let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 - encoder.outputFormatting = .prettyPrinted - - // Attempt encoding - guard let jsonData = try? encoder.encode(self) else { - print("Failed to encode Team object into JSON data.") - return nil - - } - - // Attempt stringifying the data, this is failable, which is fine since property is optional. - return String(data: jsonData, encoding: .utf8) - } - } - } diff --git a/Sluggo/Models/Token.swift b/Sluggo/Models/Token.swift index 069ca8a..53d5696 100644 --- a/Sluggo/Models/Token.swift +++ b/Sluggo/Models/Token.swift @@ -10,42 +10,4 @@ import Foundation struct TokenRecord: Codable { var key: String - - static func createFromJSON(_ string: String) -> TokenRecord? { - // Get data - guard let jsonData = string.data(using: .utf8) else { - print("Failed to decode provided JSON string into data for Token object intialization.") - return nil - } - - // Attempt decoding - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 - - guard let tokenRec = try? decoder.decode(TokenRecord.self, from: jsonData) else { - print("Failed to decode JSON data into object representation for Token object initialization.") - return nil - } - return tokenRec - } - - var jsonString: String? { - get { - // obtain an encoder - - let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 - encoder.outputFormatting = .prettyPrinted - - // Attempt encoding - guard let jsonData = try? encoder.encode(self) else { - print("Failed to encode Token object into JSON data.") - return nil - - } - - // Attempt stringifying the data, this is failable, which is fine since property is optional. - return String(data: jsonData, encoding: .utf8) - } - } } diff --git a/Sluggo/Models/User.swift b/Sluggo/Models/User.swift index 59aa24c..5210120 100644 --- a/Sluggo/Models/User.swift +++ b/Sluggo/Models/User.swift @@ -14,42 +14,4 @@ struct UserRecord: Codable { var first_name: String? var last_name: String? var username: String - - static func createFromJSON(_ string: String) -> UserRecord? { - // Get data - guard let jsonData = string.data(using: .utf8) else { - print("Failed to decode provided JSON string into data for user object intialization.") - return nil - } - - // Attempt decoding - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 - - guard let userRecord = try? decoder.decode(UserRecord.self, from: jsonData) else { - print("Failed to decode JSON data into object representation for user object initialization.") - return nil - } - return userRecord - } - - var jsonString: String? { - get { - // obtain an encoder - - let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 - encoder.outputFormatting = .prettyPrinted - - // Attempt encoding - guard let jsonData = try? encoder.encode(self) else { - print("Failed to encode user object into JSON data.") - return nil - - } - - // Attempt stringifying the data, this is failable, which is fine since property is optional. - return String(data: jsonData, encoding: .utf8) - } - } } diff --git a/SluggoTests/MemberTests.swift b/SluggoTests/MemberTests.swift index 45d4283..0497df0 100644 --- a/SluggoTests/MemberTests.swift +++ b/SluggoTests/MemberTests.swift @@ -48,7 +48,7 @@ class MemberTests: XCTestCase { """ func testMemberDoesDeserialize() { - let member = MemberRecord.createFromJSON(testWorkingJson) + let member: MemberRecord? = JsonLoader.decode(testWorkingJson) XCTAssertNotNil(member) XCTAssertEqual(member!.team_id, 1) @@ -61,7 +61,7 @@ class MemberTests: XCTestCase { } func testUserDeserializesWithExtraProps() { - let member = MemberRecord.createFromJSON(testWorkingJsonWithExtraProps) + let member: MemberRecord? = JsonLoader.decode(testWorkingJsonWithExtraProps) XCTAssertNotNil(member) XCTAssertEqual(member!.team_id, 1) @@ -76,11 +76,11 @@ class MemberTests: XCTestCase { let member = MemberRecord(id: "this is an md5 eventually", owner: user, team_id: 1, object_uuid: UUID(), role: "UA", bio: nil, created: Date(), activated: nil, deactivated: nil) - let json = member.jsonString + let json = JsonLoader.encode(member) XCTAssertNotNil(json) - print(json) + print(json!) - let memberDuplicate = MemberRecord.createFromJSON(json!) + let memberDuplicate: MemberRecord? = JsonLoader.decode(json!) XCTAssertNotNil(memberDuplicate) XCTAssertEqual(member.object_uuid, memberDuplicate!.object_uuid) diff --git a/SluggoTests/UserTests.swift b/SluggoTests/UserTests.swift index e0bfb2c..2ae8fd9 100644 --- a/SluggoTests/UserTests.swift +++ b/SluggoTests/UserTests.swift @@ -28,7 +28,7 @@ class UserTests: XCTestCase { """ func testUserDoesDeserialize() { - let user = UserRecord.createFromJSON(testWorkingJson) + let user: UserRecord? = JsonLoader.decode(testWorkingJson) XCTAssertNotNil(user) XCTAssertEqual(user!.username, "wtpisaac") @@ -40,7 +40,7 @@ class UserTests: XCTestCase { } func testUserDeserializesWithExtraProps() { - let user = UserRecord.createFromJSON(testWorkingJsonWithExtraProps) + let user: UserRecord? = JsonLoader.decode(testWorkingJsonWithExtraProps) XCTAssertNotNil(user) XCTAssertEqual(user!.username, "wtpisaac") @@ -54,11 +54,11 @@ class UserTests: XCTestCase { func testUserDoesSerialize() { let user = UserRecord(id: 1, email: "wtpisaac@icloud.com", first_name: "Isaac", last_name: "Trimble-Pederson", username: "wtpisaac") - let json = user.jsonString + let json = JsonLoader.encode(user) XCTAssertNotNil(json) - print(json) + print(json!) - let userDuplicate = UserRecord.createFromJSON(json!) + let userDuplicate: UserRecord? = JsonLoader.decode(json!) XCTAssertNotNil(userDuplicate) XCTAssertEqual(user.username, userDuplicate!.username) From e94c2201b4d615ec5e86e550b7466e83ae96a2b1 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Mon, 19 Apr 2021 08:20:05 -0700 Subject: [PATCH 018/224] Removed the decoder from ViewController --- Sluggo/ViewController.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Sluggo/ViewController.swift b/Sluggo/ViewController.swift index e787b68..f96c441 100644 --- a/Sluggo/ViewController.swift +++ b/Sluggo/ViewController.swift @@ -10,14 +10,9 @@ import UIKit class ViewController: UIViewController { - let decoder = JSONDecoder() - - override func viewDidLoad() { super.viewDidLoad() - // Set the proper date Decoding Strategy to be ISO8601 (YYYY-MM-dd'T'HH:mm:ss'Z) NOTE NO MILLISECONDS SUPPORTED - decoder.dateDecodingStrategy = .iso8601 // Do any additional setup after loading the view. } From 960c1dafb17f5af4874a3538c12e3d9d10bd1d72 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Mon, 19 Apr 2021 12:25:08 -0700 Subject: [PATCH 019/224] Added UI skeleton for login functionality --- Sluggo.xcodeproj/project.pbxproj | 4 + Sluggo/Base.lproj/Main.storyboard | 117 ++++++++++++++++++++++++++++-- Sluggo/LoginViewController.swift | 39 ++++++++++ Sluggo/ViewController.swift | 2 - 4 files changed, 155 insertions(+), 7 deletions(-) create mode 100644 Sluggo/LoginViewController.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 25cd389..400e433 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 05E66565262CEAB000F187E1 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05E66564262CEAB000F187E1 /* LoginViewController.swift */; }; 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */; }; 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */; }; 7D9963AA261EF3A4002338E7 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A9261EF3A4002338E7 /* ViewController.swift */; }; @@ -36,6 +37,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 05E66564262CEAB000F187E1 /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 7D9963A2261EF3A4002338E7 /* Sluggo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sluggo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -109,6 +111,7 @@ 7D9963B3261EF3A5002338E7 /* LaunchScreen.storyboard */, 7D9963B6261EF3A5002338E7 /* Info.plist */, 7D9963AE261EF3A4002338E7 /* Sluggo.xcdatamodeld */, + 05E66564262CEAB000F187E1 /* LoginViewController.swift */, ); path = Sluggo; sourceTree = ""; @@ -264,6 +267,7 @@ 7D9963B0261EF3A4002338E7 /* Sluggo.xcdatamodeld in Sources */, 7D9963AA261EF3A4002338E7 /* ViewController.swift in Sources */, 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */, + 05E66565262CEAB000F187E1 /* LoginViewController.swift in Sources */, 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sluggo/Base.lproj/Main.storyboard b/Sluggo/Base.lproj/Main.storyboard index 25a7638..510d965 100644 --- a/Sluggo/Base.lproj/Main.storyboard +++ b/Sluggo/Base.lproj/Main.storyboard @@ -1,24 +1,131 @@ - + + - + + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sluggo/LoginViewController.swift b/Sluggo/LoginViewController.swift new file mode 100644 index 0000000..9ae9734 --- /dev/null +++ b/Sluggo/LoginViewController.swift @@ -0,0 +1,39 @@ +// +// LoginViewController.swift +// Sluggo +// +// Created by Troy on 4/18/21. +// + +import UIKit + +class LoginViewController: UIViewController { + + @IBOutlet weak var username: UITextField! + @IBOutlet weak var password: UITextField! + + + override func viewDidLoad() { + super.viewDidLoad() + + } + + @IBAction func loginButton(_ sender: Any) { + + + // Sanitize input? + + let userString = username.text + let passString = password.text + + loginMethod(userString!, passString!) + + + } + + func loginMethod(_ user:String, _ password:String) { + + return + } + +} diff --git a/Sluggo/ViewController.swift b/Sluggo/ViewController.swift index 2430c7a..a680b7d 100644 --- a/Sluggo/ViewController.swift +++ b/Sluggo/ViewController.swift @@ -13,7 +13,5 @@ class ViewController: UIViewController { super.viewDidLoad() // Do any additional setup after loading the view. } - - } From 4e9d145f5c735cad6aa2aa39b57da65dbfd8a96e Mon Sep 17 00:00:00 2001 From: BuildTools Date: Mon, 19 Apr 2021 17:01:43 -0700 Subject: [PATCH 020/224] Polished UI --- Sluggo.xcodeproj/project.pbxproj | 4 + Sluggo/Base.lproj/Main.storyboard | 133 ++++++++++++++++++------------ Sluggo/HomeViewController.swift | 26 ++++++ Sluggo/LoginViewController.swift | 5 +- 4 files changed, 113 insertions(+), 55 deletions(-) create mode 100644 Sluggo/HomeViewController.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 400e433..3ae69ef 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 05E66565262CEAB000F187E1 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05E66564262CEAB000F187E1 /* LoginViewController.swift */; }; + 05FE6D07262E43E800A778DC /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05FE6D06262E43E800A778DC /* HomeViewController.swift */; }; 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */; }; 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */; }; 7D9963AA261EF3A4002338E7 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A9261EF3A4002338E7 /* ViewController.swift */; }; @@ -38,6 +39,7 @@ /* Begin PBXFileReference section */ 05E66564262CEAB000F187E1 /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; + 05FE6D06262E43E800A778DC /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; 7D9963A2261EF3A4002338E7 /* Sluggo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sluggo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -112,6 +114,7 @@ 7D9963B6261EF3A5002338E7 /* Info.plist */, 7D9963AE261EF3A4002338E7 /* Sluggo.xcdatamodeld */, 05E66564262CEAB000F187E1 /* LoginViewController.swift */, + 05FE6D06262E43E800A778DC /* HomeViewController.swift */, ); path = Sluggo; sourceTree = ""; @@ -269,6 +272,7 @@ 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */, 05E66565262CEAB000F187E1 /* LoginViewController.swift in Sources */, 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */, + 05FE6D07262E43E800A778DC /* HomeViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sluggo/Base.lproj/Main.storyboard b/Sluggo/Base.lproj/Main.storyboard index 510d965..9fe0a24 100644 --- a/Sluggo/Base.lproj/Main.storyboard +++ b/Sluggo/Base.lproj/Main.storyboard @@ -1,6 +1,6 @@ - + @@ -8,85 +8,81 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + - - - + + + + + - - - + + + + + - - + + + + + + + + + + + + + + + + + + + @@ -96,15 +92,44 @@ - + + + + + + + + + + + + + + + + + + + + + - + - diff --git a/Sluggo/HomeViewController.swift b/Sluggo/HomeViewController.swift new file mode 100644 index 0000000..3d46359 --- /dev/null +++ b/Sluggo/HomeViewController.swift @@ -0,0 +1,26 @@ +// +// HomeViewController.swift +// Sluggo +// +// Created by Troy on 4/19/21. +// + +import UIKit + +class HomeViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + } + + @IBAction func unwind(_ unwindSegue: UIStoryboardSegue) {} + + + @IBAction func backToLoginButton(_ sender: Any) { + performSegue(withIdentifier: "unwindToLogin", sender: self) + } + + +} diff --git a/Sluggo/LoginViewController.swift b/Sluggo/LoginViewController.swift index 9ae9734..d240fbe 100644 --- a/Sluggo/LoginViewController.swift +++ b/Sluggo/LoginViewController.swift @@ -2,7 +2,7 @@ // LoginViewController.swift // Sluggo // -// Created by Troy on 4/18/21. +// Created by Troy Ebert on 4/18/21. // import UIKit @@ -26,6 +26,9 @@ class LoginViewController: UIViewController { let userString = username.text let passString = password.text + print("User:", userString!) + print("Password:", passString!) + loginMethod(userString!, passString!) From a49930bd59f935f721f56e5710b1c8164e352819 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Mon, 19 Apr 2021 17:25:14 -0700 Subject: [PATCH 021/224] i love xcode --- Sluggo.xcodeproj/project.pbxproj | 21 ++++++++------- Sluggo/HomeViewController.swift | 26 ------------------- .../Login.storyboard} | 0 .../LoginViewController.swift | 0 Sluggo/ViewController.swift | 19 -------------- 5 files changed, 12 insertions(+), 54 deletions(-) delete mode 100644 Sluggo/HomeViewController.swift rename Sluggo/{Base.lproj/Main.storyboard => Storyboards/Login.storyboard} (100%) rename Sluggo/{ => View Controllers}/LoginViewController.swift (100%) delete mode 100644 Sluggo/ViewController.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index fc0cfb1..a3bcd2c 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -11,15 +11,16 @@ 2F3AD7A32627C86A00B61A97 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A22627C86A00B61A97 /* User.swift */; }; 2F3AD7A82627C89B00B61A97 /* Member.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A72627C89B00B61A97 /* Member.swift */; }; 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7AC2627C8EF00B61A97 /* Team.swift */; }; - 7D1DA6AE262B82C200E7C12D /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */; }; - 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */; }; 2F3F66E2262DD3BA0040404C /* JsonLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3F66E1262DD3BA0040404C /* JsonLoader.swift */; }; 2FEF5DA9262D0E7C00434763 /* PaginatedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */; }; 2FEF5DAE262D1C4900434763 /* MemberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DAD262D1C4900434763 /* MemberTests.swift */; }; + 5A79FFE1262E562A00D04320 /* Login.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A79FFE0262E562A00D04320 /* Login.storyboard */; }; + 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A79FFE5262E563F00D04320 /* LoginViewController.swift */; }; 7D1354FC262D026E00D38EE0 /* UserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1354FB262D026E00D38EE0 /* UserTests.swift */; }; + 7D1DA6AE262B82C200E7C12D /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */; }; + 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */; }; 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */; }; 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */; }; - 7D9963AA261EF3A4002338E7 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A9261EF3A4002338E7 /* ViewController.swift */; }; 7D9963AD261EF3A4002338E7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7D9963AB261EF3A4002338E7 /* Main.storyboard */; }; 7D9963B2261EF3A5002338E7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7D9963B1261EF3A5002338E7 /* Assets.xcassets */; }; 7D9963B5261EF3A5002338E7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7D9963B3261EF3A5002338E7 /* LaunchScreen.storyboard */; }; @@ -54,16 +55,17 @@ 2F3AD7A22627C86A00B61A97 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; 2F3AD7A72627C89B00B61A97 /* Member.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Member.swift; sourceTree = ""; }; 2F3AD7AC2627C8EF00B61A97 /* Team.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Team.swift; sourceTree = ""; }; - 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RootViewController.swift; path = "Sluggo/View Controllers/RootViewController.swift"; sourceTree = SOURCE_ROOT; }; - 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarData.swift; sourceTree = ""; }; 2F3F66E1262DD3BA0040404C /* JsonLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JsonLoader.swift; sourceTree = ""; }; 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginatedList.swift; sourceTree = ""; }; 2FEF5DAD262D1C4900434763 /* MemberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberTests.swift; sourceTree = ""; }; + 5A79FFE0262E562A00D04320 /* Login.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Login.storyboard; sourceTree = ""; }; + 5A79FFE5262E563F00D04320 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 7D1354FB262D026E00D38EE0 /* UserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTests.swift; sourceTree = ""; }; + 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RootViewController.swift; path = "Sluggo/View Controllers/RootViewController.swift"; sourceTree = SOURCE_ROOT; }; + 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarData.swift; sourceTree = ""; }; 7D9963A2261EF3A4002338E7 /* Sluggo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sluggo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - 7D9963A9261EF3A4002338E7 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 7D9963AC261EF3A4002338E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 7D9963B1261EF3A5002338E7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 7D9963B4261EF3A5002338E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; @@ -148,7 +150,6 @@ 2F81E6E62627E85000B6D86D /* Models */, 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */, 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */, - 7D9963A9261EF3A4002338E7 /* ViewController.swift */, 7D9963B1261EF3A5002338E7 /* Assets.xcassets */, 7D9963B3261EF3A5002338E7 /* LaunchScreen.storyboard */, 7D9963B6261EF3A5002338E7 /* Info.plist */, @@ -179,6 +180,7 @@ 7DC2A21526293F1E0002CEE6 /* Storyboards */ = { isa = PBXGroup; children = ( + 5A79FFE0262E562A00D04320 /* Login.storyboard */, 7D9963AB261EF3A4002338E7 /* Main.storyboard */, 7DC2A21626293F360002CEE6 /* Home.storyboard */, 7DC2A21B262943A70002CEE6 /* Sidebar.storyboard */, @@ -189,6 +191,7 @@ 7DC2A2202629442B0002CEE6 /* View Controllers */ = { isa = PBXGroup; children = ( + 5A79FFE5262E563F00D04320 /* LoginViewController.swift */, 7DC2A2212629447D0002CEE6 /* SluggoSidebarTableViewController.swift */, 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */, 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */, @@ -302,6 +305,7 @@ files = ( 7D9963B5261EF3A5002338E7 /* LaunchScreen.storyboard in Resources */, 7D9963B2261EF3A5002338E7 /* Assets.xcassets in Resources */, + 5A79FFE1262E562A00D04320 /* Login.storyboard in Resources */, 7D9963AD261EF3A4002338E7 /* Main.storyboard in Resources */, 7DC2A21726293F360002CEE6 /* Home.storyboard in Resources */, 7DC2A21C262943A70002CEE6 /* Sidebar.storyboard in Resources */, @@ -329,6 +333,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */, 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */, 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */, 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */, @@ -338,9 +343,7 @@ 2F3AD7A32627C86A00B61A97 /* User.swift in Sources */, 7DC2A2222629447D0002CEE6 /* SluggoSidebarTableViewController.swift in Sources */, 7DC2A22F26294F9E0002CEE6 /* HomeViewController.swift in Sources */, - 7D9963AA261EF3A4002338E7 /* ViewController.swift in Sources */, 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */, - 05E66565262CEAB000F187E1 /* LoginViewController.swift in Sources */, 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */, 2F3AD7A82627C89B00B61A97 /* Member.swift in Sources */, 2FEF5DA9262D0E7C00434763 /* PaginatedList.swift in Sources */, diff --git a/Sluggo/HomeViewController.swift b/Sluggo/HomeViewController.swift deleted file mode 100644 index 3d46359..0000000 --- a/Sluggo/HomeViewController.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// HomeViewController.swift -// Sluggo -// -// Created by Troy on 4/19/21. -// - -import UIKit - -class HomeViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - - // Do any additional setup after loading the view. - } - - @IBAction func unwind(_ unwindSegue: UIStoryboardSegue) {} - - - @IBAction func backToLoginButton(_ sender: Any) { - performSegue(withIdentifier: "unwindToLogin", sender: self) - } - - -} diff --git a/Sluggo/Base.lproj/Main.storyboard b/Sluggo/Storyboards/Login.storyboard similarity index 100% rename from Sluggo/Base.lproj/Main.storyboard rename to Sluggo/Storyboards/Login.storyboard diff --git a/Sluggo/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift similarity index 100% rename from Sluggo/LoginViewController.swift rename to Sluggo/View Controllers/LoginViewController.swift diff --git a/Sluggo/ViewController.swift b/Sluggo/ViewController.swift deleted file mode 100644 index c482043..0000000 --- a/Sluggo/ViewController.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// ViewController.swift -// Sluggo -// -// Created by Isaac Trimble-Pederson on 4/8/21. -// - -import UIKit - -class ViewController: UIViewController { - - - override func viewDidLoad() { - super.viewDidLoad() - - // Do any additional setup after loading the view. - } -} - From 1ae72b803a287843e8c90c6a2cc70d662204f02f Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Mon, 19 Apr 2021 17:54:35 -0700 Subject: [PATCH 022/224] wired the login view into the root view --- Sluggo.xcodeproj/project.pbxproj | 20 +++++++++---------- .../{Main.storyboard => Root.storyboard} | 0 .../{Login.storyboard => Main.storyboard} | 14 +++++++++++-- 3 files changed, 22 insertions(+), 12 deletions(-) rename Sluggo/Storyboards/Base.lproj/{Main.storyboard => Root.storyboard} (100%) rename Sluggo/Storyboards/{Login.storyboard => Main.storyboard} (94%) diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index a3bcd2c..1ea3e5c 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -14,14 +14,14 @@ 2F3F66E2262DD3BA0040404C /* JsonLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3F66E1262DD3BA0040404C /* JsonLoader.swift */; }; 2FEF5DA9262D0E7C00434763 /* PaginatedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */; }; 2FEF5DAE262D1C4900434763 /* MemberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DAD262D1C4900434763 /* MemberTests.swift */; }; - 5A79FFE1262E562A00D04320 /* Login.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A79FFE0262E562A00D04320 /* Login.storyboard */; }; + 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A79FFE0262E562A00D04320 /* Main.storyboard */; }; 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A79FFE5262E563F00D04320 /* LoginViewController.swift */; }; 7D1354FC262D026E00D38EE0 /* UserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1354FB262D026E00D38EE0 /* UserTests.swift */; }; 7D1DA6AE262B82C200E7C12D /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */; }; 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */; }; 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */; }; 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */; }; - 7D9963AD261EF3A4002338E7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7D9963AB261EF3A4002338E7 /* Main.storyboard */; }; + 7D9963AD261EF3A4002338E7 /* Root.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7D9963AB261EF3A4002338E7 /* Root.storyboard */; }; 7D9963B2261EF3A5002338E7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7D9963B1261EF3A5002338E7 /* Assets.xcassets */; }; 7D9963B5261EF3A5002338E7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7D9963B3261EF3A5002338E7 /* LaunchScreen.storyboard */; }; 7D9963C0261EF3A5002338E7 /* SluggoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963BF261EF3A5002338E7 /* SluggoTests.swift */; }; @@ -58,7 +58,7 @@ 2F3F66E1262DD3BA0040404C /* JsonLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JsonLoader.swift; sourceTree = ""; }; 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginatedList.swift; sourceTree = ""; }; 2FEF5DAD262D1C4900434763 /* MemberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberTests.swift; sourceTree = ""; }; - 5A79FFE0262E562A00D04320 /* Login.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Login.storyboard; sourceTree = ""; }; + 5A79FFE0262E562A00D04320 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 5A79FFE5262E563F00D04320 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 7D1354FB262D026E00D38EE0 /* UserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTests.swift; sourceTree = ""; }; 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RootViewController.swift; path = "Sluggo/View Controllers/RootViewController.swift"; sourceTree = SOURCE_ROOT; }; @@ -66,7 +66,7 @@ 7D9963A2261EF3A4002338E7 /* Sluggo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sluggo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - 7D9963AC261EF3A4002338E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 7D9963AC261EF3A4002338E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Root.storyboard; sourceTree = ""; }; 7D9963B1261EF3A5002338E7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 7D9963B4261EF3A5002338E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 7D9963B6261EF3A5002338E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -180,8 +180,8 @@ 7DC2A21526293F1E0002CEE6 /* Storyboards */ = { isa = PBXGroup; children = ( - 5A79FFE0262E562A00D04320 /* Login.storyboard */, - 7D9963AB261EF3A4002338E7 /* Main.storyboard */, + 5A79FFE0262E562A00D04320 /* Main.storyboard */, + 7D9963AB261EF3A4002338E7 /* Root.storyboard */, 7DC2A21626293F360002CEE6 /* Home.storyboard */, 7DC2A21B262943A70002CEE6 /* Sidebar.storyboard */, ); @@ -305,8 +305,8 @@ files = ( 7D9963B5261EF3A5002338E7 /* LaunchScreen.storyboard in Resources */, 7D9963B2261EF3A5002338E7 /* Assets.xcassets in Resources */, - 5A79FFE1262E562A00D04320 /* Login.storyboard in Resources */, - 7D9963AD261EF3A4002338E7 /* Main.storyboard in Resources */, + 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */, + 7D9963AD261EF3A4002338E7 /* Root.storyboard in Resources */, 7DC2A21726293F360002CEE6 /* Home.storyboard in Resources */, 7DC2A21C262943A70002CEE6 /* Sidebar.storyboard in Resources */, ); @@ -384,12 +384,12 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ - 7D9963AB261EF3A4002338E7 /* Main.storyboard */ = { + 7D9963AB261EF3A4002338E7 /* Root.storyboard */ = { isa = PBXVariantGroup; children = ( 7D9963AC261EF3A4002338E7 /* Base */, ); - name = Main.storyboard; + name = Root.storyboard; sourceTree = ""; }; 7D9963B3261EF3A5002338E7 /* LaunchScreen.storyboard */ = { diff --git a/Sluggo/Storyboards/Base.lproj/Main.storyboard b/Sluggo/Storyboards/Base.lproj/Root.storyboard similarity index 100% rename from Sluggo/Storyboards/Base.lproj/Main.storyboard rename to Sluggo/Storyboards/Base.lproj/Root.storyboard diff --git a/Sluggo/Storyboards/Login.storyboard b/Sluggo/Storyboards/Main.storyboard similarity index 94% rename from Sluggo/Storyboards/Login.storyboard rename to Sluggo/Storyboards/Main.storyboard index 9fe0a24..4117602 100644 --- a/Sluggo/Storyboards/Login.storyboard +++ b/Sluggo/Storyboards/Main.storyboard @@ -53,7 +53,7 @@ - + + + + + + + + + + + @@ -122,7 +132,7 @@ - + From 70a9d6b9dc6f8375ad97646a1b5830a947611a8c Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Mon, 19 Apr 2021 18:24:07 -0700 Subject: [PATCH 023/224] initial api calls --- Sluggo.xcodeproj/project.pbxproj | 18 ++++++++-- Sluggo/Models/{ => Codables}/Member.swift | 0 .../Models/{ => Codables}/PaginatedList.swift | 0 Sluggo/Models/{ => Codables}/Team.swift | 0 Sluggo/Models/{ => Codables}/Token.swift | 0 Sluggo/Models/{ => Codables}/User.swift | 0 Sluggo/Models/Config.swift | 12 +++++++ Sluggo/Storyboards/Main.storyboard | 33 ------------------- .../LoginViewController.swift | 22 +++++++++++-- 9 files changed, 46 insertions(+), 39 deletions(-) rename Sluggo/Models/{ => Codables}/Member.swift (100%) rename Sluggo/Models/{ => Codables}/PaginatedList.swift (100%) rename Sluggo/Models/{ => Codables}/Team.swift (100%) rename Sluggo/Models/{ => Codables}/Token.swift (100%) rename Sluggo/Models/{ => Codables}/User.swift (100%) create mode 100644 Sluggo/Models/Config.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 1ea3e5c..8ea9a16 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 2FEF5DAE262D1C4900434763 /* MemberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DAD262D1C4900434763 /* MemberTests.swift */; }; 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A79FFE0262E562A00D04320 /* Main.storyboard */; }; 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A79FFE5262E563F00D04320 /* LoginViewController.swift */; }; + 5A79FFEF262E600B00D04320 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A79FFEE262E600B00D04320 /* Config.swift */; }; 7D1354FC262D026E00D38EE0 /* UserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1354FB262D026E00D38EE0 /* UserTests.swift */; }; 7D1DA6AE262B82C200E7C12D /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */; }; 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */; }; @@ -60,6 +61,7 @@ 2FEF5DAD262D1C4900434763 /* MemberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberTests.swift; sourceTree = ""; }; 5A79FFE0262E562A00D04320 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 5A79FFE5262E563F00D04320 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; + 5A79FFEE262E600B00D04320 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; 7D1354FB262D026E00D38EE0 /* UserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTests.swift; sourceTree = ""; }; 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RootViewController.swift; path = "Sluggo/View Controllers/RootViewController.swift"; sourceTree = SOURCE_ROOT; }; 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarData.swift; sourceTree = ""; }; @@ -109,17 +111,26 @@ /* Begin PBXGroup section */ 2F81E6E62627E85000B6D86D /* Models */ = { + isa = PBXGroup; + children = ( + 2F3F66E1262DD3BA0040404C /* JsonLoader.swift */, + 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */, + 5A79FFED262E5FEF00D04320 /* Codables */, + 5A79FFEE262E600B00D04320 /* Config.swift */, + ); + path = Models; + sourceTree = ""; + }; + 5A79FFED262E5FEF00D04320 /* Codables */ = { isa = PBXGroup; children = ( 2F3AD7A22627C86A00B61A97 /* User.swift */, 2F3AD7A72627C89B00B61A97 /* Member.swift */, 2F3AD7AC2627C8EF00B61A97 /* Team.swift */, 2F2247AB262920AC006C6EE6 /* Token.swift */, - 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */, 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */, - 2F3F66E1262DD3BA0040404C /* JsonLoader.swift */, ); - path = Models; + path = Codables; sourceTree = ""; }; 7D996399261EF3A4002338E7 = { @@ -335,6 +346,7 @@ files = ( 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */, 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */, + 5A79FFEF262E600B00D04320 /* Config.swift in Sources */, 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */, 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */, 2F3F66E2262DD3BA0040404C /* JsonLoader.swift in Sources */, diff --git a/Sluggo/Models/Member.swift b/Sluggo/Models/Codables/Member.swift similarity index 100% rename from Sluggo/Models/Member.swift rename to Sluggo/Models/Codables/Member.swift diff --git a/Sluggo/Models/PaginatedList.swift b/Sluggo/Models/Codables/PaginatedList.swift similarity index 100% rename from Sluggo/Models/PaginatedList.swift rename to Sluggo/Models/Codables/PaginatedList.swift diff --git a/Sluggo/Models/Team.swift b/Sluggo/Models/Codables/Team.swift similarity index 100% rename from Sluggo/Models/Team.swift rename to Sluggo/Models/Codables/Team.swift diff --git a/Sluggo/Models/Token.swift b/Sluggo/Models/Codables/Token.swift similarity index 100% rename from Sluggo/Models/Token.swift rename to Sluggo/Models/Codables/Token.swift diff --git a/Sluggo/Models/User.swift b/Sluggo/Models/Codables/User.swift similarity index 100% rename from Sluggo/Models/User.swift rename to Sluggo/Models/Codables/User.swift diff --git a/Sluggo/Models/Config.swift b/Sluggo/Models/Config.swift new file mode 100644 index 0000000..e1bfe68 --- /dev/null +++ b/Sluggo/Models/Config.swift @@ -0,0 +1,12 @@ +// +// Config.swift +// Sluggo +// +// Created by Samuel Schmidt on 4/19/21. +// + +import Foundation + +struct Config { + static let URL_BASE = "http://127.0.0.1:8000/" +} diff --git a/Sluggo/Storyboards/Main.storyboard b/Sluggo/Storyboards/Main.storyboard index 4117602..4d365a0 100644 --- a/Sluggo/Storyboards/Main.storyboard +++ b/Sluggo/Storyboards/Main.storyboard @@ -104,36 +104,6 @@ - - - - - - - - - - - - - - - - - - - - @@ -159,8 +129,5 @@ - - - diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index d240fbe..c598b14 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -31,12 +31,28 @@ class LoginViewController: UIViewController { loginMethod(userString!, passString!) - + // TODO: programatically segue to root view } func loginMethod(_ user:String, _ password:String) { - - return + let params = ["username":user, "password":password] as Dictionary + + var request = URLRequest(url: URL(string: Config.URL_BASE + "auth/login/")!) + request.httpMethod = "POST" + request.httpBody = try? JSONSerialization.data(withJSONObject: params, options: []) + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + let session = URLSession.shared + let task = session.dataTask(with: request, completionHandler: { data, response, error -> Void in + do { + let json = try JSONSerialization.jsonObject(with: data!) as! Dictionary + print(json) + } catch { + print("error") + } + }) + + task.resume() } } From f16c41051844897eb4a08472827767e8f73cb6d2 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Tue, 20 Apr 2021 17:49:16 -0700 Subject: [PATCH 024/224] Cleaned up UI more --- Sluggo/Storyboards/Main.storyboard | 51 +++++++++++++----------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/Sluggo/Storyboards/Main.storyboard b/Sluggo/Storyboards/Main.storyboard index 4d365a0..c353c0a 100644 --- a/Sluggo/Storyboards/Main.storyboard +++ b/Sluggo/Storyboards/Main.storyboard @@ -1,6 +1,6 @@ - + @@ -13,7 +13,7 @@ - + @@ -26,26 +26,21 @@ - - - - + - - - - + From 2d67d34b9035f9a405f1e03ccf3cc79a5a269948 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Tue, 20 Apr 2021 23:58:46 -0700 Subject: [PATCH 025/224] allegedly, models --- Sluggo.xcodeproj/project.pbxproj | 36 +++++++++++++++ Sluggo/Models/AppIdentity.swift | 44 ++++++++++++++++++ Sluggo/Models/Codables/TicketRecord.swift | 13 ++++++ Sluggo/Models/Config.swift | 9 ++++ Sluggo/Models/JsonLoader.swift | 13 ++---- Sluggo/Models/Managers/BaseManager.swift | 10 ++++ Sluggo/Models/Managers/MemberManager.swift | 54 ++++++++++++++++++++++ Sluggo/Models/Managers/TagManager.swift | 12 +++++ Sluggo/Models/Managers/TeamManager.swift | 30 ++++++++++++ Sluggo/Models/Managers/TicketManager.swift | 36 +++++++++++++++ Sluggo/Models/Managers/UserManager.swift | 35 ++++++++++++++ 11 files changed, 283 insertions(+), 9 deletions(-) create mode 100644 Sluggo/Models/AppIdentity.swift create mode 100644 Sluggo/Models/Codables/TicketRecord.swift create mode 100644 Sluggo/Models/Managers/BaseManager.swift create mode 100644 Sluggo/Models/Managers/MemberManager.swift create mode 100644 Sluggo/Models/Managers/TagManager.swift create mode 100644 Sluggo/Models/Managers/TeamManager.swift create mode 100644 Sluggo/Models/Managers/TicketManager.swift create mode 100644 Sluggo/Models/Managers/UserManager.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 8ea9a16..5c5c9b7 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -17,6 +17,13 @@ 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A79FFE0262E562A00D04320 /* Main.storyboard */; }; 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A79FFE5262E563F00D04320 /* LoginViewController.swift */; }; 5A79FFEF262E600B00D04320 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A79FFEE262E600B00D04320 /* Config.swift */; }; + 5AB1C8DD262FE2DD0010F85E /* TeamManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8DC262FE2DD0010F85E /* TeamManager.swift */; }; + 5AB1C8E5262FE2E60010F85E /* TicketManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8E4262FE2E60010F85E /* TicketManager.swift */; }; + 5AB1C8EA262FE2EF0010F85E /* MemberManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8E9262FE2EF0010F85E /* MemberManager.swift */; }; + 5AB1C8EF262FE3210010F85E /* TagManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8EE262FE3210010F85E /* TagManager.swift */; }; + 5AB1C8F7262FE3520010F85E /* AppIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8F6262FE3520010F85E /* AppIdentity.swift */; }; + 5AB1C8FC262FF1690010F85E /* TicketRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8FB262FF1690010F85E /* TicketRecord.swift */; }; + 5AB1C909262FFF500010F85E /* UserManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C908262FFF500010F85E /* UserManager.swift */; }; 7D1354FC262D026E00D38EE0 /* UserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1354FB262D026E00D38EE0 /* UserTests.swift */; }; 7D1DA6AE262B82C200E7C12D /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */; }; 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */; }; @@ -62,6 +69,13 @@ 5A79FFE0262E562A00D04320 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 5A79FFE5262E563F00D04320 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 5A79FFEE262E600B00D04320 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; + 5AB1C8DC262FE2DD0010F85E /* TeamManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamManager.swift; sourceTree = ""; }; + 5AB1C8E4262FE2E60010F85E /* TicketManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketManager.swift; sourceTree = ""; }; + 5AB1C8E9262FE2EF0010F85E /* MemberManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberManager.swift; sourceTree = ""; }; + 5AB1C8EE262FE3210010F85E /* TagManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagManager.swift; sourceTree = ""; }; + 5AB1C8F6262FE3520010F85E /* AppIdentity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIdentity.swift; sourceTree = ""; }; + 5AB1C8FB262FF1690010F85E /* TicketRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketRecord.swift; sourceTree = ""; }; + 5AB1C908262FFF500010F85E /* UserManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserManager.swift; sourceTree = ""; }; 7D1354FB262D026E00D38EE0 /* UserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTests.swift; sourceTree = ""; }; 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RootViewController.swift; path = "Sluggo/View Controllers/RootViewController.swift"; sourceTree = SOURCE_ROOT; }; 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarData.swift; sourceTree = ""; }; @@ -113,10 +127,12 @@ 2F81E6E62627E85000B6D86D /* Models */ = { isa = PBXGroup; children = ( + 5AB1C8DB262FE29E0010F85E /* Managers */, 2F3F66E1262DD3BA0040404C /* JsonLoader.swift */, 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */, 5A79FFED262E5FEF00D04320 /* Codables */, 5A79FFEE262E600B00D04320 /* Config.swift */, + 5AB1C8F6262FE3520010F85E /* AppIdentity.swift */, ); path = Models; sourceTree = ""; @@ -129,10 +145,23 @@ 2F3AD7AC2627C8EF00B61A97 /* Team.swift */, 2F2247AB262920AC006C6EE6 /* Token.swift */, 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */, + 5AB1C8FB262FF1690010F85E /* TicketRecord.swift */, ); path = Codables; sourceTree = ""; }; + 5AB1C8DB262FE29E0010F85E /* Managers */ = { + isa = PBXGroup; + children = ( + 5AB1C8DC262FE2DD0010F85E /* TeamManager.swift */, + 5AB1C8E4262FE2E60010F85E /* TicketManager.swift */, + 5AB1C8E9262FE2EF0010F85E /* MemberManager.swift */, + 5AB1C8EE262FE3210010F85E /* TagManager.swift */, + 5AB1C908262FFF500010F85E /* UserManager.swift */, + ); + path = Managers; + sourceTree = ""; + }; 7D996399261EF3A4002338E7 = { isa = PBXGroup; children = ( @@ -345,8 +374,10 @@ buildActionMask = 2147483647; files = ( 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */, + 5AB1C8FC262FF1690010F85E /* TicketRecord.swift in Sources */, 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */, 5A79FFEF262E600B00D04320 /* Config.swift in Sources */, + 5AB1C8EA262FE2EF0010F85E /* MemberManager.swift in Sources */, 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */, 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */, 2F3F66E2262DD3BA0040404C /* JsonLoader.swift in Sources */, @@ -354,10 +385,15 @@ 7D1DA6AE262B82C200E7C12D /* RootViewController.swift in Sources */, 2F3AD7A32627C86A00B61A97 /* User.swift in Sources */, 7DC2A2222629447D0002CEE6 /* SluggoSidebarTableViewController.swift in Sources */, + 5AB1C8F7262FE3520010F85E /* AppIdentity.swift in Sources */, 7DC2A22F26294F9E0002CEE6 /* HomeViewController.swift in Sources */, 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */, + 5AB1C8EF262FE3210010F85E /* TagManager.swift in Sources */, + 5AB1C8E5262FE2E60010F85E /* TicketManager.swift in Sources */, 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */, 2F3AD7A82627C89B00B61A97 /* Member.swift in Sources */, + 5AB1C8DD262FE2DD0010F85E /* TeamManager.swift in Sources */, + 5AB1C909262FFF500010F85E /* UserManager.swift in Sources */, 2FEF5DA9262D0E7C00434763 /* PaginatedList.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sluggo/Models/AppIdentity.swift b/Sluggo/Models/AppIdentity.swift new file mode 100644 index 0000000..fd51f9d --- /dev/null +++ b/Sluggo/Models/AppIdentity.swift @@ -0,0 +1,44 @@ +// +// AppIdentity.swift +// Sluggo +// +// Created by Samuel Schmidt on 4/20/21. +// + +import Foundation + +class AppIdentity { + private var authenticatedUser: UserRecord + private var team: TeamRecord + private var token: String + + init(_ authenticatedUser: UserRecord, _ team: TeamRecord, _ token: String) { + self.authenticatedUser = authenticatedUser + self.team = team + self.token = token + } + + public func getAuthenticatedUser() -> UserRecord { + return authenticatedUser + } + + public func getTeam() -> TeamRecord { + return team + } + + public func getToken() -> String { + return token + } + + public func setAuthenticatedUser(_ authenticatedUser: UserRecord) { + self.authenticatedUser = authenticatedUser + } + + public func setTeam(_ team: TeamRecord) { + self.team = team + } + + public func setToken(_ token: String) { + self.token = token + } +} diff --git a/Sluggo/Models/Codables/TicketRecord.swift b/Sluggo/Models/Codables/TicketRecord.swift new file mode 100644 index 0000000..5062e58 --- /dev/null +++ b/Sluggo/Models/Codables/TicketRecord.swift @@ -0,0 +1,13 @@ +// +// TicketRecord.swift +// Sluggo +// +// Created by Samuel Schmidt on 4/20/21. +// + +import Foundation + +struct TicketRecord: Codable { + var id: Int + +} diff --git a/Sluggo/Models/Config.swift b/Sluggo/Models/Config.swift index e1bfe68..f7fba6b 100644 --- a/Sluggo/Models/Config.swift +++ b/Sluggo/Models/Config.swift @@ -9,4 +9,13 @@ import Foundation struct Config { static let URL_BASE = "http://127.0.0.1:8000/" + static let kURL = "url" + + private var configData = [ + kURL: URL_BASE // enable change if need be + ] + + public func getValue(_ key: String) -> String? { + return configData[key] + } } diff --git a/Sluggo/Models/JsonLoader.swift b/Sluggo/Models/JsonLoader.swift index be5ba80..4066a4b 100644 --- a/Sluggo/Models/JsonLoader.swift +++ b/Sluggo/Models/JsonLoader.swift @@ -8,17 +8,12 @@ import Foundation class JsonLoader { - static func decode(_ string: String) -> T? { - guard let jsonData = string.data(using: .utf8) else { - print("Failed to decode provided JSON string into data for object intialization.") - return nil - } - + static func decode(_ data: Data) -> T? { // Attempt decoding let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 - guard let pagRec = try? decoder.decode(T.self, from: jsonData) else { + guard let pagRec = try? decoder.decode(T.self, from: data) else { print("Failed to decode JSON data into object representation for object initialization.") return nil } @@ -26,7 +21,7 @@ class JsonLoader { return pagRec } - static func encode(_ data: T) -> String? { + static func encode(_ data: T) -> Data? { let encoder = JSONEncoder() encoder.dateEncodingStrategy = .iso8601 encoder.outputFormatting = .prettyPrinted @@ -39,6 +34,6 @@ class JsonLoader { } // Attempt stringifying the data, this is failable, which is fine since property is optional. - return String(data: jsonData, encoding: .utf8) + return jsonData } } diff --git a/Sluggo/Models/Managers/BaseManager.swift b/Sluggo/Models/Managers/BaseManager.swift new file mode 100644 index 0000000..1b69fd5 --- /dev/null +++ b/Sluggo/Models/Managers/BaseManager.swift @@ -0,0 +1,10 @@ +// +// BaseManager.swift +// Sluggo +// +// Created by Samuel Schmidt on 4/20/21. +// + +import Foundation + +abstract class diff --git a/Sluggo/Models/Managers/MemberManager.swift b/Sluggo/Models/Managers/MemberManager.swift new file mode 100644 index 0000000..9e878ce --- /dev/null +++ b/Sluggo/Models/Managers/MemberManager.swift @@ -0,0 +1,54 @@ +// +// MemberManager.swift +// Sluggo +// +// Created by Samuel Schmidt on 4/20/21. +// + +import Foundation + +class MemberManager { + private var identity: AppIdentity + private var config: Config + + init(_ identity: AppIdentity, _ config: Config) { + self.identity = identity + self.config = config + } + + private func makeDetailUrl(_ memberRecord: MemberRecord) -> String { + return config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.getTeam().id)" + "/members/" + "\(memberRecord.id)/" + } + + private func makeListUrl() -> String { + return config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.getTeam().id)" + "/members/" + } + + public func fetchMemberRecord() -> URLRequest { + var request = URLRequest(url: URL(string: makeListUrl())!) + request.httpMethod = "GET" + request.setValue("Bearer \(self.identity.getToken())", forHTTPHeaderField: "Authorization") + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + return request + } + + public func updateMemberRecord(_ memberRecord: MemberRecord) -> URLRequest { + var request = URLRequest(url: URL(string: makeDetailUrl(memberRecord))!) + request.httpMethod = "Put" + request.httpBody = JsonLoader.encode(memberRecord) + request.setValue("Bearer \(self.identity.getToken())", forHTTPHeaderField: "Authorization") + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + return request + } + + public func listTeamMembers() -> URLRequest { + var request = URLRequest(url: URL(string: makeListUrl())!) + request.httpMethod = "GET" + request.setValue("Bearer \(self.identity.getToken())", forHTTPHeaderField: "Authorization") + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + return request + } +} diff --git a/Sluggo/Models/Managers/TagManager.swift b/Sluggo/Models/Managers/TagManager.swift new file mode 100644 index 0000000..a83aff6 --- /dev/null +++ b/Sluggo/Models/Managers/TagManager.swift @@ -0,0 +1,12 @@ +// +// TagManger.swift +// Sluggo +// +// Created by Samuel Schmidt on 4/20/21. +// + +import Foundation + +class TagManager { + +} diff --git a/Sluggo/Models/Managers/TeamManager.swift b/Sluggo/Models/Managers/TeamManager.swift new file mode 100644 index 0000000..373f165 --- /dev/null +++ b/Sluggo/Models/Managers/TeamManager.swift @@ -0,0 +1,30 @@ +// +// TeamManager.swift +// Sluggo +// +// Created by Samuel Schmidt on 4/20/21. +// + +import Foundation + +class TeamManager { + static let urlBase = "/api/teams/" + private var identity: AppIdentity + private var config: Config + + + init(_ identity: AppIdentity, _ config: Config) { + self.identity = identity + self.config = config + } + + public func listUserTeams() -> URLRequest { + + var request = URLRequest(url: URL(string: config.getValue(Config.kURL)! + TeamManager.urlBase)!) + request.httpMethod = "GET" + request.setValue("Bearer \(self.identity.getToken())", forHTTPHeaderField: "Authorization") + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + return request + } +} diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift new file mode 100644 index 0000000..570de54 --- /dev/null +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -0,0 +1,36 @@ +// +// TicketManager.swift +// Sluggo +// +// Created by Samuel Schmidt on 4/20/21. +// + +import Foundation + +class TicketManager { + private var identity: AppIdentity + private var config: Config + + init(_ identity: AppIdentity, _ config: Config) { + self.identity = identity + self.config = config + } + + private func makeDetailUrl(_ ticketRecord: TicketRecord) -> String { + return config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.getTeam().id)" + "/tickets/" + "\(ticketRecord.id)/" + } + + private func makeListUrl() -> String { + return config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.getTeam().id)" + "/tickets/" + } + + public func updateTicket(_ ticket: TicketRecord) -> URLRequest { + var request = URLRequest(url: URL(string: makeDetailUrl(ticket))!) + request.httpMethod = "PUT" + request.httpBody = JsonLoader.encode(ticket) + request.setValue("Bearer \(self.identity.getToken())", forHTTPHeaderField: "Authorization") + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + return request + } +} diff --git a/Sluggo/Models/Managers/UserManager.swift b/Sluggo/Models/Managers/UserManager.swift new file mode 100644 index 0000000..a69862a --- /dev/null +++ b/Sluggo/Models/Managers/UserManager.swift @@ -0,0 +1,35 @@ +// +// UserManager.swift +// Sluggo +// +// Created by Samuel Schmidt on 4/20/21. +// + +import Foundation + +class UserManager { + private var config: Config + private var token: String? + + init(_ config: Config, token: String?) { + self.config = config + self.token = token + } + + public func doLogin(username: String, password: String) -> URLRequest { + let params = ["username":username, "password":password] as Dictionary + var request = URLRequest(url: URL(string: config.getValue(Config.kURL)! + "auth/login/")!) + request.httpMethod = "POST" + request.httpBody = try? JSONSerialization.data(withJSONObject: params, options: []) + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + return request + } + + public func doLogout() -> URLRequest { // TODO: this is probably incorrect + var request = URLRequest(url: URL(string: config.getValue(Config.kURL)! + "auth/logout/")!) + request.httpMethod = "POST" + request.setValue("Bearer \(self.token!)", forHTTPHeaderField: "Authorization") + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + return request + } +} From 261ee72451da61a560e47c2448c97661ab36f9ed Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 21 Apr 2021 00:02:05 -0700 Subject: [PATCH 026/224] fix tests, probably --- SluggoTests/MemberTests.swift | 4 ++-- SluggoTests/UserTests.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SluggoTests/MemberTests.swift b/SluggoTests/MemberTests.swift index 0497df0..e39c910 100644 --- a/SluggoTests/MemberTests.swift +++ b/SluggoTests/MemberTests.swift @@ -48,7 +48,7 @@ class MemberTests: XCTestCase { """ func testMemberDoesDeserialize() { - let member: MemberRecord? = JsonLoader.decode(testWorkingJson) + let member: MemberRecord? = JsonLoader.decode(testWorkingJson.data(using: .utf8)!) XCTAssertNotNil(member) XCTAssertEqual(member!.team_id, 1) @@ -61,7 +61,7 @@ class MemberTests: XCTestCase { } func testUserDeserializesWithExtraProps() { - let member: MemberRecord? = JsonLoader.decode(testWorkingJsonWithExtraProps) + let member: MemberRecord? = JsonLoader.decode(testWorkingJsonWithExtraProps.data(using: .utf8)!) XCTAssertNotNil(member) XCTAssertEqual(member!.team_id, 1) diff --git a/SluggoTests/UserTests.swift b/SluggoTests/UserTests.swift index 2ae8fd9..d48cd46 100644 --- a/SluggoTests/UserTests.swift +++ b/SluggoTests/UserTests.swift @@ -28,7 +28,7 @@ class UserTests: XCTestCase { """ func testUserDoesDeserialize() { - let user: UserRecord? = JsonLoader.decode(testWorkingJson) + let user: UserRecord? = JsonLoader.decode(testWorkingJson.data(using: .utf8)!) XCTAssertNotNil(user) XCTAssertEqual(user!.username, "wtpisaac") @@ -40,7 +40,7 @@ class UserTests: XCTestCase { } func testUserDeserializesWithExtraProps() { - let user: UserRecord? = JsonLoader.decode(testWorkingJsonWithExtraProps) + let user: UserRecord? = JsonLoader.decode(testWorkingJsonWithExtraProps.data(using: .utf8)!) XCTAssertNotNil(user) XCTAssertEqual(user!.username, "wtpisaac") From de103f8abc9fc74540f347017154d478340992ce Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 21 Apr 2021 00:21:40 -0700 Subject: [PATCH 027/224] idk what i'm doing --- .../LoginViewController.swift | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index c598b14..7e64f9c 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -11,7 +11,26 @@ class LoginViewController: UIViewController { @IBOutlet weak var username: UITextField! @IBOutlet weak var password: UITextField! + private var config: Config + private var token: String + convenience init() { + self.init(nibName:nil, bundle:nil) + } + + // This extends the superclass. + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + self.config = Config() + self.token = "asdf" // will need to be changed + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + } + + // This is also necessary when extending the superclass. + required init?(coder aDecoder: NSCoder) { + self.config = Config() + self.token = "asdf" // will need to be changed + super.init(coder: aDecoder) + } override func viewDidLoad() { super.viewDidLoad() @@ -35,20 +54,13 @@ class LoginViewController: UIViewController { } func loginMethod(_ user:String, _ password:String) { - let params = ["username":user, "password":password] as Dictionary - - var request = URLRequest(url: URL(string: Config.URL_BASE + "auth/login/")!) - request.httpMethod = "POST" - request.httpBody = try? JSONSerialization.data(withJSONObject: params, options: []) - request.addValue("application/json", forHTTPHeaderField: "Content-Type") + let userManager = UserManager(config, token: nil) + let request = userManager.doLogin(username: user, password: password) let session = URLSession.shared let task = session.dataTask(with: request, completionHandler: { data, response, error -> Void in - do { - let json = try JSONSerialization.jsonObject(with: data!) as! Dictionary - print(json) - } catch { - print("error") + if let json: TokenRecord? = JsonLoader.decode(data!) { + print("data") } }) From 131400195528b595fdf10115e581d942dc438fa5 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 21 Apr 2021 14:26:35 -0700 Subject: [PATCH 028/224] make url requests synchronous, for use with GCD. add exceptions --- Sluggo.xcodeproj/project.pbxproj | 8 ++++ Sluggo/Exceptions.swift | 12 ++++++ Sluggo/Models/Codables/ErrorMessage.swift | 12 ++++++ Sluggo/Models/JsonLoader.swift | 39 +++++++++++++++++++ Sluggo/Models/Managers/MemberManager.swift | 13 ++++--- Sluggo/Models/Managers/TeamManager.swift | 4 +- Sluggo/Models/Managers/TicketManager.swift | 4 +- Sluggo/Models/Managers/UserManager.swift | 12 +++--- .../LoginViewController.swift | 33 ++++++++++------ 9 files changed, 108 insertions(+), 29 deletions(-) create mode 100644 Sluggo/Exceptions.swift create mode 100644 Sluggo/Models/Codables/ErrorMessage.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 5c5c9b7..43b8838 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -24,6 +24,8 @@ 5AB1C8F7262FE3520010F85E /* AppIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8F6262FE3520010F85E /* AppIdentity.swift */; }; 5AB1C8FC262FF1690010F85E /* TicketRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8FB262FF1690010F85E /* TicketRecord.swift */; }; 5AB1C909262FFF500010F85E /* UserManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C908262FFF500010F85E /* UserManager.swift */; }; + 5ACC0F012630CA1400DCF83E /* ErrorMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ACC0F002630CA1400DCF83E /* ErrorMessage.swift */; }; + 5ACC0F062630CA2700DCF83E /* Exceptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ACC0F052630CA2700DCF83E /* Exceptions.swift */; }; 7D1354FC262D026E00D38EE0 /* UserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1354FB262D026E00D38EE0 /* UserTests.swift */; }; 7D1DA6AE262B82C200E7C12D /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */; }; 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */; }; @@ -76,6 +78,8 @@ 5AB1C8F6262FE3520010F85E /* AppIdentity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIdentity.swift; sourceTree = ""; }; 5AB1C8FB262FF1690010F85E /* TicketRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketRecord.swift; sourceTree = ""; }; 5AB1C908262FFF500010F85E /* UserManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserManager.swift; sourceTree = ""; }; + 5ACC0F002630CA1400DCF83E /* ErrorMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMessage.swift; sourceTree = ""; }; + 5ACC0F052630CA2700DCF83E /* Exceptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Exceptions.swift; sourceTree = ""; }; 7D1354FB262D026E00D38EE0 /* UserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTests.swift; sourceTree = ""; }; 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RootViewController.swift; path = "Sluggo/View Controllers/RootViewController.swift"; sourceTree = SOURCE_ROOT; }; 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarData.swift; sourceTree = ""; }; @@ -146,6 +150,7 @@ 2F2247AB262920AC006C6EE6 /* Token.swift */, 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */, 5AB1C8FB262FF1690010F85E /* TicketRecord.swift */, + 5ACC0F002630CA1400DCF83E /* ErrorMessage.swift */, ); path = Codables; sourceTree = ""; @@ -193,6 +198,7 @@ 7D9963B1261EF3A5002338E7 /* Assets.xcassets */, 7D9963B3261EF3A5002338E7 /* LaunchScreen.storyboard */, 7D9963B6261EF3A5002338E7 /* Info.plist */, + 5ACC0F052630CA2700DCF83E /* Exceptions.swift */, ); path = Sluggo; sourceTree = ""; @@ -383,8 +389,10 @@ 2F3F66E2262DD3BA0040404C /* JsonLoader.swift in Sources */, 2F2247AC262920AC006C6EE6 /* Token.swift in Sources */, 7D1DA6AE262B82C200E7C12D /* RootViewController.swift in Sources */, + 5ACC0F012630CA1400DCF83E /* ErrorMessage.swift in Sources */, 2F3AD7A32627C86A00B61A97 /* User.swift in Sources */, 7DC2A2222629447D0002CEE6 /* SluggoSidebarTableViewController.swift in Sources */, + 5ACC0F062630CA2700DCF83E /* Exceptions.swift in Sources */, 5AB1C8F7262FE3520010F85E /* AppIdentity.swift in Sources */, 7DC2A22F26294F9E0002CEE6 /* HomeViewController.swift in Sources */, 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */, diff --git a/Sluggo/Exceptions.swift b/Sluggo/Exceptions.swift new file mode 100644 index 0000000..7528260 --- /dev/null +++ b/Sluggo/Exceptions.swift @@ -0,0 +1,12 @@ +// +// Exceptions.swift +// Sluggo +// +// Created by Samuel Schmidt on 4/21/21. +// + +import Foundation + +enum RESTException: Error { + case FailedRequest(message: String) +} diff --git a/Sluggo/Models/Codables/ErrorMessage.swift b/Sluggo/Models/Codables/ErrorMessage.swift new file mode 100644 index 0000000..24e7bfe --- /dev/null +++ b/Sluggo/Models/Codables/ErrorMessage.swift @@ -0,0 +1,12 @@ +// +// ErrorMessage.swift +// Sluggo +// +// Created by Samuel Schmidt on 4/21/21. +// + +import Foundation + +struct ErrorMessage: Codable { + var detail: String +} diff --git a/Sluggo/Models/JsonLoader.swift b/Sluggo/Models/JsonLoader.swift index 4066a4b..cb73684 100644 --- a/Sluggo/Models/JsonLoader.swift +++ b/Sluggo/Models/JsonLoader.swift @@ -36,4 +36,43 @@ class JsonLoader { // Attempt stringifying the data, this is failable, which is fine since property is optional. return jsonData } + + static func executeCodableRequest(request: URLRequest) throws -> T? { + + let session = URLSession.shared + let semaphore = DispatchSemaphore(value: 0) + var record: T? + var errorMessage: String? + + let task = session.dataTask(with: request, completionHandler: { data, response, error -> Void in + + if error != nil { + errorMessage = "\(error!)" + return + } + + let resp = response as! HTTPURLResponse + if (resp.statusCode <= 299 && resp.statusCode >= 200) { + if let fetchedData = data { + record = JsonLoader.decode(fetchedData) + } + } else { + if let fetchedData = data { + let errorObj: ErrorMessage? = JsonLoader.decode(fetchedData) + errorMessage = errorObj != nil ? errorObj?.detail : "unknown error" + } + } + + semaphore.signal() + }) + + task.resume() + semaphore.wait() // await the request + + if let message = errorMessage { + throw RESTException.FailedRequest(message: message) + } + + return record + } } diff --git a/Sluggo/Models/Managers/MemberManager.swift b/Sluggo/Models/Managers/MemberManager.swift index 9e878ce..976d743 100644 --- a/Sluggo/Models/Managers/MemberManager.swift +++ b/Sluggo/Models/Managers/MemberManager.swift @@ -24,31 +24,32 @@ class MemberManager { return config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.getTeam().id)" + "/members/" } - public func fetchMemberRecord() -> URLRequest { + public func fetchMemberRecord() throws -> MemberRecord? { var request = URLRequest(url: URL(string: makeListUrl())!) request.httpMethod = "GET" request.setValue("Bearer \(self.identity.getToken())", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") - return request + return try JsonLoader.executeCodableRequest(request: request) } - public func updateMemberRecord(_ memberRecord: MemberRecord) -> URLRequest { + public func updateMemberRecord(_ memberRecord: MemberRecord) throws -> MemberRecord? { var request = URLRequest(url: URL(string: makeDetailUrl(memberRecord))!) request.httpMethod = "Put" request.httpBody = JsonLoader.encode(memberRecord) request.setValue("Bearer \(self.identity.getToken())", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") - return request + return try JsonLoader.executeCodableRequest(request: request) + } - public func listTeamMembers() -> URLRequest { + public func listTeamMembers() throws -> PaginatedList? { var request = URLRequest(url: URL(string: makeListUrl())!) request.httpMethod = "GET" request.setValue("Bearer \(self.identity.getToken())", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") - return request + return try JsonLoader.executeCodableRequest(request: request) } } diff --git a/Sluggo/Models/Managers/TeamManager.swift b/Sluggo/Models/Managers/TeamManager.swift index 373f165..8a98cfd 100644 --- a/Sluggo/Models/Managers/TeamManager.swift +++ b/Sluggo/Models/Managers/TeamManager.swift @@ -18,13 +18,13 @@ class TeamManager { self.config = config } - public func listUserTeams() -> URLRequest { + public func listUserTeams() throws -> PaginatedList? { var request = URLRequest(url: URL(string: config.getValue(Config.kURL)! + TeamManager.urlBase)!) request.httpMethod = "GET" request.setValue("Bearer \(self.identity.getToken())", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") - return request + return try JsonLoader.executeCodableRequest(request: request) } } diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index 570de54..12cb90a 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -24,13 +24,13 @@ class TicketManager { return config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.getTeam().id)" + "/tickets/" } - public func updateTicket(_ ticket: TicketRecord) -> URLRequest { + public func updateTicket(_ ticket: TicketRecord) throws -> TicketRecord? { var request = URLRequest(url: URL(string: makeDetailUrl(ticket))!) request.httpMethod = "PUT" request.httpBody = JsonLoader.encode(ticket) request.setValue("Bearer \(self.identity.getToken())", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") - return request + return try JsonLoader.executeCodableRequest(request: request) } } diff --git a/Sluggo/Models/Managers/UserManager.swift b/Sluggo/Models/Managers/UserManager.swift index a69862a..a4432be 100644 --- a/Sluggo/Models/Managers/UserManager.swift +++ b/Sluggo/Models/Managers/UserManager.swift @@ -9,27 +9,25 @@ import Foundation class UserManager { private var config: Config - private var token: String? init(_ config: Config, token: String?) { self.config = config - self.token = token } - public func doLogin(username: String, password: String) -> URLRequest { + public func doLogin(username: String, password: String) throws -> TokenRecord? { let params = ["username":username, "password":password] as Dictionary var request = URLRequest(url: URL(string: config.getValue(Config.kURL)! + "auth/login/")!) request.httpMethod = "POST" request.httpBody = try? JSONSerialization.data(withJSONObject: params, options: []) request.addValue("application/json", forHTTPHeaderField: "Content-Type") - return request + return try JsonLoader.executeCodableRequest(request: request) } - public func doLogout() -> URLRequest { // TODO: this is probably incorrect + public func doLogout(token: String) throws -> ErrorMessage? { // TODO: this is probably incorrect var request = URLRequest(url: URL(string: config.getValue(Config.kURL)! + "auth/logout/")!) request.httpMethod = "POST" - request.setValue("Bearer \(self.token!)", forHTTPHeaderField: "Authorization") + request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") - return request + return try JsonLoader.executeCodableRequest(request: request) } } diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index 7e64f9c..0f051b9 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -48,23 +48,32 @@ class LoginViewController: UIViewController { print("User:", userString!) print("Password:", passString!) - loginMethod(userString!, passString!) + DispatchQueue.global(qos: .userInitiated).async { + self.loginMethod(userString!, passString!) + } // TODO: programatically segue to root view } func loginMethod(_ user:String, _ password:String) { - let userManager = UserManager(config, token: nil) - let request = userManager.doLogin(username: user, password: password) - - let session = URLSession.shared - let task = session.dataTask(with: request, completionHandler: { data, response, error -> Void in - if let json: TokenRecord? = JsonLoader.decode(data!) { - print("data") + let userManager = UserManager(config, token: "asdf") + do { + let request = try userManager.doLogin(username: user, password: password) + print(request?.key) + } catch RESTException.FailedRequest(let message) { // more would need to be done here. + DispatchQueue.main.async { + let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Default action"), style: .default, handler: { _ in + NSLog("The \"OK\" alert occured.") + })) + self.present(alert, animated: true, completion: nil) } - }) - - task.resume() + } catch { + let alert = UIAlertController(title: "Error", message: "some other error ocurred", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Default action"), style: .default, handler: { _ in + NSLog("The \"OK\" alert occured.") + })) + self.present(alert, animated: true, completion: nil) + } } - } From 5056bf4658b715771e6ff90b5a6424b9fd83a638 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Wed, 21 Apr 2021 19:59:42 -0700 Subject: [PATCH 029/224] Fixed button segue, need to error handle --- Sluggo.xcodeproj/project.pbxproj | 4 +++ Sluggo/Models/Constants.swift | 17 +++++++++++ Sluggo/Storyboards/Main.storyboard | 8 ++--- .../LoginViewController.swift | 30 ++++++++++++++++--- 4 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 Sluggo/Models/Constants.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 8ea9a16..c741a0e 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 051AAB0B2630FD6500BCB9C7 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 051AAB0A2630FD6500BCB9C7 /* Constants.swift */; }; 2F2247AC262920AC006C6EE6 /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2247AB262920AC006C6EE6 /* Token.swift */; }; 2F3AD7A32627C86A00B61A97 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A22627C86A00B61A97 /* User.swift */; }; 2F3AD7A82627C89B00B61A97 /* Member.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A72627C89B00B61A97 /* Member.swift */; }; @@ -52,6 +53,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 051AAB0A2630FD6500BCB9C7 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 2F2247AB262920AC006C6EE6 /* Token.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = ""; }; 2F3AD7A22627C86A00B61A97 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; 2F3AD7A72627C89B00B61A97 /* Member.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Member.swift; sourceTree = ""; }; @@ -117,6 +119,7 @@ 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */, 5A79FFED262E5FEF00D04320 /* Codables */, 5A79FFEE262E600B00D04320 /* Config.swift */, + 051AAB0A2630FD6500BCB9C7 /* Constants.swift */, ); path = Models; sourceTree = ""; @@ -349,6 +352,7 @@ 5A79FFEF262E600B00D04320 /* Config.swift in Sources */, 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */, 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */, + 051AAB0B2630FD6500BCB9C7 /* Constants.swift in Sources */, 2F3F66E2262DD3BA0040404C /* JsonLoader.swift in Sources */, 2F2247AC262920AC006C6EE6 /* Token.swift in Sources */, 7D1DA6AE262B82C200E7C12D /* RootViewController.swift in Sources */, diff --git a/Sluggo/Models/Constants.swift b/Sluggo/Models/Constants.swift new file mode 100644 index 0000000..4e0992b --- /dev/null +++ b/Sluggo/Models/Constants.swift @@ -0,0 +1,17 @@ +// +// Constants.swift +// Sluggo +// +// Created by Troy Ebert on 4/21/21. +// + +import Foundation + +struct Constants { + + struct Storyboard { + + static let rootVC = "rVC" + + } +} diff --git a/Sluggo/Storyboards/Main.storyboard b/Sluggo/Storyboards/Main.storyboard index c353c0a..8d3e8d9 100644 --- a/Sluggo/Storyboards/Main.storyboard +++ b/Sluggo/Storyboards/Main.storyboard @@ -25,7 +25,7 @@ - + @@ -47,8 +47,7 @@ - - + @@ -90,7 +90,7 @@ - + diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index c598b14..978a546 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -26,15 +26,23 @@ class LoginViewController: UIViewController { let userString = username.text let passString = password.text - print("User:", userString!) - print("Password:", passString!) + //print("User:", userString!) + //print("Password:", passString!) - loginMethod(userString!, passString!) + //loginMethod(userString!, passString!) - // TODO: programatically segue to root view + if(userString!.isEmpty || passString!.isEmpty) { + // login Error + print("Login Error") + return + } else { + print("Login Success") + self.performSegue(withIdentifier: "loginToRoot", sender: self) + } } func loginMethod(_ user:String, _ password:String) { + let params = ["username":user, "password":password] as Dictionary var request = URLRequest(url: URL(string: Config.URL_BASE + "auth/login/")!) @@ -53,6 +61,20 @@ class LoginViewController: UIViewController { }) task.resume() + } + + func transitionToHome() { + + /* + let rViewController = storyboard?.instantiateViewController(identifier: "rVC") as? RootViewController + + view.window?.rootViewController = rViewController + view.window?.makeKeyAndVisible() + */ + + + } + } From b9674514840d7b7d2371b0a74dc074e7418e537c Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Fri, 23 Apr 2021 00:08:37 -0700 Subject: [PATCH 030/224] computed properties over setters and getters, remove optionals --- Sluggo/Models/AppIdentity.swift | 43 ++++++++----------- Sluggo/Models/JsonLoader.swift | 6 ++- Sluggo/Models/Managers/MemberManager.swift | 16 +++---- Sluggo/Models/Managers/TeamManager.swift | 4 +- Sluggo/Models/Managers/TicketManager.swift | 8 ++-- Sluggo/Models/Managers/UserManager.swift | 4 +- .../LoginViewController.swift | 2 +- 7 files changed, 38 insertions(+), 45 deletions(-) diff --git a/Sluggo/Models/AppIdentity.swift b/Sluggo/Models/AppIdentity.swift index fd51f9d..f343c5c 100644 --- a/Sluggo/Models/AppIdentity.swift +++ b/Sluggo/Models/AppIdentity.swift @@ -8,37 +8,28 @@ import Foundation class AppIdentity { - private var authenticatedUser: UserRecord - private var team: TeamRecord - private var token: String + private var _authenticatedUser: UserRecord + private var _team: TeamRecord + private var _token: String - init(_ authenticatedUser: UserRecord, _ team: TeamRecord, _ token: String) { - self.authenticatedUser = authenticatedUser - self.team = team - self.token = token - } - - public func getAuthenticatedUser() -> UserRecord { - return authenticatedUser - } - - public func getTeam() -> TeamRecord { - return team + var authenticatedUser: UserRecord { + get { return _authenticatedUser } + set { _authenticatedUser = newValue } } - - public func getToken() -> String { - return token - } - - public func setAuthenticatedUser(_ authenticatedUser: UserRecord) { - self.authenticatedUser = authenticatedUser + + var team: TeamRecord { + get { return _team } + set { _team = newValue } } - public func setTeam(_ team: TeamRecord) { - self.team = team + var token: String { + get { return _token } + set { _token = newValue } } - public func setToken(_ token: String) { - self.token = token + init(_ authenticatedUser: UserRecord, _ team: TeamRecord, _ token: String) { + self._authenticatedUser = authenticatedUser + self._team = team + self._token = token } } diff --git a/Sluggo/Models/JsonLoader.swift b/Sluggo/Models/JsonLoader.swift index 3db198e..c2b1710 100644 --- a/Sluggo/Models/JsonLoader.swift +++ b/Sluggo/Models/JsonLoader.swift @@ -37,7 +37,7 @@ class JsonLoader { return jsonData } - static func executeCodableRequest(request: URLRequest) throws -> T? { + static func executeCodableRequest(request: URLRequest) throws -> T { let session = URLSession.shared let semaphore = DispatchSemaphore(value: 0) @@ -56,6 +56,8 @@ class JsonLoader { if (resp.statusCode <= 299 && resp.statusCode >= 200) { if let fetchedData = data { record = JsonLoader.decode(fetchedData) + } else { + errorMessage = "Could not decode data!" } } else { if let fetchedData = data { @@ -74,6 +76,6 @@ class JsonLoader { throw RESTException.FailedRequest(message: message) } - return record + return record! } } diff --git a/Sluggo/Models/Managers/MemberManager.swift b/Sluggo/Models/Managers/MemberManager.swift index 976d743..67e7eea 100644 --- a/Sluggo/Models/Managers/MemberManager.swift +++ b/Sluggo/Models/Managers/MemberManager.swift @@ -17,37 +17,37 @@ class MemberManager { } private func makeDetailUrl(_ memberRecord: MemberRecord) -> String { - return config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.getTeam().id)" + "/members/" + "\(memberRecord.id)/" + return config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.team.id)" + "/members/" + "\(memberRecord.id)/" } private func makeListUrl() -> String { - return config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.getTeam().id)" + "/members/" + return config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.team.id)" + "/members/" } - public func fetchMemberRecord() throws -> MemberRecord? { + public func fetchMemberRecord() throws -> MemberRecord { var request = URLRequest(url: URL(string: makeListUrl())!) request.httpMethod = "GET" - request.setValue("Bearer \(self.identity.getToken())", forHTTPHeaderField: "Authorization") + request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") return try JsonLoader.executeCodableRequest(request: request) } - public func updateMemberRecord(_ memberRecord: MemberRecord) throws -> MemberRecord? { + public func updateMemberRecord(_ memberRecord: MemberRecord) throws -> MemberRecord { var request = URLRequest(url: URL(string: makeDetailUrl(memberRecord))!) request.httpMethod = "Put" request.httpBody = JsonLoader.encode(memberRecord) - request.setValue("Bearer \(self.identity.getToken())", forHTTPHeaderField: "Authorization") + request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") return try JsonLoader.executeCodableRequest(request: request) } - public func listTeamMembers() throws -> PaginatedList? { + public func listTeamMembers() throws -> PaginatedList { var request = URLRequest(url: URL(string: makeListUrl())!) request.httpMethod = "GET" - request.setValue("Bearer \(self.identity.getToken())", forHTTPHeaderField: "Authorization") + request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") return try JsonLoader.executeCodableRequest(request: request) diff --git a/Sluggo/Models/Managers/TeamManager.swift b/Sluggo/Models/Managers/TeamManager.swift index 8a98cfd..7311d02 100644 --- a/Sluggo/Models/Managers/TeamManager.swift +++ b/Sluggo/Models/Managers/TeamManager.swift @@ -18,11 +18,11 @@ class TeamManager { self.config = config } - public func listUserTeams() throws -> PaginatedList? { + public func listUserTeams() throws -> PaginatedList { var request = URLRequest(url: URL(string: config.getValue(Config.kURL)! + TeamManager.urlBase)!) request.httpMethod = "GET" - request.setValue("Bearer \(self.identity.getToken())", forHTTPHeaderField: "Authorization") + request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") return try JsonLoader.executeCodableRequest(request: request) diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index 12cb90a..dcdecc3 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -17,18 +17,18 @@ class TicketManager { } private func makeDetailUrl(_ ticketRecord: TicketRecord) -> String { - return config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.getTeam().id)" + "/tickets/" + "\(ticketRecord.id)/" + return config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.team.id)" + "/tickets/" + "\(ticketRecord.id)/" } private func makeListUrl() -> String { - return config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.getTeam().id)" + "/tickets/" + return config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.team.id)" + "/tickets/" } - public func updateTicket(_ ticket: TicketRecord) throws -> TicketRecord? { + public func updateTicket(_ ticket: TicketRecord) throws -> TicketRecord { var request = URLRequest(url: URL(string: makeDetailUrl(ticket))!) request.httpMethod = "PUT" request.httpBody = JsonLoader.encode(ticket) - request.setValue("Bearer \(self.identity.getToken())", forHTTPHeaderField: "Authorization") + request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") return try JsonLoader.executeCodableRequest(request: request) diff --git a/Sluggo/Models/Managers/UserManager.swift b/Sluggo/Models/Managers/UserManager.swift index a4432be..42d0424 100644 --- a/Sluggo/Models/Managers/UserManager.swift +++ b/Sluggo/Models/Managers/UserManager.swift @@ -14,7 +14,7 @@ class UserManager { self.config = config } - public func doLogin(username: String, password: String) throws -> TokenRecord? { + public func doLogin(username: String, password: String) throws -> TokenRecord { let params = ["username":username, "password":password] as Dictionary var request = URLRequest(url: URL(string: config.getValue(Config.kURL)! + "auth/login/")!) request.httpMethod = "POST" @@ -23,7 +23,7 @@ class UserManager { return try JsonLoader.executeCodableRequest(request: request) } - public func doLogout(token: String) throws -> ErrorMessage? { // TODO: this is probably incorrect + public func doLogout(token: String) throws -> ErrorMessage { // TODO: this is probably incorrect var request = URLRequest(url: URL(string: config.getValue(Config.kURL)! + "auth/logout/")!) request.httpMethod = "POST" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index 615e054..b1df062 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -66,7 +66,7 @@ class LoginViewController: UIViewController { let userManager = UserManager(config, token: "asdf") do { let request = try userManager.doLogin(username: user, password: password) - print(request?.key) + print(request.key) } catch RESTException.FailedRequest(let message) { // more would need to be done here. DispatchQueue.main.async { let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert) From 9a16ef56476e4a78cf5a24dea0aeab34796b4a9b Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Fri, 23 Apr 2021 12:35:45 -0700 Subject: [PATCH 031/224] Swapped content type in Storyboard to prevent spellchecking on username --- Sluggo.xcodeproj/project.pbxproj | 12 ++++++++++++ Sluggo/Storyboards/Main.storyboard | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 67dfa3b..4d84f87 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -584,6 +584,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = LUQ6CX5JDC; INFOPLIST_FILE = Sluggo/Info.plist; @@ -594,6 +595,7 @@ MARKETING_VERSION = 0.1; PRODUCT_BUNDLE_IDENTIFIER = ex.sluggotrack.Sluggo; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; @@ -604,6 +606,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = LUQ6CX5JDC; INFOPLIST_FILE = Sluggo/Info.plist; @@ -614,6 +617,7 @@ MARKETING_VERSION = 0.1; PRODUCT_BUNDLE_IDENTIFIER = ex.sluggotrack.Sluggo; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; @@ -667,6 +671,8 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = LUQ6CX5JDC; INFOPLIST_FILE = SluggoUITests/Info.plist; @@ -677,6 +683,8 @@ ); PRODUCT_BUNDLE_IDENTIFIER = ex.sluggotrack.SluggoUITests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = Sluggo; @@ -687,6 +695,8 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = LUQ6CX5JDC; INFOPLIST_FILE = SluggoUITests/Info.plist; @@ -697,6 +707,8 @@ ); PRODUCT_BUNDLE_IDENTIFIER = ex.sluggotrack.SluggoUITests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = Sluggo; diff --git a/Sluggo/Storyboards/Main.storyboard b/Sluggo/Storyboards/Main.storyboard index 8d3e8d9..231cd40 100644 --- a/Sluggo/Storyboards/Main.storyboard +++ b/Sluggo/Storyboards/Main.storyboard @@ -28,12 +28,12 @@ - + - + + + + + + + + + + @@ -79,13 +117,14 @@ + - + @@ -116,6 +155,9 @@ + + + diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index de32004..8387bc7 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -11,6 +11,7 @@ class LoginViewController: UIViewController { @IBOutlet weak var username: UITextField! @IBOutlet weak var password: UITextField! + @IBOutlet weak var persistButton: UIButton! private var config: Config private var token: String @@ -34,7 +35,7 @@ class LoginViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - + persistButton.setImage(UIImage(systemName: "checkmark.square.fill"), for: [.highlighted, .selected]) } @IBAction func loginButton(_ sender: Any) { @@ -77,7 +78,12 @@ class LoginViewController: UIViewController { } }) } - + @IBAction func persistLoginButton(_ sender: UIButton) { + sender.isSelected.toggle() + + //print(sender.isSelected) + } + /* func transitionToHome() { /* @@ -88,4 +94,5 @@ class LoginViewController: UIViewController { */ } + */ } From b08affa2d294f5b83ddc0b99a319250aeba1746c Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Sun, 25 Apr 2021 13:14:26 -0700 Subject: [PATCH 037/224] Updated all managers to results. --- Sluggo/Models/JsonLoader.swift | 4 +- Sluggo/Models/Managers/MemberManager.swift | 62 ++++++++++++---------- Sluggo/Models/Managers/TeamManager.swift | 18 +++---- Sluggo/Models/Managers/TicketManager.swift | 25 +++++---- Sluggo/Models/Managers/UserManager.swift | 14 ++--- 5 files changed, 68 insertions(+), 55 deletions(-) diff --git a/Sluggo/Models/JsonLoader.swift b/Sluggo/Models/JsonLoader.swift index 13286d1..08b673b 100644 --- a/Sluggo/Models/JsonLoader.swift +++ b/Sluggo/Models/JsonLoader.swift @@ -12,12 +12,12 @@ class JsonLoader { // Attempt decoding let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 - + guard let pagRec = try? decoder.decode(T.self, from: data) else { print("Failed to decode JSON data into object representation for object initialization.") return nil } - + return pagRec } diff --git a/Sluggo/Models/Managers/MemberManager.swift b/Sluggo/Models/Managers/MemberManager.swift index d2b3a4d..211165d 100644 --- a/Sluggo/Models/Managers/MemberManager.swift +++ b/Sluggo/Models/Managers/MemberManager.swift @@ -24,32 +24,38 @@ class MemberManager { return config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.team.id)" + "/members/" } -// public func fetchMemberRecord() throws -> MemberRecord { -// var request = URLRequest(url: URL(string: makeListUrl())!) -// request.httpMethod = "GET" -// request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") -// request.addValue("application/json", forHTTPHeaderField: "Content-Type") -// -// return try JsonLoader.executeCodableRequest(request: request) -// } -// -// public func updateMemberRecord(_ memberRecord: MemberRecord) throws -> MemberRecord { -// var request = URLRequest(url: URL(string: makeDetailUrl(memberRecord))!) -// request.httpMethod = "Put" -// request.httpBody = JsonLoader.encode(memberRecord) -// request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") -// request.addValue("application/json", forHTTPHeaderField: "Content-Type") -// -// return try JsonLoader.executeCodableRequest(request: request) -// -// } -// -// public func listTeamMembers() throws -> PaginatedList { -// var request = URLRequest(url: URL(string: makeListUrl())!) -// request.httpMethod = "GET" -// request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") -// request.addValue("application/json", forHTTPHeaderField: "Content-Type") -// -// return try JsonLoader.executeCodableRequest(request: request) -// } + public func fetchMemberRecord(completionHandler: @escaping(Result) -> Void) -> Void { + var request = URLRequest(url: URL(string: makeListUrl())!) + request.httpMethod = "GET" + request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + JsonLoader.executeCodableRequest(request: request, completionHandler: completionHandler) + } + + public func updateMemberRecord(_ memberRecord: MemberRecord, completionHandler: @escaping(Result) -> Void) -> Void { + var request = URLRequest(url: URL(string: makeDetailUrl(memberRecord))!) + request.httpMethod = "Put" + + guard let body = JsonLoader.encode(memberRecord) else { + completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize member JSON for updateMemberRecord in MemberManager"))) + return + } + + request.httpBody = body + request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + JsonLoader.executeCodableRequest(request: request, completionHandler: completionHandler) + + } + + public func listTeamMembers(completionHandler: @escaping(Result, Error>) -> Void) -> Void{ + var request = URLRequest(url: URL(string: makeListUrl())!) + request.httpMethod = "GET" + request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + JsonLoader.executeCodableRequest(request: request, completionHandler: completionHandler) + } } diff --git a/Sluggo/Models/Managers/TeamManager.swift b/Sluggo/Models/Managers/TeamManager.swift index 6986d73..20fb3d3 100644 --- a/Sluggo/Models/Managers/TeamManager.swift +++ b/Sluggo/Models/Managers/TeamManager.swift @@ -18,13 +18,13 @@ class TeamManager { self.config = config } -// public func listUserTeams() throws -> PaginatedList { -// -// var request = URLRequest(url: URL(string: config.getValue(Config.kURL)! + TeamManager.urlBase)!) -// request.httpMethod = "GET" -// request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") -// request.addValue("application/json", forHTTPHeaderField: "Content-Type") -// -// return try JsonLoader.executeCodableRequest(request: request) -// } + public func listUserTeams(completionHandler: @escaping(Result , Error>) -> Void) -> Void { + + var request = URLRequest(url: URL(string: config.getValue(Config.kURL)! + TeamManager.urlBase)!) + request.httpMethod = "GET" + request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + JsonLoader.executeCodableRequest(request: request, completionHandler: completionHandler) + } } diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index a72fe01..416c9f7 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -24,13 +24,20 @@ class TicketManager { return config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.team.id)" + "/tickets/" } -// public func updateTicket(_ ticket: TicketRecord) throws -> TicketRecord { -// var request = URLRequest(url: URL(string: makeDetailUrl(ticket))!) -// request.httpMethod = "PUT" -// request.httpBody = JsonLoader.encode(ticket) -// request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") -// request.addValue("application/json", forHTTPHeaderField: "Content-Type") -// -// return try JsonLoader.executeCodableRequest(request: request) -// } + public func updateTicket(_ ticket: TicketRecord, completionHandler: @escaping(Result) -> Void)-> Void { + var request = URLRequest(url: URL(string: makeDetailUrl(ticket))!) + request.httpMethod = "PUT" + + guard let body = JsonLoader.encode(ticket) else { + completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize ticket JSON for updateTicket in Ticket"))) + return + } + + request.httpBody = body + request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + JsonLoader.executeCodableRequest(request: request, completionHandler: completionHandler) + + } } diff --git a/Sluggo/Models/Managers/UserManager.swift b/Sluggo/Models/Managers/UserManager.swift index 2e7159b..8b2244e 100644 --- a/Sluggo/Models/Managers/UserManager.swift +++ b/Sluggo/Models/Managers/UserManager.swift @@ -27,11 +27,11 @@ class UserManager { JsonLoader.executeCodableRequest(request: request, completionHandler: completionHandler) } -// public func doLogout(token: String) throws -> ErrorMessage { // TODO: this is probably incorrect -// var request = URLRequest(url: URL(string: config.getValue(Config.kURL)! + "auth/logout/")!) -// request.httpMethod = "POST" -// request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") -// request.addValue("application/json", forHTTPHeaderField: "Content-Type") -// return try JsonLoader.executeCodableRequest(request: request) -// } + // public func doLogout(token: String) throws -> ErrorMessage { // TODO: this is probably incorrect + // var request = URLRequest(url: URL(string: config.getValue(Config.kURL)! + "auth/logout/")!) + // request.httpMethod = "POST" + // request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + // request.addValue("application/json", forHTTPHeaderField: "Content-Type") + // return try JsonLoader.executeCodableRequest(request: request) + // } } From 85c8fa9c554fddb630bd9753d91180c86c034f89 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Sun, 25 Apr 2021 13:16:17 -0700 Subject: [PATCH 038/224] adjusted spacing and fixed error message. --- Sluggo/Models/Managers/TeamManager.swift | 2 +- Sluggo/Models/Managers/TicketManager.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sluggo/Models/Managers/TeamManager.swift b/Sluggo/Models/Managers/TeamManager.swift index 20fb3d3..6debb1b 100644 --- a/Sluggo/Models/Managers/TeamManager.swift +++ b/Sluggo/Models/Managers/TeamManager.swift @@ -18,7 +18,7 @@ class TeamManager { self.config = config } - public func listUserTeams(completionHandler: @escaping(Result , Error>) -> Void) -> Void { + public func listUserTeams(completionHandler: @escaping(Result, Error>) -> Void) -> Void { var request = URLRequest(url: URL(string: config.getValue(Config.kURL)! + TeamManager.urlBase)!) request.httpMethod = "GET" diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index 416c9f7..758c5a1 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -29,7 +29,7 @@ class TicketManager { request.httpMethod = "PUT" guard let body = JsonLoader.encode(ticket) else { - completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize ticket JSON for updateTicket in Ticket"))) + completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize ticket JSON for updateTicket in TicketManager"))) return } From d47e793d425e5903ddc1873ac145acfe36f29693 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Sun, 25 Apr 2021 13:45:29 -0700 Subject: [PATCH 039/224] Adjusted completion handler in loginMethod. --- Sluggo/View Controllers/LoginViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index de32004..df573e1 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -64,7 +64,7 @@ class LoginViewController: UIViewController { func loginMethod(_ user:String, _ password:String) { let userManager = UserManager(config, token: "asdf") - userManager.doLogin(username: user, password: password, completionHandler: { (result: Result) -> Void in + userManager.doLogin(username: user, password: password) { result in switch(result) { case .success(let record): print(record.key) @@ -75,7 +75,7 @@ class LoginViewController: UIViewController { self.present(alert, animated: true, completion: nil) } } - }) + } } func transitionToHome() { From 07ba66992d93b020b6f141d5447f95963b646828 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Sun, 25 Apr 2021 14:21:06 -0700 Subject: [PATCH 040/224] Added LogoutMessage codable and implement doLogout --- Sluggo.xcodeproj/project.pbxproj | 4 ++++ Sluggo/Models/Codables/LogoutMessage.swift | 12 ++++++++++++ Sluggo/Models/Managers/UserManager.swift | 14 +++++++------- 3 files changed, 23 insertions(+), 7 deletions(-) create mode 100644 Sluggo/Models/Codables/LogoutMessage.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index e6f9028..49374e7 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 2F3AD7A82627C89B00B61A97 /* Member.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A72627C89B00B61A97 /* Member.swift */; }; 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7AC2627C8EF00B61A97 /* Team.swift */; }; 2F3F66E2262DD3BA0040404C /* JsonLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3F66E1262DD3BA0040404C /* JsonLoader.swift */; }; + 2FD70D5D2636144300D13C75 /* LogoutMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FD70D5C2636144300D13C75 /* LogoutMessage.swift */; }; 2FEF5DA9262D0E7C00434763 /* PaginatedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */; }; 2FEF5DAE262D1C4900434763 /* MemberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DAD262D1C4900434763 /* MemberTests.swift */; }; 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A79FFE0262E562A00D04320 /* Main.storyboard */; }; @@ -68,6 +69,7 @@ 2F3AD7A72627C89B00B61A97 /* Member.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Member.swift; sourceTree = ""; }; 2F3AD7AC2627C8EF00B61A97 /* Team.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Team.swift; sourceTree = ""; }; 2F3F66E1262DD3BA0040404C /* JsonLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JsonLoader.swift; sourceTree = ""; }; + 2FD70D5C2636144300D13C75 /* LogoutMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutMessage.swift; sourceTree = ""; }; 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginatedList.swift; sourceTree = ""; }; 2FEF5DAD262D1C4900434763 /* MemberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberTests.swift; sourceTree = ""; }; 5A79FFE0262E562A00D04320 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; @@ -155,6 +157,7 @@ 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */, 5AB1C8FB262FF1690010F85E /* TicketRecord.swift */, 5ACC0F002630CA1400DCF83E /* ErrorMessage.swift */, + 2FD70D5C2636144300D13C75 /* LogoutMessage.swift */, ); path = Codables; sourceTree = ""; @@ -385,6 +388,7 @@ 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */, 5AB1C8FC262FF1690010F85E /* TicketRecord.swift in Sources */, 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */, + 2FD70D5D2636144300D13C75 /* LogoutMessage.swift in Sources */, 5A79FFEF262E600B00D04320 /* Config.swift in Sources */, 5AB1C8EA262FE2EF0010F85E /* MemberManager.swift in Sources */, 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */, diff --git a/Sluggo/Models/Codables/LogoutMessage.swift b/Sluggo/Models/Codables/LogoutMessage.swift new file mode 100644 index 0000000..856e7ea --- /dev/null +++ b/Sluggo/Models/Codables/LogoutMessage.swift @@ -0,0 +1,12 @@ +// +// LogoutMessage.swift +// Sluggo +// +// Created by Andrew Gavgavian on 4/25/21. +// + +import Foundation + +struct LogoutMessage: Codable { + var detail: String +} diff --git a/Sluggo/Models/Managers/UserManager.swift b/Sluggo/Models/Managers/UserManager.swift index 8b2244e..df84895 100644 --- a/Sluggo/Models/Managers/UserManager.swift +++ b/Sluggo/Models/Managers/UserManager.swift @@ -27,11 +27,11 @@ class UserManager { JsonLoader.executeCodableRequest(request: request, completionHandler: completionHandler) } - // public func doLogout(token: String) throws -> ErrorMessage { // TODO: this is probably incorrect - // var request = URLRequest(url: URL(string: config.getValue(Config.kURL)! + "auth/logout/")!) - // request.httpMethod = "POST" - // request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") - // request.addValue("application/json", forHTTPHeaderField: "Content-Type") - // return try JsonLoader.executeCodableRequest(request: request) - // } + public func doLogout(token: String, completionHandler: @escaping(Result) -> Void) -> Void { // TODO: this is probably incorrect + var request = URLRequest(url: URL(string: config.getValue(Config.kURL)! + "auth/logout/")!) + request.httpMethod = "POST" + request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + JsonLoader.executeCodableRequest(request: request, completionHandler: completionHandler) + } } From 4209cd0c44b1bc8894f853db03f8bbdc08f31e20 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Sun, 25 Apr 2021 16:08:57 -0700 Subject: [PATCH 041/224] Added modal view for login --- Sluggo.xcodeproj/project.pbxproj | 4 ++ Sluggo/Models/Constants.swift | 5 -- Sluggo/Storyboards/Main.storyboard | 71 ++++++++++++------- .../LaunchViewController.swift | 35 +++++++++ .../LoginViewController.swift | 17 ++--- 5 files changed, 87 insertions(+), 45 deletions(-) create mode 100644 Sluggo/View Controllers/LaunchViewController.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index e6f9028..463d9d3 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 051AAB0B2630FD6500BCB9C7 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 051AAB0A2630FD6500BCB9C7 /* Constants.swift */; }; + 052258702636100B00B49CE4 /* LaunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0522586F2636100B00B49CE4 /* LaunchViewController.swift */; }; 2F2247AC262920AC006C6EE6 /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2247AB262920AC006C6EE6 /* Token.swift */; }; 2F3AD7A32627C86A00B61A97 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A22627C86A00B61A97 /* User.swift */; }; 2F3AD7A82627C89B00B61A97 /* Member.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A72627C89B00B61A97 /* Member.swift */; }; @@ -63,6 +64,7 @@ /* Begin PBXFileReference section */ 051AAB0A2630FD6500BCB9C7 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + 0522586F2636100B00B49CE4 /* LaunchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchViewController.swift; sourceTree = ""; }; 2F2247AB262920AC006C6EE6 /* Token.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = ""; }; 2F3AD7A22627C86A00B61A97 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; 2F3AD7A72627C89B00B61A97 /* Member.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Member.swift; sourceTree = ""; }; @@ -240,6 +242,7 @@ 7DC2A2202629442B0002CEE6 /* View Controllers */ = { isa = PBXGroup; children = ( + 0522586F2636100B00B49CE4 /* LaunchViewController.swift */, 5A79FFE5262E563F00D04320 /* LoginViewController.swift */, 7DC2A2212629447D0002CEE6 /* SluggoSidebarTableViewController.swift */, 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */, @@ -395,6 +398,7 @@ 7D1DA6AE262B82C200E7C12D /* RootViewController.swift in Sources */, 5ACC0F012630CA1400DCF83E /* ErrorMessage.swift in Sources */, 2F3AD7A32627C86A00B61A97 /* User.swift in Sources */, + 052258702636100B00B49CE4 /* LaunchViewController.swift in Sources */, 7DC2A2222629447D0002CEE6 /* SluggoSidebarTableViewController.swift in Sources */, 5ACC0F062630CA2700DCF83E /* Exceptions.swift in Sources */, 5AB1C8F7262FE3520010F85E /* AppIdentity.swift in Sources */, diff --git a/Sluggo/Models/Constants.swift b/Sluggo/Models/Constants.swift index 4e0992b..2e6f7fb 100644 --- a/Sluggo/Models/Constants.swift +++ b/Sluggo/Models/Constants.swift @@ -9,9 +9,4 @@ import Foundation struct Constants { - struct Storyboard { - - static let rootVC = "rVC" - - } } diff --git a/Sluggo/Storyboards/Main.storyboard b/Sluggo/Storyboards/Main.storyboard index 377dd78..3b2f3d1 100644 --- a/Sluggo/Storyboards/Main.storyboard +++ b/Sluggo/Storyboards/Main.storyboard @@ -1,5 +1,5 @@ - + @@ -8,12 +8,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -30,17 +61,17 @@ - + - + - + + - + + + @@ -154,6 +157,11 @@ + + + + + diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index 280e0e2..ebb31a6 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -81,9 +81,8 @@ class LoginViewController: UIViewController { } } } - @IBAction func persistLoginButton(_ sender: UIButton) { - sender.isSelected.toggle() - - print(sender.isSelected) + @IBAction func persistLoginButton(_ sender: Any) { + self.persistButton.isSelected.toggle() + print(self.persistButton.isSelected) } } From 0b834d49b57f343bced3a93ecd7811bf980227b5 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Sun, 25 Apr 2021 16:55:48 -0700 Subject: [PATCH 043/224] First pass at persistence functions, nonworking --- Sluggo/Models/AppIdentity.swift | 59 ++++++++++++++++--- Sluggo/Models/Constants.swift | 6 ++ .../LoginViewController.swift | 5 +- 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/Sluggo/Models/AppIdentity.swift b/Sluggo/Models/AppIdentity.swift index 775860b..88abdd8 100644 --- a/Sluggo/Models/AppIdentity.swift +++ b/Sluggo/Models/AppIdentity.swift @@ -7,14 +7,57 @@ import Foundation -struct AppIdentity { - var authenticatedUser: UserRecord - var team: TeamRecord - var token: String +struct AppIdentity: Codable { + var authenticatedUser: UserRecord? + var team: TeamRecord? + var token: String? - init(authenticatedUser: UserRecord, team: TeamRecord, token: String) { - self.authenticatedUser = authenticatedUser - self.team = team - self.token = token + init() { + print(AppIdentity.persistencePath) + } + + private static var persistencePath: URL { + get { + return URL(string: NSHomeDirectory() + Constants.FilePaths.persistencePath)! + } + } + + static func loadFromDisk() -> AppIdentity? { + guard let persistenceFileContents = try? String(contentsOf: persistencePath) else { + return nil + } + + guard let persistenceFileData = persistenceFileContents.data(using: .utf8) else { + return nil + } + + let appIdentity: AppIdentity? = JsonLoader.decode(persistenceFileData) + return appIdentity + } + + func saveToDisk() -> Bool { + guard let appIdentityData = JsonLoader.encode(self) else { + return false + } + + guard let appIdentityContents = String(data: appIdentityData, encoding: .utf8) else { + return false + } + + do { + try appIdentityContents.write(to: AppIdentity.persistencePath, atomically: false, encoding: .utf8) + return true + } catch { + return false + } + } + + static func deletePersistenceFile() -> Bool { + do { + try FileManager.default.removeItem(at: self.persistencePath) + return true + } catch { + return false + } } } diff --git a/Sluggo/Models/Constants.swift b/Sluggo/Models/Constants.swift index 2e6f7fb..7db460c 100644 --- a/Sluggo/Models/Constants.swift +++ b/Sluggo/Models/Constants.swift @@ -9,4 +9,10 @@ import Foundation struct Constants { + struct FilePaths { + + static let persistencePath = "/Library/sluggodata.json" + + } + } diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index ebb31a6..5f89c80 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -60,8 +60,11 @@ class LoginViewController: UIViewController { return } else { print("Login Success") - self.performSegue(withIdentifier: "loginToRoot", sender: self) + // MARK: Persistence + + + self.performSegue(withIdentifier: "loginToRoot", sender: self) } } From 8793be016cab98f4d06a33be439e404435347ee8 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Sun, 25 Apr 2021 17:03:18 -0700 Subject: [PATCH 044/224] Changes to models managers for tickets and members There are some force unwraps introduced, which should theoretically work as long as we don't call things without having verified the correctness of the server URL and without having no team set. That should work since we need to ensure that anyways, but we need to be careful about it. --- Sluggo/Models/Managers/MemberManager.swift | 14 +++++++------- Sluggo/Models/Managers/TicketManager.swift | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Sluggo/Models/Managers/MemberManager.swift b/Sluggo/Models/Managers/MemberManager.swift index 211165d..ab2cdd9 100644 --- a/Sluggo/Models/Managers/MemberManager.swift +++ b/Sluggo/Models/Managers/MemberManager.swift @@ -16,16 +16,16 @@ class MemberManager { self.config = config } - private func makeDetailUrl(_ memberRecord: MemberRecord) -> String { - return config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.team.id)" + "/members/" + "\(memberRecord.id)/" + private func makeDetailUrl(memberRecord: MemberRecord) -> URL { + return URL(string: config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.team!.id)" + "/members/" + "\(memberRecord.id)/")! } - private func makeListUrl() -> String { - return config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.team.id)" + "/members/" + private func makeListUrl() -> URL { + return URL(string: config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.team!.id)" + "/members/")! } public func fetchMemberRecord(completionHandler: @escaping(Result) -> Void) -> Void { - var request = URLRequest(url: URL(string: makeListUrl())!) + var request = URLRequest(url: makeListUrl()) request.httpMethod = "GET" request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") @@ -34,7 +34,7 @@ class MemberManager { } public func updateMemberRecord(_ memberRecord: MemberRecord, completionHandler: @escaping(Result) -> Void) -> Void { - var request = URLRequest(url: URL(string: makeDetailUrl(memberRecord))!) + var request = URLRequest(url: makeDetailUrl(memberRecord: memberRecord)) request.httpMethod = "Put" guard let body = JsonLoader.encode(memberRecord) else { @@ -51,7 +51,7 @@ class MemberManager { } public func listTeamMembers(completionHandler: @escaping(Result, Error>) -> Void) -> Void{ - var request = URLRequest(url: URL(string: makeListUrl())!) + var request = URLRequest(url: makeListUrl()) request.httpMethod = "GET" request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index 758c5a1..9ef4222 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -16,16 +16,16 @@ class TicketManager { self.config = config } - private func makeDetailUrl(_ ticketRecord: TicketRecord) -> String { - return config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.team.id)" + "/tickets/" + "\(ticketRecord.id)/" + private func makeDetailUrl(_ ticketRecord: TicketRecord) -> URL { + return URL(string: config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.team!.id)" + "/tickets/" + "\(ticketRecord.id)/")! } - private func makeListUrl() -> String { - return config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.team.id)" + "/tickets/" + private func makeListUrl() -> URL { + return URL(string: config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.team!.id)" + "/tickets/")! } public func updateTicket(_ ticket: TicketRecord, completionHandler: @escaping(Result) -> Void)-> Void { - var request = URLRequest(url: URL(string: makeDetailUrl(ticket))!) + var request = URLRequest(url: makeDetailUrl(ticket)) request.httpMethod = "PUT" guard let body = JsonLoader.encode(ticket) else { From c7b41bc08d64d7adbbb71d8357b3d01465ea7dea Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Sun, 25 Apr 2021 17:45:32 -0700 Subject: [PATCH 045/224] Major changes to enable better login flow. Removed Config. Config should be bundled with AppIdentity. I made a lot of changes to facilitate this. This also includes some changes to login flow which should enable persistence. Theoretically, this is writing to disk, but it hasn't been tested yet. --- Sluggo.xcodeproj/project.pbxproj | 4 -- Sluggo/AppDelegate.swift | 2 + Sluggo/Models/AppIdentity.swift | 17 +++++- Sluggo/Models/Config.swift | 21 ------- Sluggo/Models/Constants.swift | 6 ++ Sluggo/Models/Managers/MemberManager.swift | 8 +-- Sluggo/Models/Managers/TeamManager.swift | 7 +-- Sluggo/Models/Managers/TicketManager.swift | 8 +-- Sluggo/Models/Managers/UserManager.swift | 10 +-- .../LoginViewController.swift | 61 ++++++++++--------- 10 files changed, 70 insertions(+), 74 deletions(-) delete mode 100644 Sluggo/Models/Config.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 8e0ceee..0e68269 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -19,7 +19,6 @@ 2FEF5DAE262D1C4900434763 /* MemberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DAD262D1C4900434763 /* MemberTests.swift */; }; 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A79FFE0262E562A00D04320 /* Main.storyboard */; }; 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A79FFE5262E563F00D04320 /* LoginViewController.swift */; }; - 5A79FFEF262E600B00D04320 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A79FFEE262E600B00D04320 /* Config.swift */; }; 5AB1C8DD262FE2DD0010F85E /* TeamManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8DC262FE2DD0010F85E /* TeamManager.swift */; }; 5AB1C8E5262FE2E60010F85E /* TicketManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8E4262FE2E60010F85E /* TicketManager.swift */; }; 5AB1C8EA262FE2EF0010F85E /* MemberManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8E9262FE2EF0010F85E /* MemberManager.swift */; }; @@ -76,7 +75,6 @@ 2FEF5DAD262D1C4900434763 /* MemberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberTests.swift; sourceTree = ""; }; 5A79FFE0262E562A00D04320 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 5A79FFE5262E563F00D04320 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; - 5A79FFEE262E600B00D04320 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; 5AB1C8DC262FE2DD0010F85E /* TeamManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamManager.swift; sourceTree = ""; }; 5AB1C8E4262FE2E60010F85E /* TicketManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketManager.swift; sourceTree = ""; }; 5AB1C8E9262FE2EF0010F85E /* MemberManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberManager.swift; sourceTree = ""; }; @@ -141,7 +139,6 @@ 2F3F66E1262DD3BA0040404C /* JsonLoader.swift */, 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */, 5A79FFED262E5FEF00D04320 /* Codables */, - 5A79FFEE262E600B00D04320 /* Config.swift */, 5AB1C8F6262FE3520010F85E /* AppIdentity.swift */, 051AAB0A2630FD6500BCB9C7 /* Constants.swift */, 5ACC0F052630CA2700DCF83E /* Exceptions.swift */, @@ -392,7 +389,6 @@ 5AB1C8FC262FF1690010F85E /* TicketRecord.swift in Sources */, 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */, 2FD70D5D2636144300D13C75 /* LogoutMessage.swift in Sources */, - 5A79FFEF262E600B00D04320 /* Config.swift in Sources */, 5AB1C8EA262FE2EF0010F85E /* MemberManager.swift in Sources */, 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */, 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */, diff --git a/Sluggo/AppDelegate.swift b/Sluggo/AppDelegate.swift index 31d7f59..7137b7d 100644 --- a/Sluggo/AppDelegate.swift +++ b/Sluggo/AppDelegate.swift @@ -14,6 +14,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. + + // TODO Persistence return true } diff --git a/Sluggo/Models/AppIdentity.swift b/Sluggo/Models/AppIdentity.swift index 88abdd8..7b580b4 100644 --- a/Sluggo/Models/AppIdentity.swift +++ b/Sluggo/Models/AppIdentity.swift @@ -7,10 +7,23 @@ import Foundation -struct AppIdentity: Codable { +class AppIdentity: Codable { var authenticatedUser: UserRecord? var team: TeamRecord? var token: String? + var instanceURLString: String? { + get { + return self.configData[Constants.Config.kURL] + } + + set(newURLString) { + self.configData[Constants.Config.kURL] = newURLString + } + } + + var configData: [String: String] = [ + Constants.Config.kURL: Constants.Config.URL_BASE // enable change if need be + ] init() { print(AppIdentity.persistencePath) @@ -60,4 +73,6 @@ struct AppIdentity: Codable { return false } } + + } diff --git a/Sluggo/Models/Config.swift b/Sluggo/Models/Config.swift deleted file mode 100644 index f7fba6b..0000000 --- a/Sluggo/Models/Config.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// Config.swift -// Sluggo -// -// Created by Samuel Schmidt on 4/19/21. -// - -import Foundation - -struct Config { - static let URL_BASE = "http://127.0.0.1:8000/" - static let kURL = "url" - - private var configData = [ - kURL: URL_BASE // enable change if need be - ] - - public func getValue(_ key: String) -> String? { - return configData[key] - } -} diff --git a/Sluggo/Models/Constants.swift b/Sluggo/Models/Constants.swift index 7db460c..570be79 100644 --- a/Sluggo/Models/Constants.swift +++ b/Sluggo/Models/Constants.swift @@ -15,4 +15,10 @@ struct Constants { } + struct Config { + + static let URL_BASE = "http://127.0.0.1:8000/" + static let kURL = "url" + + } } diff --git a/Sluggo/Models/Managers/MemberManager.swift b/Sluggo/Models/Managers/MemberManager.swift index ab2cdd9..2127ad7 100644 --- a/Sluggo/Models/Managers/MemberManager.swift +++ b/Sluggo/Models/Managers/MemberManager.swift @@ -9,19 +9,17 @@ import Foundation class MemberManager { private var identity: AppIdentity - private var config: Config - init(_ identity: AppIdentity, _ config: Config) { + init(_ identity: AppIdentity) { self.identity = identity - self.config = config } private func makeDetailUrl(memberRecord: MemberRecord) -> URL { - return URL(string: config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.team!.id)" + "/members/" + "\(memberRecord.id)/")! + return URL(string: identity.configData[Constants.Config.kURL]! + "/api/teams/" + "\(identity.team!.id)" + "/members/" + "\(memberRecord.id)/")! } private func makeListUrl() -> URL { - return URL(string: config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.team!.id)" + "/members/")! + return URL(string: identity.configData[Constants.Config.kURL]! + "/api/teams/" + "\(identity.team!.id)" + "/members/")! } public func fetchMemberRecord(completionHandler: @escaping(Result) -> Void) -> Void { diff --git a/Sluggo/Models/Managers/TeamManager.swift b/Sluggo/Models/Managers/TeamManager.swift index 6debb1b..2c9f4f9 100644 --- a/Sluggo/Models/Managers/TeamManager.swift +++ b/Sluggo/Models/Managers/TeamManager.swift @@ -10,17 +10,14 @@ import Foundation class TeamManager { static let urlBase = "/api/teams/" private var identity: AppIdentity - private var config: Config - - init(_ identity: AppIdentity, _ config: Config) { + init(identity: AppIdentity) { self.identity = identity - self.config = config } public func listUserTeams(completionHandler: @escaping(Result, Error>) -> Void) -> Void { - var request = URLRequest(url: URL(string: config.getValue(Config.kURL)! + TeamManager.urlBase)!) + var request = URLRequest(url: URL(string: identity.configData["instanceURL"]! + TeamManager.urlBase)!) request.httpMethod = "GET" request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index 9ef4222..e3f1175 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -9,19 +9,17 @@ import Foundation class TicketManager { private var identity: AppIdentity - private var config: Config - init(_ identity: AppIdentity, _ config: Config) { + init(_ identity: AppIdentity) { self.identity = identity - self.config = config } private func makeDetailUrl(_ ticketRecord: TicketRecord) -> URL { - return URL(string: config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.team!.id)" + "/tickets/" + "\(ticketRecord.id)/")! + return URL(string: identity.configData[Constants.Config.kURL]! + "/api/teams/" + "\(identity.team!.id)" + "/tickets/" + "\(ticketRecord.id)/")! } private func makeListUrl() -> URL { - return URL(string: config.getValue(Config.kURL)! + "/api/teams/" + "\(identity.team!.id)" + "/tickets/")! + return URL(string: identity.configData[Constants.Config.kURL]! + "/api/teams/" + "\(identity.team!.id)" + "/tickets/")! } public func updateTicket(_ ticket: TicketRecord, completionHandler: @escaping(Result) -> Void)-> Void { diff --git a/Sluggo/Models/Managers/UserManager.swift b/Sluggo/Models/Managers/UserManager.swift index df84895..e04c907 100644 --- a/Sluggo/Models/Managers/UserManager.swift +++ b/Sluggo/Models/Managers/UserManager.swift @@ -8,15 +8,15 @@ import Foundation class UserManager { - private var config: Config + private var identity: AppIdentity - init(_ config: Config, token: String?) { - self.config = config + init(identity: AppIdentity) { + self.identity = identity } public func doLogin(username: String, password: String, completionHandler: @escaping(Result) -> Void) -> Void { let params = ["username":username, "password":password] as Dictionary - var request = URLRequest(url: URL(string: config.getValue(Config.kURL)! + "auth/login/")!) + var request = URLRequest(url: URL(string: identity.configData[Constants.Config.kURL]! + "auth/login/")!) request.httpMethod = "POST" guard let body = try? JSONSerialization.data(withJSONObject: params, options: []) else { completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize JSON for doLogin in UserManager"))) @@ -28,7 +28,7 @@ class UserManager { } public func doLogout(token: String, completionHandler: @escaping(Result) -> Void) -> Void { // TODO: this is probably incorrect - var request = URLRequest(url: URL(string: config.getValue(Config.kURL)! + "auth/logout/")!) + var request = URLRequest(url: URL(string: identity.configData[Constants.Config.kURL]! + "auth/logout/")!) request.httpMethod = "POST" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index 5f89c80..bfb8025 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -12,8 +12,11 @@ class LoginViewController: UIViewController { @IBOutlet weak var username: UITextField! @IBOutlet weak var password: UITextField! @IBOutlet weak var persistButton: UIButton! - private var config: Config - private var token: String + + // TODO: AppIdentity be migrated when login flow progressed + // This should really be managed by the AppDelegate and passed into VCs along + // segues and otherwise. + let identity = AppIdentity() convenience init() { self.init(nibName:nil, bundle:nil) @@ -21,15 +24,11 @@ class LoginViewController: UIViewController { // This extends the superclass. override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { - self.config = Config() - self.token = "asdf" // will need to be changed super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) } // This is also necessary when extending the superclass. required init?(coder aDecoder: NSCoder) { - self.config = Config() - self.token = "asdf" // will need to be changed super.init(coder: aDecoder) } @@ -40,41 +39,46 @@ class LoginViewController: UIViewController { } @IBAction func loginButton(_ sender: Any) { - - - // Sanitize input? - let userString = username.text let passString = password.text - //print("User:", userString!) - //print("Password:", passString!) - - DispatchQueue.global(qos: .userInitiated).async { - self.loginMethod(userString!, passString!) - } - if(userString!.isEmpty || passString!.isEmpty) { // login Error - print("Login Error") + print("No username or password provided, not attempting login") return } else { - print("Login Success") - - // MARK: Persistence - - - self.performSegue(withIdentifier: "loginToRoot", sender: self) + DispatchQueue.global(qos: .userInitiated).async { + self.attemptLogin(username: userString!, password: passString!) + } } } - func loginMethod(_ user:String, _ password:String) { - let userManager = UserManager(config, token: "asdf") - userManager.doLogin(username: user, password: password) { result in + func attemptLogin(username: String, password: String) { + let userManager = UserManager(identity: identity) + userManager.doLogin(username: username, password: password) { result in switch(result) { case .success(let record): - print(record.key) + print("Successful login and retrieved token " + record.key) + + // Save to identity + self.identity.token = record.key + + // Persistence + // TODO: Fix calling UI stuff from background thread + // (maybe have a separate function to move on if login successful?) + if(self.persistButton.isSelected) { // if user checks "Remember Me?" + let persistenceResult = self.identity.saveToDisk() + if(!persistenceResult) { + print("SOMETHING WENT WRONG WITH PERSISTENCE!") + } + } + + // Segue out of VC + DispatchQueue.main.async { + self.performSegue(withIdentifier: "loginToRoot", sender: self) + } + break; case .failure(let error): DispatchQueue.main.async { @@ -89,3 +93,4 @@ class LoginViewController: UIViewController { print(self.persistButton.isSelected) } } + From d1dca5c9478ef62d02750f64d10b868dcf3718a5 Mon Sep 17 00:00:00 2001 From: Stephan Martin Date: Sun, 25 Apr 2021 19:34:21 -0700 Subject: [PATCH 046/224] Initial push of codables for tickets --- Sluggo.xcodeproj/project.pbxproj | 8 ++++++++ Sluggo/Models/Codables/StatusRecord.swift | 19 +++++++++++++++++++ Sluggo/Models/Codables/TagRecord.swift | 19 +++++++++++++++++++ Sluggo/Models/Codables/TicketRecord.swift | 13 +++++++++++++ 4 files changed, 59 insertions(+) create mode 100644 Sluggo/Models/Codables/StatusRecord.swift create mode 100644 Sluggo/Models/Codables/TagRecord.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 8e0ceee..2f680b8 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -17,6 +17,8 @@ 2FD70D5D2636144300D13C75 /* LogoutMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FD70D5C2636144300D13C75 /* LogoutMessage.swift */; }; 2FEF5DA9262D0E7C00434763 /* PaginatedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */; }; 2FEF5DAE262D1C4900434763 /* MemberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DAD262D1C4900434763 /* MemberTests.swift */; }; + 3A150A6726365A9A0052934B /* StatusRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A6626365A9A0052934B /* StatusRecord.swift */; }; + 3A150A6C26365B670052934B /* TagRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A6B26365B670052934B /* TagRecord.swift */; }; 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A79FFE0262E562A00D04320 /* Main.storyboard */; }; 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A79FFE5262E563F00D04320 /* LoginViewController.swift */; }; 5A79FFEF262E600B00D04320 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A79FFEE262E600B00D04320 /* Config.swift */; }; @@ -74,6 +76,8 @@ 2FD70D5C2636144300D13C75 /* LogoutMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutMessage.swift; sourceTree = ""; }; 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginatedList.swift; sourceTree = ""; }; 2FEF5DAD262D1C4900434763 /* MemberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberTests.swift; sourceTree = ""; }; + 3A150A6626365A9A0052934B /* StatusRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusRecord.swift; sourceTree = ""; }; + 3A150A6B26365B670052934B /* TagRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagRecord.swift; sourceTree = ""; }; 5A79FFE0262E562A00D04320 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 5A79FFE5262E563F00D04320 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 5A79FFEE262E600B00D04320 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; @@ -160,6 +164,8 @@ 5AB1C8FB262FF1690010F85E /* TicketRecord.swift */, 5ACC0F002630CA1400DCF83E /* ErrorMessage.swift */, 2FD70D5C2636144300D13C75 /* LogoutMessage.swift */, + 3A150A6626365A9A0052934B /* StatusRecord.swift */, + 3A150A6B26365B670052934B /* TagRecord.swift */, ); path = Codables; sourceTree = ""; @@ -404,10 +410,12 @@ 2F3AD7A32627C86A00B61A97 /* User.swift in Sources */, 052258702636100B00B49CE4 /* LaunchViewController.swift in Sources */, 7DC2A2222629447D0002CEE6 /* SluggoSidebarTableViewController.swift in Sources */, + 3A150A6726365A9A0052934B /* StatusRecord.swift in Sources */, 5ACC0F062630CA2700DCF83E /* Exceptions.swift in Sources */, 5AB1C8F7262FE3520010F85E /* AppIdentity.swift in Sources */, 7DC2A22F26294F9E0002CEE6 /* HomeViewController.swift in Sources */, 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */, + 3A150A6C26365B670052934B /* TagRecord.swift in Sources */, 5AB1C8EF262FE3210010F85E /* TagManager.swift in Sources */, 5AB1C8E5262FE2E60010F85E /* TicketManager.swift in Sources */, 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */, diff --git a/Sluggo/Models/Codables/StatusRecord.swift b/Sluggo/Models/Codables/StatusRecord.swift new file mode 100644 index 0000000..36cd941 --- /dev/null +++ b/Sluggo/Models/Codables/StatusRecord.swift @@ -0,0 +1,19 @@ +// +// StatusRecord.swift +// Sluggo +// +// Created by Stephan Martin on 4/25/21. +// + +import Foundation + + +struct StatusRecord: Codable { + var id: Int + var object_uuid: UUID + var title: String + var created: Date + var activated: Date? + var deactivated: Date? + +} diff --git a/Sluggo/Models/Codables/TagRecord.swift b/Sluggo/Models/Codables/TagRecord.swift new file mode 100644 index 0000000..91d590d --- /dev/null +++ b/Sluggo/Models/Codables/TagRecord.swift @@ -0,0 +1,19 @@ +// +// TagRecord.swift +// Sluggo +// +// Created by Stephan Martin on 4/25/21. +// + +import Foundation + + +struct TagRecord: Codable { + var id: Int + var object_uuid: UUID + var title: String + var created: Date + var activated: Date? + var deactivated: Date? + +} diff --git a/Sluggo/Models/Codables/TicketRecord.swift b/Sluggo/Models/Codables/TicketRecord.swift index 5062e58..d90f0e0 100644 --- a/Sluggo/Models/Codables/TicketRecord.swift +++ b/Sluggo/Models/Codables/TicketRecord.swift @@ -3,11 +3,24 @@ // Sluggo // // Created by Samuel Schmidt on 4/20/21. +// Edited by Stephan Martin on 4/25/21. // import Foundation struct TicketRecord: Codable { var id: Int + var ticket_number: Int + var taglist: [TagRecord]? + var object_uuid: UUID + var status: StatusRecord? + var assigned_user: UserRecord? + var title: String + var description: String? + // var comments? Future Future Implementation + var created: Date + var activated: Date? + var deactivated: Date? + } From 003e3e34c4c1ab2d5038715e6e3a963f914ff354 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Mon, 26 Apr 2021 08:14:50 -0700 Subject: [PATCH 047/224] added parameter names for JsonLoader functions --- Sluggo/Models/JsonLoader.swift | 6 +++--- Sluggo/Models/Managers/MemberManager.swift | 2 +- Sluggo/Models/Managers/TicketManager.swift | 2 +- SluggoTests/MemberTests.swift | 8 ++++---- SluggoTests/UserTests.swift | 8 ++++---- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Sluggo/Models/JsonLoader.swift b/Sluggo/Models/JsonLoader.swift index 08b673b..cc29e22 100644 --- a/Sluggo/Models/JsonLoader.swift +++ b/Sluggo/Models/JsonLoader.swift @@ -8,7 +8,7 @@ import Foundation class JsonLoader { - static func decode(_ data: Data) -> T? { + static func decode(data: Data) -> T? { // Attempt decoding let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 @@ -21,7 +21,7 @@ class JsonLoader { return pagRec } - static func encode(_ data: T) -> Data? { + static func encode(object data: T) -> Data? { let encoder = JSONEncoder() encoder.dateEncodingStrategy = .iso8601 encoder.outputFormatting = .prettyPrinted @@ -49,7 +49,7 @@ class JsonLoader { let resp = response as! HTTPURLResponse if (resp.statusCode <= 299 && resp.statusCode >= 200) { if let fetchedData = data { - guard let record: T = JsonLoader.decode(fetchedData) else { + guard let record: T = JsonLoader.decode(data: fetchedData) else { completionHandler(.failure(RESTException.failedRequest(message: "Failure to decode retrieved model in JsonLoader Codable Request"))) return; } diff --git a/Sluggo/Models/Managers/MemberManager.swift b/Sluggo/Models/Managers/MemberManager.swift index 211165d..d08de53 100644 --- a/Sluggo/Models/Managers/MemberManager.swift +++ b/Sluggo/Models/Managers/MemberManager.swift @@ -37,7 +37,7 @@ class MemberManager { var request = URLRequest(url: URL(string: makeDetailUrl(memberRecord))!) request.httpMethod = "Put" - guard let body = JsonLoader.encode(memberRecord) else { + guard let body = JsonLoader.encode(object: memberRecord) else { completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize member JSON for updateMemberRecord in MemberManager"))) return } diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index 758c5a1..0e10b26 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -28,7 +28,7 @@ class TicketManager { var request = URLRequest(url: URL(string: makeDetailUrl(ticket))!) request.httpMethod = "PUT" - guard let body = JsonLoader.encode(ticket) else { + guard let body = JsonLoader.encode(object: ticket) else { completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize ticket JSON for updateTicket in TicketManager"))) return } diff --git a/SluggoTests/MemberTests.swift b/SluggoTests/MemberTests.swift index e39c910..43d319d 100644 --- a/SluggoTests/MemberTests.swift +++ b/SluggoTests/MemberTests.swift @@ -48,7 +48,7 @@ class MemberTests: XCTestCase { """ func testMemberDoesDeserialize() { - let member: MemberRecord? = JsonLoader.decode(testWorkingJson.data(using: .utf8)!) + let member: MemberRecord? = JsonLoader.decode(data: testWorkingJson.data(using: .utf8)!) XCTAssertNotNil(member) XCTAssertEqual(member!.team_id, 1) @@ -61,7 +61,7 @@ class MemberTests: XCTestCase { } func testUserDeserializesWithExtraProps() { - let member: MemberRecord? = JsonLoader.decode(testWorkingJsonWithExtraProps.data(using: .utf8)!) + let member: MemberRecord? = JsonLoader.decode(data: testWorkingJsonWithExtraProps.data(using: .utf8)!) XCTAssertNotNil(member) XCTAssertEqual(member!.team_id, 1) @@ -76,11 +76,11 @@ class MemberTests: XCTestCase { let member = MemberRecord(id: "this is an md5 eventually", owner: user, team_id: 1, object_uuid: UUID(), role: "UA", bio: nil, created: Date(), activated: nil, deactivated: nil) - let json = JsonLoader.encode(member) + let json = JsonLoader.encode(object: member) XCTAssertNotNil(json) print(json!) - let memberDuplicate: MemberRecord? = JsonLoader.decode(json!) + let memberDuplicate: MemberRecord? = JsonLoader.decode(data: json!) XCTAssertNotNil(memberDuplicate) XCTAssertEqual(member.object_uuid, memberDuplicate!.object_uuid) diff --git a/SluggoTests/UserTests.swift b/SluggoTests/UserTests.swift index d48cd46..c888316 100644 --- a/SluggoTests/UserTests.swift +++ b/SluggoTests/UserTests.swift @@ -28,7 +28,7 @@ class UserTests: XCTestCase { """ func testUserDoesDeserialize() { - let user: UserRecord? = JsonLoader.decode(testWorkingJson.data(using: .utf8)!) + let user: UserRecord? = JsonLoader.decode(data: testWorkingJson.data(using: .utf8)!) XCTAssertNotNil(user) XCTAssertEqual(user!.username, "wtpisaac") @@ -40,7 +40,7 @@ class UserTests: XCTestCase { } func testUserDeserializesWithExtraProps() { - let user: UserRecord? = JsonLoader.decode(testWorkingJsonWithExtraProps.data(using: .utf8)!) + let user: UserRecord? = JsonLoader.decode(data: testWorkingJsonWithExtraProps.data(using: .utf8)!) XCTAssertNotNil(user) XCTAssertEqual(user!.username, "wtpisaac") @@ -54,11 +54,11 @@ class UserTests: XCTestCase { func testUserDoesSerialize() { let user = UserRecord(id: 1, email: "wtpisaac@icloud.com", first_name: "Isaac", last_name: "Trimble-Pederson", username: "wtpisaac") - let json = JsonLoader.encode(user) + let json = JsonLoader.encode(object: user) XCTAssertNotNil(json) print(json!) - let userDuplicate: UserRecord? = JsonLoader.decode(json!) + let userDuplicate: UserRecord? = JsonLoader.decode(data: json!) XCTAssertNotNil(userDuplicate) XCTAssertEqual(user.username, userDuplicate!.username) From 841ef83b2ea41203f9442298d3c6f670d3227837 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Mon, 26 Apr 2021 17:46:47 -0700 Subject: [PATCH 048/224] Basic persistence functionality --- Sluggo/Models/AppIdentity.swift | 5 ++++- .../LoginViewController.swift | 21 +++++++++++++------ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/Sluggo/Models/AppIdentity.swift b/Sluggo/Models/AppIdentity.swift index 7b580b4..c3c11a7 100644 --- a/Sluggo/Models/AppIdentity.swift +++ b/Sluggo/Models/AppIdentity.swift @@ -31,7 +31,7 @@ class AppIdentity: Codable { private static var persistencePath: URL { get { - return URL(string: NSHomeDirectory() + Constants.FilePaths.persistencePath)! + return URL(fileURLWithPath: NSHomeDirectory().appending("/Library/appdata.json")) } } @@ -50,10 +50,12 @@ class AppIdentity: Codable { func saveToDisk() -> Bool { guard let appIdentityData = JsonLoader.encode(self) else { + print("Failed to encode app identity with JSON, could not persist") return false } guard let appIdentityContents = String(data: appIdentityData, encoding: .utf8) else { + print("Failed to encode app identity encoded data as string, could not persist") return false } @@ -61,6 +63,7 @@ class AppIdentity: Codable { try appIdentityContents.write(to: AppIdentity.persistencePath, atomically: false, encoding: .utf8) return true } catch { + print("Could not write persistence file to disk, could not persist") return false } } diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index bfb8025..9a3201f 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -16,7 +16,9 @@ class LoginViewController: UIViewController { // TODO: AppIdentity be migrated when login flow progressed // This should really be managed by the AppDelegate and passed into VCs along // segues and otherwise. - let identity = AppIdentity() + + // Attempt to load from disk, otherwise, use the new one. + let identity = AppIdentity.loadFromDisk() ?? AppIdentity() convenience init() { self.init(nibName:nil, bundle:nil) @@ -38,6 +40,15 @@ class LoginViewController: UIViewController { persistButton.setImage(UIImage(systemName: "checkmark.square.fill"), for: [.highlighted, .selected]) } + override func viewDidAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if(identity.token != nil) { + // Segue out of VC + self.performSegue(withIdentifier: "loginToRoot", sender: self) + } + } + @IBAction func loginButton(_ sender: Any) { let userString = username.text let passString = password.text @@ -67,11 +78,9 @@ class LoginViewController: UIViewController { // Persistence // TODO: Fix calling UI stuff from background thread // (maybe have a separate function to move on if login successful?) - if(self.persistButton.isSelected) { // if user checks "Remember Me?" - let persistenceResult = self.identity.saveToDisk() - if(!persistenceResult) { - print("SOMETHING WENT WRONG WITH PERSISTENCE!") - } + let persistenceResult = self.identity.saveToDisk() + if(!persistenceResult) { + print("SOMETHING WENT WRONG WITH PERSISTENCE!") } // Segue out of VC From 9e7e155ba41c4b9d3fa1b691c07ab741e86c502c Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Mon, 26 Apr 2021 19:04:02 -0700 Subject: [PATCH 049/224] Cleanup failure logic --- Sluggo/Models/AppIdentity.swift | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/Sluggo/Models/AppIdentity.swift b/Sluggo/Models/AppIdentity.swift index c3c11a7..e78caf6 100644 --- a/Sluggo/Models/AppIdentity.swift +++ b/Sluggo/Models/AppIdentity.swift @@ -25,13 +25,11 @@ class AppIdentity: Codable { Constants.Config.kURL: Constants.Config.URL_BASE // enable change if need be ] - init() { - print(AppIdentity.persistencePath) - } - private static var persistencePath: URL { get { - return URL(fileURLWithPath: NSHomeDirectory().appending("/Library/appdata.json")) + let path = URL(fileURLWithPath: NSHomeDirectory().appending("/Library/appdata.json")) + print(path) + return path } } @@ -41,10 +39,17 @@ class AppIdentity: Codable { } guard let persistenceFileData = persistenceFileContents.data(using: .utf8) else { + // File exists but was corrupted, so clean it up + let _ = deletePersistenceFile() return nil } let appIdentity: AppIdentity? = JsonLoader.decode(persistenceFileData) + if(appIdentity == nil) { + // File exists, but failed to deserialize, so clean it up + let _ = deletePersistenceFile() + } + return appIdentity } @@ -69,13 +74,18 @@ class AppIdentity: Codable { } static func deletePersistenceFile() -> Bool { + // Succeed if the persistence file doesn't exist. This allows us to clean up a bad file. + if !(FileManager.default.fileExists(atPath: self.persistencePath.path)) { + print("Attempted to delete persistence file when it doesn't exist, returning") + return true + } + do { try FileManager.default.removeItem(at: self.persistencePath) return true } catch { + print("FAILED TO CLEAN UP PERSISTENCE FILE") return false } } - - } From 9603fef3b692f5da16c92826cc6b77a0255588cd Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Mon, 26 Apr 2021 19:04:33 -0700 Subject: [PATCH 050/224] Shifted folder placement in list to not be bad --- Sluggo.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 0e68269..512d7e9 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -136,9 +136,9 @@ isa = PBXGroup; children = ( 5AB1C8DB262FE29E0010F85E /* Managers */, + 5A79FFED262E5FEF00D04320 /* Codables */, 2F3F66E1262DD3BA0040404C /* JsonLoader.swift */, 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */, - 5A79FFED262E5FEF00D04320 /* Codables */, 5AB1C8F6262FE3520010F85E /* AppIdentity.swift */, 051AAB0A2630FD6500BCB9C7 /* Constants.swift */, 5ACC0F052630CA2700DCF83E /* Exceptions.swift */, From 7df52ec2644c608c9df0f3145007a887ae69e78e Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Mon, 26 Apr 2021 19:16:59 -0700 Subject: [PATCH 051/224] Prevent granny tapping login button and respect Remember Me choice AppIdentity might need to swap how it is approaching Remember Me because we are currently using this to determine if we remember anything, which could have ramifications down the line. (For what it's worth, I don't think we really need a Remember Me feature, but Troy is insistent on having it, so for now it's fine) --- Sluggo/Models/AppIdentity.swift | 2 +- Sluggo/Storyboards/Main.storyboard | 7 ++++--- .../LoginViewController.swift | 19 ++++++++++++++----- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Sluggo/Models/AppIdentity.swift b/Sluggo/Models/AppIdentity.swift index e78caf6..09047c8 100644 --- a/Sluggo/Models/AppIdentity.swift +++ b/Sluggo/Models/AppIdentity.swift @@ -65,7 +65,7 @@ class AppIdentity: Codable { } do { - try appIdentityContents.write(to: AppIdentity.persistencePath, atomically: false, encoding: .utf8) + try appIdentityContents.write(to: AppIdentity.persistencePath, atomically: true, encoding: .utf8) return true } catch { print("Could not write persistence file to disk, could not persist") diff --git a/Sluggo/Storyboards/Main.storyboard b/Sluggo/Storyboards/Main.storyboard index 24ea7ca..bf96689 100644 --- a/Sluggo/Storyboards/Main.storyboard +++ b/Sluggo/Storyboards/Main.storyboard @@ -1,8 +1,8 @@ - + - + @@ -55,7 +55,7 @@ - + @@ -150,6 +150,7 @@ + diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index 9a3201f..3d5a6e4 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -12,6 +12,7 @@ class LoginViewController: UIViewController { @IBOutlet weak var username: UITextField! @IBOutlet weak var password: UITextField! @IBOutlet weak var persistButton: UIButton! + @IBOutlet weak var loginButton: UIButton! // TODO: AppIdentity be migrated when login flow progressed // This should really be managed by the AppDelegate and passed into VCs along @@ -37,6 +38,9 @@ class LoginViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() self.isModalInPresentation = true + // only enable persistence button if the token is nil (that is, we didn't load an AppIdentity) + self.persistButton.isEnabled = (self.identity.token == nil) + self.loginButton.isEnabled = (self.identity.token == nil) persistButton.setImage(UIImage(systemName: "checkmark.square.fill"), for: [.highlighted, .selected]) } @@ -76,11 +80,16 @@ class LoginViewController: UIViewController { self.identity.token = record.key // Persistence - // TODO: Fix calling UI stuff from background thread - // (maybe have a separate function to move on if login successful?) - let persistenceResult = self.identity.saveToDisk() - if(!persistenceResult) { - print("SOMETHING WENT WRONG WITH PERSISTENCE!") + // This is hacky but better than nothing + var rememberMe: Bool = false + DispatchQueue.main.sync { + rememberMe = self.persistButton.isSelected + } + if(rememberMe) { + let persistenceResult = self.identity.saveToDisk() + if(!persistenceResult) { + print("SOMETHING WENT WRONG WITH PERSISTENCE!") + } } // Segue out of VC From 09e670189fb0a9762abfd74a62163b19d42ba6f4 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Mon, 26 Apr 2021 22:41:16 -0700 Subject: [PATCH 052/224] Migrate to storing baseURL on identity directly The config dictionary was fairly arbitrary and can be removed without consequence, since our AppIdentity object is serializable anyways. --- Sluggo/Models/AppIdentity.swift | 14 +------------- Sluggo/Models/Managers/MemberManager.swift | 4 ++-- Sluggo/Models/Managers/TeamManager.swift | 2 +- Sluggo/Models/Managers/TicketManager.swift | 4 ++-- Sluggo/Models/Managers/UserManager.swift | 4 ++-- 5 files changed, 8 insertions(+), 20 deletions(-) diff --git a/Sluggo/Models/AppIdentity.swift b/Sluggo/Models/AppIdentity.swift index 09047c8..60a26cb 100644 --- a/Sluggo/Models/AppIdentity.swift +++ b/Sluggo/Models/AppIdentity.swift @@ -11,19 +11,7 @@ class AppIdentity: Codable { var authenticatedUser: UserRecord? var team: TeamRecord? var token: String? - var instanceURLString: String? { - get { - return self.configData[Constants.Config.kURL] - } - - set(newURLString) { - self.configData[Constants.Config.kURL] = newURLString - } - } - - var configData: [String: String] = [ - Constants.Config.kURL: Constants.Config.URL_BASE // enable change if need be - ] + var baseAddress: String = Constants.Config.URL_BASE private static var persistencePath: URL { get { diff --git a/Sluggo/Models/Managers/MemberManager.swift b/Sluggo/Models/Managers/MemberManager.swift index 2127ad7..103425f 100644 --- a/Sluggo/Models/Managers/MemberManager.swift +++ b/Sluggo/Models/Managers/MemberManager.swift @@ -15,11 +15,11 @@ class MemberManager { } private func makeDetailUrl(memberRecord: MemberRecord) -> URL { - return URL(string: identity.configData[Constants.Config.kURL]! + "/api/teams/" + "\(identity.team!.id)" + "/members/" + "\(memberRecord.id)/")! + return URL(string: identity.baseAddress + "/api/teams/" + "\(identity.team!.id)" + "/members/" + "\(memberRecord.id)/")! } private func makeListUrl() -> URL { - return URL(string: identity.configData[Constants.Config.kURL]! + "/api/teams/" + "\(identity.team!.id)" + "/members/")! + return URL(string: identity.baseAddress + "/api/teams/" + "\(identity.team!.id)" + "/members/")! } public func fetchMemberRecord(completionHandler: @escaping(Result) -> Void) -> Void { diff --git a/Sluggo/Models/Managers/TeamManager.swift b/Sluggo/Models/Managers/TeamManager.swift index 2c9f4f9..c5c7f9d 100644 --- a/Sluggo/Models/Managers/TeamManager.swift +++ b/Sluggo/Models/Managers/TeamManager.swift @@ -17,7 +17,7 @@ class TeamManager { public func listUserTeams(completionHandler: @escaping(Result, Error>) -> Void) -> Void { - var request = URLRequest(url: URL(string: identity.configData["instanceURL"]! + TeamManager.urlBase)!) + var request = URLRequest(url: URL(string: identity.baseAddress + TeamManager.urlBase)!) request.httpMethod = "GET" request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index e3f1175..4ada331 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -15,11 +15,11 @@ class TicketManager { } private func makeDetailUrl(_ ticketRecord: TicketRecord) -> URL { - return URL(string: identity.configData[Constants.Config.kURL]! + "/api/teams/" + "\(identity.team!.id)" + "/tickets/" + "\(ticketRecord.id)/")! + return URL(string: identity.baseAddress + "/api/teams/" + "\(identity.team!.id)" + "/tickets/" + "\(ticketRecord.id)/")! } private func makeListUrl() -> URL { - return URL(string: identity.configData[Constants.Config.kURL]! + "/api/teams/" + "\(identity.team!.id)" + "/tickets/")! + return URL(string: identity.baseAddress + "/api/teams/" + "\(identity.team!.id)" + "/tickets/")! } public func updateTicket(_ ticket: TicketRecord, completionHandler: @escaping(Result) -> Void)-> Void { diff --git a/Sluggo/Models/Managers/UserManager.swift b/Sluggo/Models/Managers/UserManager.swift index e04c907..8d61a65 100644 --- a/Sluggo/Models/Managers/UserManager.swift +++ b/Sluggo/Models/Managers/UserManager.swift @@ -16,7 +16,7 @@ class UserManager { public func doLogin(username: String, password: String, completionHandler: @escaping(Result) -> Void) -> Void { let params = ["username":username, "password":password] as Dictionary - var request = URLRequest(url: URL(string: identity.configData[Constants.Config.kURL]! + "auth/login/")!) + var request = URLRequest(url: URL(string: identity.baseAddress + "auth/login/")!) request.httpMethod = "POST" guard let body = try? JSONSerialization.data(withJSONObject: params, options: []) else { completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize JSON for doLogin in UserManager"))) @@ -28,7 +28,7 @@ class UserManager { } public func doLogout(token: String, completionHandler: @escaping(Result) -> Void) -> Void { // TODO: this is probably incorrect - var request = URLRequest(url: URL(string: identity.configData[Constants.Config.kURL]! + "auth/logout/")!) + var request = URLRequest(url: URL(string: identity.baseAddress + "auth/logout/")!) request.httpMethod = "POST" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") From d29f9db3beb6af6299fd0de1be72551d59c871c6 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Tue, 27 Apr 2021 00:41:58 -0700 Subject: [PATCH 053/224] dependency injection sorta works --- Sluggo.xcodeproj/project.pbxproj | 8 +++++ Sluggo/Models/AppIdentity.swift | 4 +-- Sluggo/Storyboards/Base.lproj/Root.storyboard | 24 ++----------- Sluggo/Storyboards/Home.storyboard | 25 +++++++++++-- Sluggo/Storyboards/Main.storyboard | 6 ++-- .../View Controllers/HomeControllerView.swift | 35 +++++++++++++++++++ .../HomeNavigationController.swift | 31 ++++++++++++++++ .../View Controllers/HomeViewController.swift | 23 ++++++++++-- Sluggo/View Controllers/IdentityView.swift | 13 +++++++ .../LoginViewController.swift | 5 +++ .../View Controllers/RootViewController.swift | 14 ++++++++ 11 files changed, 158 insertions(+), 30 deletions(-) create mode 100644 Sluggo/View Controllers/HomeControllerView.swift create mode 100644 Sluggo/View Controllers/HomeNavigationController.swift create mode 100644 Sluggo/View Controllers/IdentityView.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 512d7e9..36cafba 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -19,6 +19,8 @@ 2FEF5DAE262D1C4900434763 /* MemberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DAD262D1C4900434763 /* MemberTests.swift */; }; 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A79FFE0262E562A00D04320 /* Main.storyboard */; }; 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A79FFE5262E563F00D04320 /* LoginViewController.swift */; }; + 5A9FF8792637EFE800378550 /* IdentityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9FF8782637EFE800378550 /* IdentityView.swift */; }; + 5A9FF87E2637F03600378550 /* HomeNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9FF87D2637F03600378550 /* HomeNavigationController.swift */; }; 5AB1C8DD262FE2DD0010F85E /* TeamManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8DC262FE2DD0010F85E /* TeamManager.swift */; }; 5AB1C8E5262FE2E60010F85E /* TicketManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8E4262FE2E60010F85E /* TicketManager.swift */; }; 5AB1C8EA262FE2EF0010F85E /* MemberManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8E9262FE2EF0010F85E /* MemberManager.swift */; }; @@ -75,6 +77,8 @@ 2FEF5DAD262D1C4900434763 /* MemberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberTests.swift; sourceTree = ""; }; 5A79FFE0262E562A00D04320 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 5A79FFE5262E563F00D04320 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; + 5A9FF8782637EFE800378550 /* IdentityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityView.swift; sourceTree = ""; }; + 5A9FF87D2637F03600378550 /* HomeNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeNavigationController.swift; sourceTree = ""; }; 5AB1C8DC262FE2DD0010F85E /* TeamManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamManager.swift; sourceTree = ""; }; 5AB1C8E4262FE2E60010F85E /* TicketManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketManager.swift; sourceTree = ""; }; 5AB1C8E9262FE2EF0010F85E /* MemberManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberManager.swift; sourceTree = ""; }; @@ -248,6 +252,8 @@ 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */, 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */, 7DC2A22E26294F9E0002CEE6 /* HomeViewController.swift */, + 5A9FF8782637EFE800378550 /* IdentityView.swift */, + 5A9FF87D2637F03600378550 /* HomeNavigationController.swift */, ); path = "View Controllers"; sourceTree = ""; @@ -395,6 +401,7 @@ 051AAB0B2630FD6500BCB9C7 /* Constants.swift in Sources */, 2F3F66E2262DD3BA0040404C /* JsonLoader.swift in Sources */, 2F2247AC262920AC006C6EE6 /* Token.swift in Sources */, + 5A9FF87E2637F03600378550 /* HomeNavigationController.swift in Sources */, 7D1DA6AE262B82C200E7C12D /* RootViewController.swift in Sources */, 5ACC0F012630CA1400DCF83E /* ErrorMessage.swift in Sources */, 2F3AD7A32627C86A00B61A97 /* User.swift in Sources */, @@ -404,6 +411,7 @@ 5AB1C8F7262FE3520010F85E /* AppIdentity.swift in Sources */, 7DC2A22F26294F9E0002CEE6 /* HomeViewController.swift in Sources */, 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */, + 5A9FF8792637EFE800378550 /* IdentityView.swift in Sources */, 5AB1C8EF262FE3210010F85E /* TagManager.swift in Sources */, 5AB1C8E5262FE2E60010F85E /* TicketManager.swift in Sources */, 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */, diff --git a/Sluggo/Models/AppIdentity.swift b/Sluggo/Models/AppIdentity.swift index 60a26cb..5f6e620 100644 --- a/Sluggo/Models/AppIdentity.swift +++ b/Sluggo/Models/AppIdentity.swift @@ -32,7 +32,7 @@ class AppIdentity: Codable { return nil } - let appIdentity: AppIdentity? = JsonLoader.decode(persistenceFileData) + let appIdentity: AppIdentity? = JsonLoader.decode(data: persistenceFileData) if(appIdentity == nil) { // File exists, but failed to deserialize, so clean it up let _ = deletePersistenceFile() @@ -42,7 +42,7 @@ class AppIdentity: Codable { } func saveToDisk() -> Bool { - guard let appIdentityData = JsonLoader.encode(self) else { + guard let appIdentityData = JsonLoader.encode(object: self) else { print("Failed to encode app identity with JSON, could not persist") return false } diff --git a/Sluggo/Storyboards/Base.lproj/Root.storyboard b/Sluggo/Storyboards/Base.lproj/Root.storyboard index 7d49af9..5048d8a 100644 --- a/Sluggo/Storyboards/Base.lproj/Root.storyboard +++ b/Sluggo/Storyboards/Base.lproj/Root.storyboard @@ -69,7 +69,7 @@ - + @@ -80,30 +80,12 @@ + - - - - - - - - - - - - - - - - - - - - + diff --git a/Sluggo/Storyboards/Home.storyboard b/Sluggo/Storyboards/Home.storyboard index f694114..f2cd728 100644 --- a/Sluggo/Storyboards/Home.storyboard +++ b/Sluggo/Storyboards/Home.storyboard @@ -1,5 +1,5 @@ - + @@ -31,12 +31,33 @@ + - + + + + + + + + + + + + + + + + + + + + + diff --git a/Sluggo/Storyboards/Main.storyboard b/Sluggo/Storyboards/Main.storyboard index bf96689..a613601 100644 --- a/Sluggo/Storyboards/Main.storyboard +++ b/Sluggo/Storyboards/Main.storyboard @@ -1,8 +1,8 @@ - + - + @@ -154,7 +154,7 @@ - + diff --git a/Sluggo/View Controllers/HomeControllerView.swift b/Sluggo/View Controllers/HomeControllerView.swift new file mode 100644 index 0000000..526c4a2 --- /dev/null +++ b/Sluggo/View Controllers/HomeControllerView.swift @@ -0,0 +1,35 @@ +// +// HomeControllerView.swift +// Sluggo +// +// Created by Samuel Schmidt on 4/26/21. +// + +import UIKit + +class HomeNavigationController: UINavigationController { + var identity: AppIdentity + + init? (coder: NSCoder, identity: AppIdentity) { + self.identity = identity + super.init(coder: coder) + } + + required init? (coder: NSCoder) { + fatalError("you fucked up") + } + + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + print("custom prepare") + super.prepare(for: segue, sender: sender) + } + + @IBSegueAction func createHome(coder: NSCoder, sender: Any?, segueIdentifier: String?)-> HomeViewController? { + print("coder") + return HomeViewController(coder: coder) + } + + + +} diff --git a/Sluggo/View Controllers/HomeNavigationController.swift b/Sluggo/View Controllers/HomeNavigationController.swift new file mode 100644 index 0000000..e8fe100 --- /dev/null +++ b/Sluggo/View Controllers/HomeNavigationController.swift @@ -0,0 +1,31 @@ +// +// HomeNavigationController.swift +// Sluggo +// +// Created by Samuel Schmidt on 4/27/21. +// + +import UIKit + +class HomeNavigationController: UINavigationController, IdentityView { + var identity: AppIdentity? + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if let destination = segue.destination as? HomeViewController { + destination.setIdentity(identity: identity!) + } + } + + required init? (coder: NSCoder, identity: AppIdentity) { + self.identity = identity + super.init(coder: coder) + } + + required init? (coder: NSCoder) { + super.init(coder: coder) + } + + func setIdentity(identity: AppIdentity) { + self.identity = identity + } +} diff --git a/Sluggo/View Controllers/HomeViewController.swift b/Sluggo/View Controllers/HomeViewController.swift index 4ae564c..2d1e1e4 100644 --- a/Sluggo/View Controllers/HomeViewController.swift +++ b/Sluggo/View Controllers/HomeViewController.swift @@ -7,14 +7,33 @@ import UIKit -class HomeViewController: UIViewController { +class HomeViewController: UIViewController, IdentityView { + + public var identity: AppIdentity + + required init?(coder: NSCoder, identity: AppIdentity) { + self.identity = identity + super.init(coder: coder) + } + + required init?(coder: NSCoder) { + fatalError("identity must be passed by ctor") + } + + func setIdentity(identity: AppIdentity) { + print("is set") + self.identity = identity + } + @IBAction func pushDaButton(_ sender: Any) { NotificationCenter.default.post(Notification(name: .onSidebarTrigger, userInfo: [Sidebar.USER_INFO_KEY : SidebarStatus.open])) } override func viewDidLoad() { super.viewDidLoad() - + print(identity.token!) + + // Do any additional setup after loading the view. } diff --git a/Sluggo/View Controllers/IdentityView.swift b/Sluggo/View Controllers/IdentityView.swift new file mode 100644 index 0000000..eb8a8fb --- /dev/null +++ b/Sluggo/View Controllers/IdentityView.swift @@ -0,0 +1,13 @@ +// +// IdentityView.swift +// Sluggo +// +// Created by Samuel Schmidt on 4/27/21. +// + +import UIKit + +protocol IdentityView { + init? (coder: NSCoder, identity: AppIdentity) + func setIdentity(identity: AppIdentity) +} diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index 3d5a6e4..49c41b4 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -110,5 +110,10 @@ class LoginViewController: UIViewController { self.persistButton.isSelected.toggle() print(self.persistButton.isSelected) } + + @IBSegueAction func createRootViewController(_ coder: NSCoder) -> UIViewController? { + return RootViewController(coder: coder, identity: identity) + } + } diff --git a/Sluggo/View Controllers/RootViewController.swift b/Sluggo/View Controllers/RootViewController.swift index 986cb33..1b70626 100644 --- a/Sluggo/View Controllers/RootViewController.swift +++ b/Sluggo/View Controllers/RootViewController.swift @@ -8,9 +8,19 @@ import UIKit class RootViewController: UIViewController { + private var identity: AppIdentity @IBOutlet weak var mainContainerView: UIView! // contains the main app and tab bar controller @IBOutlet weak var sidebarContainerView: UIView! // contains the sidebar container view controller + init? (coder: NSCoder, identity: AppIdentity) { + self.identity = identity + super.init(coder: coder) + } + + required init?(coder: NSCoder) { + fatalError("must be called with identity") + } + @objc func onSidebarNotificationRecieved(_notification: Notification) { guard let status = _notification.userInfo?[Sidebar.USER_INFO_KEY] as? SidebarStatus else { return; @@ -31,6 +41,10 @@ class RootViewController: UIViewController { // Do any additional setup after loading the view. } + @IBSegueAction func createHome(_ coder: NSCoder) -> HomeViewController? { + print("custom") + return HomeViewController(coder: coder, identity: identity) + } /* // MARK: - Navigation From 992d27331cfb3d3886ba99fc8937f97bc710522a Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Tue, 27 Apr 2021 09:44:26 -0700 Subject: [PATCH 054/224] dependency injection along with basic ticket functionality --- Sluggo.xcodeproj/project.pbxproj | 8 ++ Sluggo/Models/Codables/TicketRecord.swift | 3 +- Sluggo/Models/Managers/TeamManager.swift | 4 +- Sluggo/Models/Managers/TicketManager.swift | 16 +++- Sluggo/Storyboards/Base.lproj/Root.storyboard | 13 +++- Sluggo/Storyboards/Home.storyboard | 8 +- Sluggo/Storyboards/Ticket.storyboard | 74 +++++++++++++++++++ .../LoginViewController.swift | 6 +- .../View Controllers/RootViewController.swift | 21 +++--- .../TicketListController.swift | 57 ++++++++++++++ 10 files changed, 189 insertions(+), 21 deletions(-) create mode 100644 Sluggo/Storyboards/Ticket.storyboard create mode 100644 Sluggo/View Controllers/TicketListController.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 36cafba..c57ab6a 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -21,6 +21,8 @@ 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A79FFE5262E563F00D04320 /* LoginViewController.swift */; }; 5A9FF8792637EFE800378550 /* IdentityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9FF8782637EFE800378550 /* IdentityView.swift */; }; 5A9FF87E2637F03600378550 /* HomeNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9FF87D2637F03600378550 /* HomeNavigationController.swift */; }; + 5A9FF88926386D1400378550 /* Ticket.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A9FF88826386D1400378550 /* Ticket.storyboard */; }; + 5A9FF88E26386D4700378550 /* TicketListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9FF88D26386D4700378550 /* TicketListController.swift */; }; 5AB1C8DD262FE2DD0010F85E /* TeamManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8DC262FE2DD0010F85E /* TeamManager.swift */; }; 5AB1C8E5262FE2E60010F85E /* TicketManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8E4262FE2E60010F85E /* TicketManager.swift */; }; 5AB1C8EA262FE2EF0010F85E /* MemberManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8E9262FE2EF0010F85E /* MemberManager.swift */; }; @@ -79,6 +81,8 @@ 5A79FFE5262E563F00D04320 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 5A9FF8782637EFE800378550 /* IdentityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityView.swift; sourceTree = ""; }; 5A9FF87D2637F03600378550 /* HomeNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeNavigationController.swift; sourceTree = ""; }; + 5A9FF88826386D1400378550 /* Ticket.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Ticket.storyboard; sourceTree = ""; }; + 5A9FF88D26386D4700378550 /* TicketListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketListController.swift; sourceTree = ""; }; 5AB1C8DC262FE2DD0010F85E /* TeamManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamManager.swift; sourceTree = ""; }; 5AB1C8E4262FE2E60010F85E /* TicketManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketManager.swift; sourceTree = ""; }; 5AB1C8E9262FE2EF0010F85E /* MemberManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberManager.swift; sourceTree = ""; }; @@ -239,6 +243,7 @@ 7D9963AB261EF3A4002338E7 /* Root.storyboard */, 7DC2A21626293F360002CEE6 /* Home.storyboard */, 7DC2A21B262943A70002CEE6 /* Sidebar.storyboard */, + 5A9FF88826386D1400378550 /* Ticket.storyboard */, ); path = Storyboards; sourceTree = ""; @@ -254,6 +259,7 @@ 7DC2A22E26294F9E0002CEE6 /* HomeViewController.swift */, 5A9FF8782637EFE800378550 /* IdentityView.swift */, 5A9FF87D2637F03600378550 /* HomeNavigationController.swift */, + 5A9FF88D26386D4700378550 /* TicketListController.swift */, ); path = "View Controllers"; sourceTree = ""; @@ -363,6 +369,7 @@ files = ( 7D9963B5261EF3A5002338E7 /* LaunchScreen.storyboard in Resources */, 7D9963B2261EF3A5002338E7 /* Assets.xcassets in Resources */, + 5A9FF88926386D1400378550 /* Ticket.storyboard in Resources */, 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */, 7D9963AD261EF3A4002338E7 /* Root.storyboard in Resources */, 7DC2A21726293F360002CEE6 /* Home.storyboard in Resources */, @@ -413,6 +420,7 @@ 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */, 5A9FF8792637EFE800378550 /* IdentityView.swift in Sources */, 5AB1C8EF262FE3210010F85E /* TagManager.swift in Sources */, + 5A9FF88E26386D4700378550 /* TicketListController.swift in Sources */, 5AB1C8E5262FE2E60010F85E /* TicketManager.swift in Sources */, 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */, 2F3AD7A82627C89B00B61A97 /* Member.swift in Sources */, diff --git a/Sluggo/Models/Codables/TicketRecord.swift b/Sluggo/Models/Codables/TicketRecord.swift index 5062e58..e15b9c6 100644 --- a/Sluggo/Models/Codables/TicketRecord.swift +++ b/Sluggo/Models/Codables/TicketRecord.swift @@ -9,5 +9,6 @@ import Foundation struct TicketRecord: Codable { var id: Int - + var ticket_number: Int + var title: String } diff --git a/Sluggo/Models/Managers/TeamManager.swift b/Sluggo/Models/Managers/TeamManager.swift index c5c7f9d..d6d5100 100644 --- a/Sluggo/Models/Managers/TeamManager.swift +++ b/Sluggo/Models/Managers/TeamManager.swift @@ -8,7 +8,7 @@ import Foundation class TeamManager { - static let urlBase = "/api/teams/" + static let urlBase = "api/teams/" private var identity: AppIdentity init(identity: AppIdentity) { @@ -19,7 +19,7 @@ class TeamManager { var request = URLRequest(url: URL(string: identity.baseAddress + TeamManager.urlBase)!) request.httpMethod = "GET" - request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") + request.setValue("Bearer \(self.identity.token!)", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") JsonLoader.executeCodableRequest(request: request, completionHandler: completionHandler) diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index bda6272..a73c2d7 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -15,11 +15,19 @@ class TicketManager { } private func makeDetailUrl(_ ticketRecord: TicketRecord) -> URL { - return URL(string: identity.baseAddress + "/api/teams/" + "\(identity.team!.id)" + "/tickets/" + "\(ticketRecord.id)/")! + return URL(string: identity.baseAddress + "api/teams/" + "\(identity.team!.id)" + "/tickets/" + "\(ticketRecord.id)/")! } private func makeListUrl() -> URL { - return URL(string: identity.baseAddress + "/api/teams/" + "\(identity.team!.id)" + "/tickets/")! + return URL(string: identity.baseAddress + "api/teams/" + "\(identity.team!.id)" + "/tickets/")! + } + + public func listTeamTickets(completionHandler: @escaping (Result, Error>) -> Void) -> Void { + var request = URLRequest(url: makeListUrl()) + request.setValue("Bearer \(self.identity.token!)", forHTTPHeaderField: "Authorization") + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + JsonLoader.executeCodableRequest(request: request, completionHandler: completionHandler) } public func updateTicket(_ ticket: TicketRecord, completionHandler: @escaping(Result) -> Void)-> Void { @@ -32,9 +40,9 @@ class TicketManager { } request.httpBody = body - request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") + request.setValue("Bearer \(self.identity.token!)", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") - + JsonLoader.executeCodableRequest(request: request, completionHandler: completionHandler) } diff --git a/Sluggo/Storyboards/Base.lproj/Root.storyboard b/Sluggo/Storyboards/Base.lproj/Root.storyboard index 5048d8a..4539cb2 100644 --- a/Sluggo/Storyboards/Base.lproj/Root.storyboard +++ b/Sluggo/Storyboards/Base.lproj/Root.storyboard @@ -70,6 +70,7 @@ + @@ -85,7 +86,17 @@ - + + + + + + + + + + + diff --git a/Sluggo/Storyboards/Home.storyboard b/Sluggo/Storyboards/Home.storyboard index f2cd728..682218b 100644 --- a/Sluggo/Storyboards/Home.storyboard +++ b/Sluggo/Storyboards/Home.storyboard @@ -39,10 +39,12 @@ - + + + @@ -51,11 +53,13 @@ + + + - diff --git a/Sluggo/Storyboards/Ticket.storyboard b/Sluggo/Storyboards/Ticket.storyboard new file mode 100644 index 0000000..23530f2 --- /dev/null +++ b/Sluggo/Storyboards/Ticket.storyboard @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index 49c41b4..aa9b243 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -38,6 +38,11 @@ class LoginViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() self.isModalInPresentation = true + + // TODO: for testing purposes, the team for the identity is hardcoded. Change this eventually : ) + self.identity.team = TeamRecord(id: 1, name: "bugslotics", object_uuid: UUID(), ticket_head: 1, + created: Date(), activated: nil, deactivated: nil) + // only enable persistence button if the token is nil (that is, we didn't load an AppIdentity) self.persistButton.isEnabled = (self.identity.token == nil) self.loginButton.isEnabled = (self.identity.token == nil) @@ -114,6 +119,5 @@ class LoginViewController: UIViewController { @IBSegueAction func createRootViewController(_ coder: NSCoder) -> UIViewController? { return RootViewController(coder: coder, identity: identity) } - } diff --git a/Sluggo/View Controllers/RootViewController.swift b/Sluggo/View Controllers/RootViewController.swift index 1b70626..3a42801 100644 --- a/Sluggo/View Controllers/RootViewController.swift +++ b/Sluggo/View Controllers/RootViewController.swift @@ -41,18 +41,19 @@ class RootViewController: UIViewController { // Do any additional setup after loading the view. } + // MARK: - Navigation + + + // these are interesting. + // while connecting them from view controller segues in the tab bar controller + // did not actually call these, wrapping each tab in a navigation controlller + // in the other storybaord and *then* connecting them to these handlers seems + // to have worked. @IBSegueAction func createHome(_ coder: NSCoder) -> HomeViewController? { - print("custom") return HomeViewController(coder: coder, identity: identity) } - /* - // MARK: - Navigation - - // In a storyboard-based application, you will often want to do a little preparation before navigation - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - // Get the new view controller using segue.destination. - // Pass the selected object to the new view controller. + + @IBSegueAction func createTicket(_ coder: NSCoder) -> TicketListController? { + return TicketListController(coder: coder, identity: identity) } - */ - } diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift new file mode 100644 index 0000000..d44bcdc --- /dev/null +++ b/Sluggo/View Controllers/TicketListController.swift @@ -0,0 +1,57 @@ +// +// TicketListController.swift +// Sluggo +// +// Created by Samuel Schmidt on 4/27/21. +// + +import UIKit + +class TicketListController: UITableViewController { + var identity: AppIdentity + var tickets: [TicketRecord] + + init? (coder: NSCoder, identity: AppIdentity) { + self.identity = identity + self.tickets = [] + super.init(coder: coder) + } + + required init? (coder: NSCoder) { + fatalError("must be initialized with identity") + } + + override func viewDidAppear(_ animated: Bool) { + loadData() + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.tickets.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "Ticket", for: indexPath) + cell.textLabel?.text = tickets[indexPath.row].title + return cell + } + + private func loadData() { + let ticketManager = TicketManager(identity) + ticketManager.listTeamTickets() { result in + switch(result) { + case .success(let record): + print("Successful login and retrieved tickets") + self.tickets = record.results + DispatchQueue.main.async { + self.tableView.reloadData() + } + break; + case .failure(let error): + DispatchQueue.main.async { + let alert = UIAlertController.errorController(error: error) + self.present(alert, animated: true, completion: nil) + } + } + } + } +} From 4d973a0c789e694923bf59433cb4bfd8336a7c42 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Tue, 27 Apr 2021 10:39:56 -0700 Subject: [PATCH 055/224] minor appearance change --- Sluggo/Storyboards/Home.storyboard | 5 +++-- Sluggo/Storyboards/Ticket.storyboard | 3 ++- Sluggo/View Controllers/TicketListController.swift | 10 ++++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Sluggo/Storyboards/Home.storyboard b/Sluggo/Storyboards/Home.storyboard index 682218b..f840da6 100644 --- a/Sluggo/Storyboards/Home.storyboard +++ b/Sluggo/Storyboards/Home.storyboard @@ -42,9 +42,8 @@ - - + @@ -60,11 +59,13 @@ + + diff --git a/Sluggo/Storyboards/Ticket.storyboard b/Sluggo/Storyboards/Ticket.storyboard index 23530f2..723045b 100644 --- a/Sluggo/Storyboards/Ticket.storyboard +++ b/Sluggo/Storyboards/Ticket.storyboard @@ -49,7 +49,7 @@ - + @@ -67,6 +67,7 @@ + diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index d44bcdc..81f3c9b 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -7,11 +7,13 @@ import UIKit -class TicketListController: UITableViewController { +class TicketListController: UITableViewController, IdentityView { + + var identity: AppIdentity var tickets: [TicketRecord] - init? (coder: NSCoder, identity: AppIdentity) { + required init? (coder: NSCoder, identity: AppIdentity) { self.identity = identity self.tickets = [] super.init(coder: coder) @@ -21,6 +23,10 @@ class TicketListController: UITableViewController { fatalError("must be initialized with identity") } + func setIdentity(identity: AppIdentity) { + self.identity = identity + } + override func viewDidAppear(_ animated: Bool) { loadData() } From bc4a50fc580a9d7cec3eaac7b1f86f4b0a65326f Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Tue, 27 Apr 2021 10:44:30 -0700 Subject: [PATCH 056/224] update protocol name --- Sluggo.xcodeproj/project.pbxproj | 8 ++++---- Sluggo/Storyboards/Main.storyboard | 2 +- Sluggo/View Controllers/HomeNavigationController.swift | 2 +- Sluggo/View Controllers/HomeViewController.swift | 2 +- .../{IdentityView.swift => IdentityInitialized.swift} | 3 +-- Sluggo/View Controllers/RootViewController.swift | 2 +- Sluggo/View Controllers/TicketListController.swift | 2 +- 7 files changed, 10 insertions(+), 11 deletions(-) rename Sluggo/View Controllers/{IdentityView.swift => IdentityInitialized.swift} (69%) diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index c57ab6a..2ccd88f 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -19,7 +19,7 @@ 2FEF5DAE262D1C4900434763 /* MemberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DAD262D1C4900434763 /* MemberTests.swift */; }; 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A79FFE0262E562A00D04320 /* Main.storyboard */; }; 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A79FFE5262E563F00D04320 /* LoginViewController.swift */; }; - 5A9FF8792637EFE800378550 /* IdentityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9FF8782637EFE800378550 /* IdentityView.swift */; }; + 5A9FF8792637EFE800378550 /* IdentityInitialized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9FF8782637EFE800378550 /* IdentityInitialized.swift */; }; 5A9FF87E2637F03600378550 /* HomeNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9FF87D2637F03600378550 /* HomeNavigationController.swift */; }; 5A9FF88926386D1400378550 /* Ticket.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A9FF88826386D1400378550 /* Ticket.storyboard */; }; 5A9FF88E26386D4700378550 /* TicketListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9FF88D26386D4700378550 /* TicketListController.swift */; }; @@ -79,7 +79,7 @@ 2FEF5DAD262D1C4900434763 /* MemberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberTests.swift; sourceTree = ""; }; 5A79FFE0262E562A00D04320 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 5A79FFE5262E563F00D04320 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; - 5A9FF8782637EFE800378550 /* IdentityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityView.swift; sourceTree = ""; }; + 5A9FF8782637EFE800378550 /* IdentityInitialized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityInitialized.swift; sourceTree = ""; }; 5A9FF87D2637F03600378550 /* HomeNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeNavigationController.swift; sourceTree = ""; }; 5A9FF88826386D1400378550 /* Ticket.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Ticket.storyboard; sourceTree = ""; }; 5A9FF88D26386D4700378550 /* TicketListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketListController.swift; sourceTree = ""; }; @@ -257,7 +257,7 @@ 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */, 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */, 7DC2A22E26294F9E0002CEE6 /* HomeViewController.swift */, - 5A9FF8782637EFE800378550 /* IdentityView.swift */, + 5A9FF8782637EFE800378550 /* IdentityInitialized.swift */, 5A9FF87D2637F03600378550 /* HomeNavigationController.swift */, 5A9FF88D26386D4700378550 /* TicketListController.swift */, ); @@ -418,7 +418,7 @@ 5AB1C8F7262FE3520010F85E /* AppIdentity.swift in Sources */, 7DC2A22F26294F9E0002CEE6 /* HomeViewController.swift in Sources */, 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */, - 5A9FF8792637EFE800378550 /* IdentityView.swift in Sources */, + 5A9FF8792637EFE800378550 /* IdentityInitialized.swift in Sources */, 5AB1C8EF262FE3210010F85E /* TagManager.swift in Sources */, 5A9FF88E26386D4700378550 /* TicketListController.swift in Sources */, 5AB1C8E5262FE2E60010F85E /* TicketManager.swift in Sources */, diff --git a/Sluggo/Storyboards/Main.storyboard b/Sluggo/Storyboards/Main.storyboard index a613601..6f66454 100644 --- a/Sluggo/Storyboards/Main.storyboard +++ b/Sluggo/Storyboards/Main.storyboard @@ -178,7 +178,7 @@ - + diff --git a/Sluggo/View Controllers/HomeNavigationController.swift b/Sluggo/View Controllers/HomeNavigationController.swift index e8fe100..28b001e 100644 --- a/Sluggo/View Controllers/HomeNavigationController.swift +++ b/Sluggo/View Controllers/HomeNavigationController.swift @@ -7,7 +7,7 @@ import UIKit -class HomeNavigationController: UINavigationController, IdentityView { +class HomeNavigationController: UINavigationController, IdentityInitialized { var identity: AppIdentity? override func prepare(for segue: UIStoryboardSegue, sender: Any?) { diff --git a/Sluggo/View Controllers/HomeViewController.swift b/Sluggo/View Controllers/HomeViewController.swift index 2d1e1e4..fec4daa 100644 --- a/Sluggo/View Controllers/HomeViewController.swift +++ b/Sluggo/View Controllers/HomeViewController.swift @@ -7,7 +7,7 @@ import UIKit -class HomeViewController: UIViewController, IdentityView { +class HomeViewController: UIViewController, IdentityInitialized { public var identity: AppIdentity diff --git a/Sluggo/View Controllers/IdentityView.swift b/Sluggo/View Controllers/IdentityInitialized.swift similarity index 69% rename from Sluggo/View Controllers/IdentityView.swift rename to Sluggo/View Controllers/IdentityInitialized.swift index eb8a8fb..2d42003 100644 --- a/Sluggo/View Controllers/IdentityView.swift +++ b/Sluggo/View Controllers/IdentityInitialized.swift @@ -7,7 +7,6 @@ import UIKit -protocol IdentityView { +protocol IdentityInitialized { init? (coder: NSCoder, identity: AppIdentity) - func setIdentity(identity: AppIdentity) } diff --git a/Sluggo/View Controllers/RootViewController.swift b/Sluggo/View Controllers/RootViewController.swift index 3a42801..9c586b8 100644 --- a/Sluggo/View Controllers/RootViewController.swift +++ b/Sluggo/View Controllers/RootViewController.swift @@ -7,7 +7,7 @@ import UIKit -class RootViewController: UIViewController { +class RootViewController: UIViewController, IdentityInitialized { private var identity: AppIdentity @IBOutlet weak var mainContainerView: UIView! // contains the main app and tab bar controller @IBOutlet weak var sidebarContainerView: UIView! // contains the sidebar container view controller diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index 81f3c9b..3612f4f 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -7,7 +7,7 @@ import UIKit -class TicketListController: UITableViewController, IdentityView { +class TicketListController: UITableViewController, IdentityInitialized { var identity: AppIdentity From 2f382e47990e11070564f3c9eac29b678c88873a Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Tue, 27 Apr 2021 10:47:14 -0700 Subject: [PATCH 057/224] remove unnecessary files --- .../View Controllers/HomeControllerView.swift | 35 ------------------- .../HomeNavigationController.swift | 31 ---------------- 2 files changed, 66 deletions(-) delete mode 100644 Sluggo/View Controllers/HomeControllerView.swift delete mode 100644 Sluggo/View Controllers/HomeNavigationController.swift diff --git a/Sluggo/View Controllers/HomeControllerView.swift b/Sluggo/View Controllers/HomeControllerView.swift deleted file mode 100644 index 526c4a2..0000000 --- a/Sluggo/View Controllers/HomeControllerView.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// HomeControllerView.swift -// Sluggo -// -// Created by Samuel Schmidt on 4/26/21. -// - -import UIKit - -class HomeNavigationController: UINavigationController { - var identity: AppIdentity - - init? (coder: NSCoder, identity: AppIdentity) { - self.identity = identity - super.init(coder: coder) - } - - required init? (coder: NSCoder) { - fatalError("you fucked up") - } - - - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - print("custom prepare") - super.prepare(for: segue, sender: sender) - } - - @IBSegueAction func createHome(coder: NSCoder, sender: Any?, segueIdentifier: String?)-> HomeViewController? { - print("coder") - return HomeViewController(coder: coder) - } - - - -} diff --git a/Sluggo/View Controllers/HomeNavigationController.swift b/Sluggo/View Controllers/HomeNavigationController.swift deleted file mode 100644 index 28b001e..0000000 --- a/Sluggo/View Controllers/HomeNavigationController.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// HomeNavigationController.swift -// Sluggo -// -// Created by Samuel Schmidt on 4/27/21. -// - -import UIKit - -class HomeNavigationController: UINavigationController, IdentityInitialized { - var identity: AppIdentity? - - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - if let destination = segue.destination as? HomeViewController { - destination.setIdentity(identity: identity!) - } - } - - required init? (coder: NSCoder, identity: AppIdentity) { - self.identity = identity - super.init(coder: coder) - } - - required init? (coder: NSCoder) { - super.init(coder: coder) - } - - func setIdentity(identity: AppIdentity) { - self.identity = identity - } -} From 0d4df6941c5ecf51f51a0e1f45019734ba43d859 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Tue, 27 Apr 2021 22:10:20 -0700 Subject: [PATCH 058/224] remove sceneDelegate and appManifest stuff from info.plist. inject stuff --- Sluggo.xcodeproj/project.pbxproj | 24 --------- Sluggo/AppDelegate.swift | 47 ++++++++++------ Sluggo/Base.lproj/LaunchScreen.storyboard | 25 --------- Sluggo/Info.plist | 25 +-------- Sluggo/SceneDelegate.swift | 53 ------------------- Sluggo/Storyboards/Main.storyboard | 10 ++-- .../View Controllers/HomeViewController.swift | 9 +--- .../LaunchViewController.swift | 19 ++++++- .../LoginViewController.swift | 20 +++---- .../View Controllers/RootViewController.swift | 2 +- .../TicketListController.swift | 8 +-- 11 files changed, 67 insertions(+), 175 deletions(-) delete mode 100644 Sluggo/Base.lproj/LaunchScreen.storyboard delete mode 100644 Sluggo/SceneDelegate.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 2ccd88f..daf25c4 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -19,8 +19,6 @@ 2FEF5DAE262D1C4900434763 /* MemberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DAD262D1C4900434763 /* MemberTests.swift */; }; 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A79FFE0262E562A00D04320 /* Main.storyboard */; }; 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A79FFE5262E563F00D04320 /* LoginViewController.swift */; }; - 5A9FF8792637EFE800378550 /* IdentityInitialized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9FF8782637EFE800378550 /* IdentityInitialized.swift */; }; - 5A9FF87E2637F03600378550 /* HomeNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9FF87D2637F03600378550 /* HomeNavigationController.swift */; }; 5A9FF88926386D1400378550 /* Ticket.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A9FF88826386D1400378550 /* Ticket.storyboard */; }; 5A9FF88E26386D4700378550 /* TicketListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9FF88D26386D4700378550 /* TicketListController.swift */; }; 5AB1C8DD262FE2DD0010F85E /* TeamManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8DC262FE2DD0010F85E /* TeamManager.swift */; }; @@ -36,10 +34,8 @@ 7D1DA6AE262B82C200E7C12D /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */; }; 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */; }; 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */; }; - 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */; }; 7D9963AD261EF3A4002338E7 /* Root.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7D9963AB261EF3A4002338E7 /* Root.storyboard */; }; 7D9963B2261EF3A5002338E7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7D9963B1261EF3A5002338E7 /* Assets.xcassets */; }; - 7D9963B5261EF3A5002338E7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7D9963B3261EF3A5002338E7 /* LaunchScreen.storyboard */; }; 7D9963C0261EF3A5002338E7 /* SluggoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963BF261EF3A5002338E7 /* SluggoTests.swift */; }; 7D9963CB261EF3A5002338E7 /* SluggoUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963CA261EF3A5002338E7 /* SluggoUITests.swift */; }; 7DC2A21726293F360002CEE6 /* Home.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DC2A21626293F360002CEE6 /* Home.storyboard */; }; @@ -79,8 +75,6 @@ 2FEF5DAD262D1C4900434763 /* MemberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberTests.swift; sourceTree = ""; }; 5A79FFE0262E562A00D04320 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 5A79FFE5262E563F00D04320 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; - 5A9FF8782637EFE800378550 /* IdentityInitialized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityInitialized.swift; sourceTree = ""; }; - 5A9FF87D2637F03600378550 /* HomeNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeNavigationController.swift; sourceTree = ""; }; 5A9FF88826386D1400378550 /* Ticket.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Ticket.storyboard; sourceTree = ""; }; 5A9FF88D26386D4700378550 /* TicketListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketListController.swift; sourceTree = ""; }; 5AB1C8DC262FE2DD0010F85E /* TeamManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamManager.swift; sourceTree = ""; }; @@ -97,10 +91,8 @@ 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarData.swift; sourceTree = ""; }; 7D9963A2261EF3A4002338E7 /* Sluggo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sluggo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 7D9963AC261EF3A4002338E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Root.storyboard; sourceTree = ""; }; 7D9963B1261EF3A5002338E7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 7D9963B4261EF3A5002338E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 7D9963B6261EF3A5002338E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7D9963BB261EF3A5002338E7 /* SluggoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SluggoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 7D9963BF261EF3A5002338E7 /* SluggoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SluggoTests.swift; sourceTree = ""; }; @@ -208,9 +200,7 @@ 7DC2A21526293F1E0002CEE6 /* Storyboards */, 2F81E6E62627E85000B6D86D /* Models */, 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */, - 7D9963A7261EF3A4002338E7 /* SceneDelegate.swift */, 7D9963B1261EF3A5002338E7 /* Assets.xcassets */, - 7D9963B3261EF3A5002338E7 /* LaunchScreen.storyboard */, 7D9963B6261EF3A5002338E7 /* Info.plist */, ); path = Sluggo; @@ -257,8 +247,6 @@ 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */, 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */, 7DC2A22E26294F9E0002CEE6 /* HomeViewController.swift */, - 5A9FF8782637EFE800378550 /* IdentityInitialized.swift */, - 5A9FF87D2637F03600378550 /* HomeNavigationController.swift */, 5A9FF88D26386D4700378550 /* TicketListController.swift */, ); path = "View Controllers"; @@ -367,7 +355,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7D9963B5261EF3A5002338E7 /* LaunchScreen.storyboard in Resources */, 7D9963B2261EF3A5002338E7 /* Assets.xcassets in Resources */, 5A9FF88926386D1400378550 /* Ticket.storyboard in Resources */, 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */, @@ -408,7 +395,6 @@ 051AAB0B2630FD6500BCB9C7 /* Constants.swift in Sources */, 2F3F66E2262DD3BA0040404C /* JsonLoader.swift in Sources */, 2F2247AC262920AC006C6EE6 /* Token.swift in Sources */, - 5A9FF87E2637F03600378550 /* HomeNavigationController.swift in Sources */, 7D1DA6AE262B82C200E7C12D /* RootViewController.swift in Sources */, 5ACC0F012630CA1400DCF83E /* ErrorMessage.swift in Sources */, 2F3AD7A32627C86A00B61A97 /* User.swift in Sources */, @@ -418,11 +404,9 @@ 5AB1C8F7262FE3520010F85E /* AppIdentity.swift in Sources */, 7DC2A22F26294F9E0002CEE6 /* HomeViewController.swift in Sources */, 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */, - 5A9FF8792637EFE800378550 /* IdentityInitialized.swift in Sources */, 5AB1C8EF262FE3210010F85E /* TagManager.swift in Sources */, 5A9FF88E26386D4700378550 /* TicketListController.swift in Sources */, 5AB1C8E5262FE2E60010F85E /* TicketManager.swift in Sources */, - 7D9963A8261EF3A4002338E7 /* SceneDelegate.swift in Sources */, 2F3AD7A82627C89B00B61A97 /* Member.swift in Sources */, 5AB1C8DD262FE2DD0010F85E /* TeamManager.swift in Sources */, 5AB1C909262FFF500010F85E /* UserManager.swift in Sources */, @@ -472,14 +456,6 @@ name = Root.storyboard; sourceTree = ""; }; - 7D9963B3261EF3A5002338E7 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 7D9963B4261EF3A5002338E7 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ diff --git a/Sluggo/AppDelegate.swift b/Sluggo/AppDelegate.swift index 7137b7d..eee09cb 100644 --- a/Sluggo/AppDelegate.swift +++ b/Sluggo/AppDelegate.swift @@ -9,29 +9,46 @@ import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { - - + var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. - - // TODO Persistence + configureInitialViewController() return true } + + // https://stackoverflow.com/questions/33714671/value-of-type-appdelegate-has-no-member-window + private func configureInitialViewController() { + let storyboard = UIStoryboard(name: "Main", bundle: nil) + let initialViewController: UIViewController + + window = UIWindow() + let identity = AppIdentity.loadFromDisk() ?? AppIdentity() + + // TODO: for testing purposes, the team for the identity is hardcoded. Change this eventually :) + identity.team = TeamRecord(id: 1, name: "bugslotics", object_uuid: UUID(), ticket_head: 1, + created: Date(), activated: nil, deactivated: nil) + + initialViewController = storyboard.instantiateViewController(identifier: "launchView", creator: { coder in + return LaunchViewController(coder: coder, identity: identity) + }) + window!.rootViewController = initialViewController + window!.makeKeyAndVisible() + } // MARK: UISceneSession Lifecycle - func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { - // Called when a new scene session is being created. - // Use this method to select a configuration to create the new scene with. - return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) - } - - func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { - // Called when the user discards a scene session. - // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. - // Use this method to release any resources that were specific to the discarded scenes, as they will not return. - } +// func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { +// // Called when a new scene session is being created. +// // Use this method to select a configuration to create the new scene with. +// return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) +// } +// +// func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { +// // Called when the user discards a scene session. +// // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. +// // Use this method to release any resources that were specific to the discarded scenes, as they will not return. +// } } diff --git a/Sluggo/Base.lproj/LaunchScreen.storyboard b/Sluggo/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 865e932..0000000 --- a/Sluggo/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Sluggo/Info.plist b/Sluggo/Info.plist index bc9d90c..f5ed25e 100644 --- a/Sluggo/Info.plist +++ b/Sluggo/Info.plist @@ -20,31 +20,10 @@ 1 LSRequiresIPhoneOS - UIApplicationSceneManifest - - UIApplicationSupportsMultipleScenes - - UISceneConfigurations - - UIWindowSceneSessionRoleApplication - - - UISceneConfigurationName - Default Configuration - UISceneDelegateClassName - $(PRODUCT_MODULE_NAME).SceneDelegate - UISceneStoryboardFile - Main - - - - UIApplicationSupportsIndirectInputEvents UILaunchStoryboardName LaunchScreen - UIMainStoryboardFile - Main UIRequiredDeviceCapabilities armv7 @@ -55,8 +34,6 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIUserInterfaceStyle - Light UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait @@ -64,5 +41,7 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIUserInterfaceStyle + Light diff --git a/Sluggo/SceneDelegate.swift b/Sluggo/SceneDelegate.swift deleted file mode 100644 index 21e4809..0000000 --- a/Sluggo/SceneDelegate.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// SceneDelegate.swift -// Sluggo -// -// Created by Isaac Trimble-Pederson on 4/8/21. -// - -import UIKit - -class SceneDelegate: UIResponder, UIWindowSceneDelegate { - - var window: UIWindow? - - - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. - // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. - // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). - guard let _ = (scene as? UIWindowScene) else { return } - } - - func sceneDidDisconnect(_ scene: UIScene) { - // Called as the scene is being released by the system. - // This occurs shortly after the scene enters the background, or when its session is discarded. - // Release any resources associated with this scene that can be re-created the next time the scene connects. - // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). - } - - func sceneDidBecomeActive(_ scene: UIScene) { - // Called when the scene has moved from an inactive state to an active state. - // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. - } - - func sceneWillResignActive(_ scene: UIScene) { - // Called when the scene will move from an active state to an inactive state. - // This may occur due to temporary interruptions (ex. an incoming phone call). - } - - func sceneWillEnterForeground(_ scene: UIScene) { - // Called as the scene transitions from the background to the foreground. - // Use this method to undo the changes made on entering the background. - } - - func sceneDidEnterBackground(_ scene: UIScene) { - // Called as the scene transitions from the foreground to the background. - // Use this method to save data, release shared resources, and store enough scene-specific state information - // to restore the scene back to its current state. - - } - - -} - diff --git a/Sluggo/Storyboards/Main.storyboard b/Sluggo/Storyboards/Main.storyboard index 6f66454..c7c5200 100644 --- a/Sluggo/Storyboards/Main.storyboard +++ b/Sluggo/Storyboards/Main.storyboard @@ -11,7 +11,7 @@ - + @@ -31,8 +31,8 @@ - - + + @@ -42,7 +42,7 @@ - + @@ -178,7 +178,7 @@ - + diff --git a/Sluggo/View Controllers/HomeViewController.swift b/Sluggo/View Controllers/HomeViewController.swift index fec4daa..4b1c552 100644 --- a/Sluggo/View Controllers/HomeViewController.swift +++ b/Sluggo/View Controllers/HomeViewController.swift @@ -7,11 +7,11 @@ import UIKit -class HomeViewController: UIViewController, IdentityInitialized { +class HomeViewController: UIViewController { public var identity: AppIdentity - required init?(coder: NSCoder, identity: AppIdentity) { + init?(coder: NSCoder, identity: AppIdentity) { self.identity = identity super.init(coder: coder) } @@ -20,11 +20,6 @@ class HomeViewController: UIViewController, IdentityInitialized { fatalError("identity must be passed by ctor") } - func setIdentity(identity: AppIdentity) { - print("is set") - self.identity = identity - } - @IBAction func pushDaButton(_ sender: Any) { NotificationCenter.default.post(Notification(name: .onSidebarTrigger, userInfo: [Sidebar.USER_INFO_KEY : SidebarStatus.open])) } diff --git a/Sluggo/View Controllers/LaunchViewController.swift b/Sluggo/View Controllers/LaunchViewController.swift index d5cdd07..8bfd0f8 100644 --- a/Sluggo/View Controllers/LaunchViewController.swift +++ b/Sluggo/View Controllers/LaunchViewController.swift @@ -9,6 +9,17 @@ import UIKit class LaunchViewController: UIViewController { + var identity: AppIdentity + + init? (coder: NSCoder, identity: AppIdentity) { + self.identity = identity + super.init(coder: coder) + } + + required init? (coder: NSCoder) { + fatalError("must create class with identity") + } + override func viewDidLoad() { super.viewDidLoad() } @@ -23,8 +34,6 @@ class LaunchViewController: UIViewController { sleep(1) self.performSegue(withIdentifier: "showLogin", sender: self) } - - } func continueLogin() { @@ -32,4 +41,10 @@ class LaunchViewController: UIViewController { self.performSegue(withIdentifier: "automaticLogin", sender: self) } + @IBSegueAction func createRoot(_ coder: NSCoder) -> UIViewController? { + return RootViewController(coder: coder, identity: identity) + } + @IBSegueAction func createLogin(_ coder: NSCoder) -> LoginViewController? { + return LoginViewController(coder: coder, identity: identity) + } } diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index aa9b243..dc72139 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -19,30 +19,22 @@ class LoginViewController: UIViewController { // segues and otherwise. // Attempt to load from disk, otherwise, use the new one. - let identity = AppIdentity.loadFromDisk() ?? AppIdentity() + var identity: AppIdentity - convenience init() { - self.init(nibName:nil, bundle:nil) + init? (coder: NSCoder, identity: AppIdentity) { + self.identity = identity + super.init(coder: coder) } - - // This extends the superclass. - override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) - } - + // This is also necessary when extending the superclass. required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) + fatalError("must be created with identity") } override func viewDidLoad() { super.viewDidLoad() self.isModalInPresentation = true - // TODO: for testing purposes, the team for the identity is hardcoded. Change this eventually : ) - self.identity.team = TeamRecord(id: 1, name: "bugslotics", object_uuid: UUID(), ticket_head: 1, - created: Date(), activated: nil, deactivated: nil) - // only enable persistence button if the token is nil (that is, we didn't load an AppIdentity) self.persistButton.isEnabled = (self.identity.token == nil) self.loginButton.isEnabled = (self.identity.token == nil) diff --git a/Sluggo/View Controllers/RootViewController.swift b/Sluggo/View Controllers/RootViewController.swift index 9c586b8..3a42801 100644 --- a/Sluggo/View Controllers/RootViewController.swift +++ b/Sluggo/View Controllers/RootViewController.swift @@ -7,7 +7,7 @@ import UIKit -class RootViewController: UIViewController, IdentityInitialized { +class RootViewController: UIViewController { private var identity: AppIdentity @IBOutlet weak var mainContainerView: UIView! // contains the main app and tab bar controller @IBOutlet weak var sidebarContainerView: UIView! // contains the sidebar container view controller diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index 3612f4f..7db5948 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -7,13 +7,13 @@ import UIKit -class TicketListController: UITableViewController, IdentityInitialized { +class TicketListController: UITableViewController { var identity: AppIdentity var tickets: [TicketRecord] - required init? (coder: NSCoder, identity: AppIdentity) { + init? (coder: NSCoder, identity: AppIdentity) { self.identity = identity self.tickets = [] super.init(coder: coder) @@ -23,10 +23,6 @@ class TicketListController: UITableViewController, IdentityInitialized { fatalError("must be initialized with identity") } - func setIdentity(identity: AppIdentity) { - self.identity = identity - } - override func viewDidAppear(_ animated: Bool) { loadData() } From b36a4665263be56b93c9d495657b3d4eb73dce81 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Wed, 28 Apr 2021 12:04:25 -0700 Subject: [PATCH 059/224] Re-add launch screen --- Sluggo.xcodeproj/project.pbxproj | 4 ++ Sluggo/Storyboards/LaunchScreen.storyboard | 44 ++++++++++++++++++++++ Sluggo/Storyboards/Main.storyboard | 6 +-- 3 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 Sluggo/Storyboards/LaunchScreen.storyboard diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index daf25c4..5ffd9ef 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -43,6 +43,7 @@ 7DC2A2222629447D0002CEE6 /* SluggoSidebarTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC2A2212629447D0002CEE6 /* SluggoSidebarTableViewController.swift */; }; 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */; }; 7DC2A22F26294F9E0002CEE6 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC2A22E26294F9E0002CEE6 /* HomeViewController.swift */; }; + 7DEA17852639E7B5004E5A71 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DEA17842639E7B5004E5A71 /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -105,6 +106,7 @@ 7DC2A2212629447D0002CEE6 /* SluggoSidebarTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SluggoSidebarTableViewController.swift; sourceTree = ""; }; 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SluggoSidebarContainerViewController.swift; sourceTree = ""; }; 7DC2A22E26294F9E0002CEE6 /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; + 7DEA17842639E7B5004E5A71 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -234,6 +236,7 @@ 7DC2A21626293F360002CEE6 /* Home.storyboard */, 7DC2A21B262943A70002CEE6 /* Sidebar.storyboard */, 5A9FF88826386D1400378550 /* Ticket.storyboard */, + 7DEA17842639E7B5004E5A71 /* LaunchScreen.storyboard */, ); path = Storyboards; sourceTree = ""; @@ -359,6 +362,7 @@ 5A9FF88926386D1400378550 /* Ticket.storyboard in Resources */, 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */, 7D9963AD261EF3A4002338E7 /* Root.storyboard in Resources */, + 7DEA17852639E7B5004E5A71 /* LaunchScreen.storyboard in Resources */, 7DC2A21726293F360002CEE6 /* Home.storyboard in Resources */, 7DC2A21C262943A70002CEE6 /* Sidebar.storyboard in Resources */, ); diff --git a/Sluggo/Storyboards/LaunchScreen.storyboard b/Sluggo/Storyboards/LaunchScreen.storyboard new file mode 100644 index 0000000..279c780 --- /dev/null +++ b/Sluggo/Storyboards/LaunchScreen.storyboard @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sluggo/Storyboards/Main.storyboard b/Sluggo/Storyboards/Main.storyboard index c7c5200..bae1ebb 100644 --- a/Sluggo/Storyboards/Main.storyboard +++ b/Sluggo/Storyboards/Main.storyboard @@ -1,8 +1,8 @@ - + - + @@ -178,7 +178,7 @@ - + From c539807f54d06f3bc968039304af3703644bb132 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Wed, 28 Apr 2021 12:05:57 -0700 Subject: [PATCH 060/224] Remove arbitrary sleeps --- Sluggo/View Controllers/LaunchViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sluggo/View Controllers/LaunchViewController.swift b/Sluggo/View Controllers/LaunchViewController.swift index 8bfd0f8..9629248 100644 --- a/Sluggo/View Controllers/LaunchViewController.swift +++ b/Sluggo/View Controllers/LaunchViewController.swift @@ -31,13 +31,13 @@ class LaunchViewController: UIViewController { if(remember) { // Call login function from remembered. If failed go to login } else { - sleep(1) +// sleep(1) self.performSegue(withIdentifier: "showLogin", sender: self) } } func continueLogin() { - sleep(3) +// sleep(3) self.performSegue(withIdentifier: "automaticLogin", sender: self) } From 55f5e680e1084a4bceb87024c82fec5266093fb5 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 28 Apr 2021 12:20:36 -0700 Subject: [PATCH 061/224] regularize urls --- Sluggo/Models/Managers/MemberManager.swift | 11 ++++++----- Sluggo/Models/Managers/TicketManager.swift | 5 +++-- Sluggo/Models/Managers/UserManager.swift | 5 +++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Sluggo/Models/Managers/MemberManager.swift b/Sluggo/Models/Managers/MemberManager.swift index 0d7dcba..0b41051 100644 --- a/Sluggo/Models/Managers/MemberManager.swift +++ b/Sluggo/Models/Managers/MemberManager.swift @@ -8,6 +8,7 @@ import Foundation class MemberManager { + static let urlBase = "/members/" private var identity: AppIdentity init(_ identity: AppIdentity) { @@ -15,17 +16,17 @@ class MemberManager { } private func makeDetailUrl(memberRecord: MemberRecord) -> URL { - return URL(string: identity.baseAddress + "/api/teams/" + "\(identity.team!.id)" + "/members/" + "\(memberRecord.id)/")! + return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + MemberManager.urlBase + "\(memberRecord.id)/")! } private func makeListUrl() -> URL { - return URL(string: identity.baseAddress + "/api/teams/" + "\(identity.team!.id)" + "/members/")! + return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + MemberManager.urlBase)! } public func fetchMemberRecord(completionHandler: @escaping(Result) -> Void) -> Void { var request = URLRequest(url: makeListUrl()) request.httpMethod = "GET" - request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") + request.setValue("Bearer \(self.identity.token!)", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") JsonLoader.executeCodableRequest(request: request, completionHandler: completionHandler) @@ -41,7 +42,7 @@ class MemberManager { } request.httpBody = body - request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") + request.setValue("Bearer \(self.identity.token!)", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") JsonLoader.executeCodableRequest(request: request, completionHandler: completionHandler) @@ -51,7 +52,7 @@ class MemberManager { public func listTeamMembers(completionHandler: @escaping(Result, Error>) -> Void) -> Void{ var request = URLRequest(url: makeListUrl()) request.httpMethod = "GET" - request.setValue("Bearer \(self.identity.token)", forHTTPHeaderField: "Authorization") + request.setValue("Bearer \(self.identity.token!)", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") JsonLoader.executeCodableRequest(request: request, completionHandler: completionHandler) diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index a73c2d7..7591234 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -8,6 +8,7 @@ import Foundation class TicketManager { + static let urlBase = "/tickets/" private var identity: AppIdentity init(_ identity: AppIdentity) { @@ -15,11 +16,11 @@ class TicketManager { } private func makeDetailUrl(_ ticketRecord: TicketRecord) -> URL { - return URL(string: identity.baseAddress + "api/teams/" + "\(identity.team!.id)" + "/tickets/" + "\(ticketRecord.id)/")! + return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + TicketManager.urlBase + "\(ticketRecord.id)/")! } private func makeListUrl() -> URL { - return URL(string: identity.baseAddress + "api/teams/" + "\(identity.team!.id)" + "/tickets/")! + return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + TicketManager.urlBase)! } public func listTeamTickets(completionHandler: @escaping (Result, Error>) -> Void) -> Void { diff --git a/Sluggo/Models/Managers/UserManager.swift b/Sluggo/Models/Managers/UserManager.swift index 8d61a65..a262e4f 100644 --- a/Sluggo/Models/Managers/UserManager.swift +++ b/Sluggo/Models/Managers/UserManager.swift @@ -8,6 +8,7 @@ import Foundation class UserManager { + static let urlBase = "auth/" private var identity: AppIdentity init(identity: AppIdentity) { @@ -16,7 +17,7 @@ class UserManager { public func doLogin(username: String, password: String, completionHandler: @escaping(Result) -> Void) -> Void { let params = ["username":username, "password":password] as Dictionary - var request = URLRequest(url: URL(string: identity.baseAddress + "auth/login/")!) + var request = URLRequest(url: URL(string: identity.baseAddress + UserManager.urlBase + "login/")!) request.httpMethod = "POST" guard let body = try? JSONSerialization.data(withJSONObject: params, options: []) else { completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize JSON for doLogin in UserManager"))) @@ -28,7 +29,7 @@ class UserManager { } public func doLogout(token: String, completionHandler: @escaping(Result) -> Void) -> Void { // TODO: this is probably incorrect - var request = URLRequest(url: URL(string: identity.baseAddress + "auth/logout/")!) + var request = URLRequest(url: URL(string: identity.baseAddress + UserManager.urlBase + "logout/")!) request.httpMethod = "POST" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") From 6e52df9c7d3c01382e7713cdac604b0ee478787f Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 28 Apr 2021 12:51:48 -0700 Subject: [PATCH 062/224] builder & abstractions --- Sluggo.xcodeproj/project.pbxproj | 4 ++ Sluggo/Models/Managers/MemberManager.swift | 35 +++++++++-------- Sluggo/Models/Managers/TeamManager.swift | 9 ++--- Sluggo/Models/Managers/TicketManager.swift | 20 +++++----- Sluggo/Models/Managers/UserManager.swift | 23 +++++------ Sluggo/Models/RequestBuilder.swift | 44 ++++++++++++++++++++++ Sluggo/Models/URLRequestBuilder.swift | 44 ++++++++++++++++++++++ 7 files changed, 134 insertions(+), 45 deletions(-) create mode 100644 Sluggo/Models/RequestBuilder.swift create mode 100644 Sluggo/Models/URLRequestBuilder.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 5ffd9ef..ff62913 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 2FD70D5D2636144300D13C75 /* LogoutMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FD70D5C2636144300D13C75 /* LogoutMessage.swift */; }; 2FEF5DA9262D0E7C00434763 /* PaginatedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */; }; 2FEF5DAE262D1C4900434763 /* MemberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DAD262D1C4900434763 /* MemberTests.swift */; }; + 5A00A0142639EDEA0085C8C6 /* URLRequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */; }; 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A79FFE0262E562A00D04320 /* Main.storyboard */; }; 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A79FFE5262E563F00D04320 /* LoginViewController.swift */; }; 5A9FF88926386D1400378550 /* Ticket.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A9FF88826386D1400378550 /* Ticket.storyboard */; }; @@ -74,6 +75,7 @@ 2FD70D5C2636144300D13C75 /* LogoutMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutMessage.swift; sourceTree = ""; }; 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginatedList.swift; sourceTree = ""; }; 2FEF5DAD262D1C4900434763 /* MemberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberTests.swift; sourceTree = ""; }; + 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLRequestBuilder.swift; sourceTree = ""; }; 5A79FFE0262E562A00D04320 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 5A79FFE5262E563F00D04320 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 5A9FF88826386D1400378550 /* Ticket.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Ticket.storyboard; sourceTree = ""; }; @@ -144,6 +146,7 @@ 5AB1C8F6262FE3520010F85E /* AppIdentity.swift */, 051AAB0A2630FD6500BCB9C7 /* Constants.swift */, 5ACC0F052630CA2700DCF83E /* Exceptions.swift */, + 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */, ); path = Models; sourceTree = ""; @@ -392,6 +395,7 @@ 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */, 5AB1C8FC262FF1690010F85E /* TicketRecord.swift in Sources */, 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */, + 5A00A0142639EDEA0085C8C6 /* URLRequestBuilder.swift in Sources */, 2FD70D5D2636144300D13C75 /* LogoutMessage.swift in Sources */, 5AB1C8EA262FE2EF0010F85E /* MemberManager.swift in Sources */, 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */, diff --git a/Sluggo/Models/Managers/MemberManager.swift b/Sluggo/Models/Managers/MemberManager.swift index 0b41051..4a95da9 100644 --- a/Sluggo/Models/Managers/MemberManager.swift +++ b/Sluggo/Models/Managers/MemberManager.swift @@ -23,38 +23,37 @@ class MemberManager { return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + MemberManager.urlBase)! } - public func fetchMemberRecord(completionHandler: @escaping(Result) -> Void) -> Void { - var request = URLRequest(url: makeListUrl()) - request.httpMethod = "GET" - request.setValue("Bearer \(self.identity.token!)", forHTTPHeaderField: "Authorization") - request.addValue("application/json", forHTTPHeaderField: "Content-Type") + public func fetchTeamMembers(completionHandler: @escaping(Result) -> Void) -> Void { - JsonLoader.executeCodableRequest(request: request, completionHandler: completionHandler) + let requestBuilder = URLRequestBuilder(url: makeListUrl()) + .setMethod(method: HTTPMethod.GET) + .setIdentity(identity: identity) + + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } public func updateMemberRecord(_ memberRecord: MemberRecord, completionHandler: @escaping(Result) -> Void) -> Void { - var request = URLRequest(url: makeDetailUrl(memberRecord: memberRecord)) - request.httpMethod = "Put" guard let body = JsonLoader.encode(object: memberRecord) else { completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize member JSON for updateMemberRecord in MemberManager"))) return } + + let requestBuilder = URLRequestBuilder(url: makeDetailUrl(memberRecord: memberRecord)) + .setData(data: body) + .setMethod(method: HTTPMethod.PUT) + .setIdentity(identity: identity) - request.httpBody = body - request.setValue("Bearer \(self.identity.token!)", forHTTPHeaderField: "Authorization") - request.addValue("application/json", forHTTPHeaderField: "Content-Type") - - JsonLoader.executeCodableRequest(request: request, completionHandler: completionHandler) + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } public func listTeamMembers(completionHandler: @escaping(Result, Error>) -> Void) -> Void{ - var request = URLRequest(url: makeListUrl()) - request.httpMethod = "GET" - request.setValue("Bearer \(self.identity.token!)", forHTTPHeaderField: "Authorization") - request.addValue("application/json", forHTTPHeaderField: "Content-Type") - JsonLoader.executeCodableRequest(request: request, completionHandler: completionHandler) + let requestBuilder = URLRequestBuilder(url: makeListUrl()) + .setIdentity(identity: identity) + .setMethod(method: HTTPMethod.GET) + + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } } diff --git a/Sluggo/Models/Managers/TeamManager.swift b/Sluggo/Models/Managers/TeamManager.swift index d6d5100..47eb52a 100644 --- a/Sluggo/Models/Managers/TeamManager.swift +++ b/Sluggo/Models/Managers/TeamManager.swift @@ -17,11 +17,10 @@ class TeamManager { public func listUserTeams(completionHandler: @escaping(Result, Error>) -> Void) -> Void { - var request = URLRequest(url: URL(string: identity.baseAddress + TeamManager.urlBase)!) - request.httpMethod = "GET" - request.setValue("Bearer \(self.identity.token!)", forHTTPHeaderField: "Authorization") - request.addValue("application/json", forHTTPHeaderField: "Content-Type") + let requestBuilder = URLRequestBuilder(url: URL(string: identity.baseAddress + TeamManager.urlBase)!) + .setIdentity(identity: identity) + .setMethod(method: HTTPMethod.GET) - JsonLoader.executeCodableRequest(request: request, completionHandler: completionHandler) + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } } diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index 7591234..fb9ec07 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -24,27 +24,25 @@ class TicketManager { } public func listTeamTickets(completionHandler: @escaping (Result, Error>) -> Void) -> Void { - var request = URLRequest(url: makeListUrl()) - request.setValue("Bearer \(self.identity.token!)", forHTTPHeaderField: "Authorization") - request.addValue("application/json", forHTTPHeaderField: "Content-Type") + let requestBuilder = URLRequestBuilder(url: makeListUrl()) + .setMethod(method: HTTPMethod.GET) + .setIdentity(identity: self.identity) - JsonLoader.executeCodableRequest(request: request, completionHandler: completionHandler) + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } public func updateTicket(_ ticket: TicketRecord, completionHandler: @escaping(Result) -> Void)-> Void { - var request = URLRequest(url: makeDetailUrl(ticket)) - request.httpMethod = "PUT" - guard let body = JsonLoader.encode(object: ticket) else { completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize ticket JSON for updateTicket in TicketManager"))) return } - request.httpBody = body - request.setValue("Bearer \(self.identity.token!)", forHTTPHeaderField: "Authorization") - request.addValue("application/json", forHTTPHeaderField: "Content-Type") + let requestBuilder = URLRequestBuilder(url: makeDetailUrl(ticket)) + .setMethod(method: HTTPMethod.PUT) + .setData(data: body) + .setIdentity(identity: self.identity) - JsonLoader.executeCodableRequest(request: request, completionHandler: completionHandler) + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } } diff --git a/Sluggo/Models/Managers/UserManager.swift b/Sluggo/Models/Managers/UserManager.swift index a262e4f..dbf25b4 100644 --- a/Sluggo/Models/Managers/UserManager.swift +++ b/Sluggo/Models/Managers/UserManager.swift @@ -17,22 +17,23 @@ class UserManager { public func doLogin(username: String, password: String, completionHandler: @escaping(Result) -> Void) -> Void { let params = ["username":username, "password":password] as Dictionary - var request = URLRequest(url: URL(string: identity.baseAddress + UserManager.urlBase + "login/")!) - request.httpMethod = "POST" guard let body = try? JSONSerialization.data(withJSONObject: params, options: []) else { completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize JSON for doLogin in UserManager"))) return; } - request.httpBody = body; - request.addValue("application/json", forHTTPHeaderField: "Content-Type") - JsonLoader.executeCodableRequest(request: request, completionHandler: completionHandler) + + let requestBuilder = URLRequestBuilder(url: URL(string: identity.baseAddress + UserManager.urlBase + "login/")!) + .setData(data: body) + .setMethod(method: HTTPMethod.POST) + + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } - public func doLogout(token: String, completionHandler: @escaping(Result) -> Void) -> Void { // TODO: this is probably incorrect - var request = URLRequest(url: URL(string: identity.baseAddress + UserManager.urlBase + "logout/")!) - request.httpMethod = "POST" - request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") - request.addValue("application/json", forHTTPHeaderField: "Content-Type") - JsonLoader.executeCodableRequest(request: request, completionHandler: completionHandler) + public func doLogout(completionHandler: @escaping(Result) -> Void) -> Void { // TODO: this is probably incorrect + let requestBuilder = URLRequestBuilder(url: URL(string: identity.baseAddress + UserManager.urlBase + "logout/")!) + .setMethod(method: HTTPMethod.POST) + .setIdentity(identity: self.identity) + + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } } diff --git a/Sluggo/Models/RequestBuilder.swift b/Sluggo/Models/RequestBuilder.swift new file mode 100644 index 0000000..5394e80 --- /dev/null +++ b/Sluggo/Models/RequestBuilder.swift @@ -0,0 +1,44 @@ +// +// RequestBuilder.swift +// Sluggo +// +// Created by Samuel Schmidt on 4/28/21. +// + +import Foundation + +enum HTTPMethod: String { + case GET = "GET" + case POST = "POST" + case PUT = "PUT" + case PATCH = "PATCH" + case DELETE = "DELETE" +} + +class URLRequestBuilder { + private var request: URLRequest + + init(url: URL) { + request = URLRequest(url: url) + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + } + + func setMethod(method: HTTPMethod) -> URLRequestBuilder { + request.httpMethod = method.rawValue + return self + } + + func setData(data: Data) -> URLRequestBuilder { + request.httpBody = data + return self + } + + func setIdentity(identity: AppIdentity) { + request.setValue("Bearer \(self.identity.token!)", forHTTPHeaderField: "Authorization") + return self + } + + func getRequest() -> URLRequest { + return request + } +} diff --git a/Sluggo/Models/URLRequestBuilder.swift b/Sluggo/Models/URLRequestBuilder.swift new file mode 100644 index 0000000..e6c298d --- /dev/null +++ b/Sluggo/Models/URLRequestBuilder.swift @@ -0,0 +1,44 @@ +// +// RequestBuilder.swift +// Sluggo +// +// Created by Samuel Schmidt on 4/28/21. +// + +import Foundation + +enum HTTPMethod: String { + case GET = "GET" + case POST = "POST" + case PUT = "PUT" + case PATCH = "PATCH" + case DELETE = "DELETE" +} + +class URLRequestBuilder { + private var request: URLRequest + + init(url: URL) { + request = URLRequest(url: url) + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + } + + func setMethod(method: HTTPMethod) -> URLRequestBuilder { + request.httpMethod = method.rawValue + return self + } + + func setData(data: Data) -> URLRequestBuilder { + request.httpBody = data + return self + } + + func setIdentity(identity: AppIdentity) -> URLRequestBuilder { + request.setValue("Bearer \(identity.token!)", forHTTPHeaderField: "Authorization") + return self + } + + func getRequest() -> URLRequest { + return request + } +} From 068bd074fc11565245d0bcf22445c8c1c98fde8b Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 28 Apr 2021 12:55:06 -0700 Subject: [PATCH 063/224] remove RequestBuilder --- Sluggo/Models/RequestBuilder.swift | 44 ------------------------------ 1 file changed, 44 deletions(-) delete mode 100644 Sluggo/Models/RequestBuilder.swift diff --git a/Sluggo/Models/RequestBuilder.swift b/Sluggo/Models/RequestBuilder.swift deleted file mode 100644 index 5394e80..0000000 --- a/Sluggo/Models/RequestBuilder.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// RequestBuilder.swift -// Sluggo -// -// Created by Samuel Schmidt on 4/28/21. -// - -import Foundation - -enum HTTPMethod: String { - case GET = "GET" - case POST = "POST" - case PUT = "PUT" - case PATCH = "PATCH" - case DELETE = "DELETE" -} - -class URLRequestBuilder { - private var request: URLRequest - - init(url: URL) { - request = URLRequest(url: url) - request.addValue("application/json", forHTTPHeaderField: "Content-Type") - } - - func setMethod(method: HTTPMethod) -> URLRequestBuilder { - request.httpMethod = method.rawValue - return self - } - - func setData(data: Data) -> URLRequestBuilder { - request.httpBody = data - return self - } - - func setIdentity(identity: AppIdentity) { - request.setValue("Bearer \(self.identity.token!)", forHTTPHeaderField: "Authorization") - return self - } - - func getRequest() -> URLRequest { - return request - } -} From 2f788323da91cbf6383f202cf65c6771712cf0e8 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 28 Apr 2021 12:56:36 -0700 Subject: [PATCH 064/224] cleanup --- Sluggo/Models/Managers/MemberManager.swift | 6 +++--- Sluggo/Models/Managers/TeamManager.swift | 2 +- Sluggo/Models/Managers/TicketManager.swift | 4 ++-- Sluggo/Models/Managers/UserManager.swift | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sluggo/Models/Managers/MemberManager.swift b/Sluggo/Models/Managers/MemberManager.swift index 4a95da9..556d421 100644 --- a/Sluggo/Models/Managers/MemberManager.swift +++ b/Sluggo/Models/Managers/MemberManager.swift @@ -26,7 +26,7 @@ class MemberManager { public func fetchTeamMembers(completionHandler: @escaping(Result) -> Void) -> Void { let requestBuilder = URLRequestBuilder(url: makeListUrl()) - .setMethod(method: HTTPMethod.GET) + .setMethod(method: .GET) .setIdentity(identity: identity) JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) @@ -41,7 +41,7 @@ class MemberManager { let requestBuilder = URLRequestBuilder(url: makeDetailUrl(memberRecord: memberRecord)) .setData(data: body) - .setMethod(method: HTTPMethod.PUT) + .setMethod(method: .PUT) .setIdentity(identity: identity) JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) @@ -52,7 +52,7 @@ class MemberManager { let requestBuilder = URLRequestBuilder(url: makeListUrl()) .setIdentity(identity: identity) - .setMethod(method: HTTPMethod.GET) + .setMethod(method: .GET) JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } diff --git a/Sluggo/Models/Managers/TeamManager.swift b/Sluggo/Models/Managers/TeamManager.swift index 47eb52a..83a6db5 100644 --- a/Sluggo/Models/Managers/TeamManager.swift +++ b/Sluggo/Models/Managers/TeamManager.swift @@ -19,7 +19,7 @@ class TeamManager { let requestBuilder = URLRequestBuilder(url: URL(string: identity.baseAddress + TeamManager.urlBase)!) .setIdentity(identity: identity) - .setMethod(method: HTTPMethod.GET) + .setMethod(method: .GET) JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index fb9ec07..56aac5e 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -25,7 +25,7 @@ class TicketManager { public func listTeamTickets(completionHandler: @escaping (Result, Error>) -> Void) -> Void { let requestBuilder = URLRequestBuilder(url: makeListUrl()) - .setMethod(method: HTTPMethod.GET) + .setMethod(method: .GET) .setIdentity(identity: self.identity) JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) @@ -38,7 +38,7 @@ class TicketManager { } let requestBuilder = URLRequestBuilder(url: makeDetailUrl(ticket)) - .setMethod(method: HTTPMethod.PUT) + .setMethod(method: .PUT) .setData(data: body) .setIdentity(identity: self.identity) diff --git a/Sluggo/Models/Managers/UserManager.swift b/Sluggo/Models/Managers/UserManager.swift index dbf25b4..0709d9a 100644 --- a/Sluggo/Models/Managers/UserManager.swift +++ b/Sluggo/Models/Managers/UserManager.swift @@ -24,14 +24,14 @@ class UserManager { let requestBuilder = URLRequestBuilder(url: URL(string: identity.baseAddress + UserManager.urlBase + "login/")!) .setData(data: body) - .setMethod(method: HTTPMethod.POST) + .setMethod(method: .POST) JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } public func doLogout(completionHandler: @escaping(Result) -> Void) -> Void { // TODO: this is probably incorrect let requestBuilder = URLRequestBuilder(url: URL(string: identity.baseAddress + UserManager.urlBase + "logout/")!) - .setMethod(method: HTTPMethod.POST) + .setMethod(method: .POST) .setIdentity(identity: self.identity) JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) From d997f2ada101e17ee8a973082d886f500b3e3b51 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Wed, 28 Apr 2021 14:09:27 -0700 Subject: [PATCH 065/224] Added tests and updated ticketRecord to work --- Sluggo.xcodeproj/project.pbxproj | 8 +- Sluggo/Models/Codables/TicketRecord.swift | 5 +- SluggoTests/TicketTests.swift | 128 ++++++++++++++++++++++ 3 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 SluggoTests/TicketTests.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 2f3a54c..cf913f0 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -14,12 +14,13 @@ 2F3AD7A82627C89B00B61A97 /* Member.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A72627C89B00B61A97 /* Member.swift */; }; 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7AC2627C8EF00B61A97 /* Team.swift */; }; 2F3F66E2262DD3BA0040404C /* JsonLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3F66E1262DD3BA0040404C /* JsonLoader.swift */; }; + 2F44E21D2639FA6B006CA85D /* TicketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F44E21C2639FA6B006CA85D /* TicketTests.swift */; }; 2FD70D5D2636144300D13C75 /* LogoutMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FD70D5C2636144300D13C75 /* LogoutMessage.swift */; }; 2FEF5DA9262D0E7C00434763 /* PaginatedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */; }; 2FEF5DAE262D1C4900434763 /* MemberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DAD262D1C4900434763 /* MemberTests.swift */; }; - 5A00A0142639EDEA0085C8C6 /* URLRequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */; }; 3A150A6726365A9A0052934B /* StatusRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A6626365A9A0052934B /* StatusRecord.swift */; }; 3A150A6C26365B670052934B /* TagRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A6B26365B670052934B /* TagRecord.swift */; }; + 5A00A0142639EDEA0085C8C6 /* URLRequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */; }; 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A79FFE0262E562A00D04320 /* Main.storyboard */; }; 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A79FFE5262E563F00D04320 /* LoginViewController.swift */; }; 5A9FF88926386D1400378550 /* Ticket.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A9FF88826386D1400378550 /* Ticket.storyboard */; }; @@ -74,12 +75,13 @@ 2F3AD7A72627C89B00B61A97 /* Member.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Member.swift; sourceTree = ""; }; 2F3AD7AC2627C8EF00B61A97 /* Team.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Team.swift; sourceTree = ""; }; 2F3F66E1262DD3BA0040404C /* JsonLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JsonLoader.swift; sourceTree = ""; }; + 2F44E21C2639FA6B006CA85D /* TicketTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketTests.swift; sourceTree = ""; }; 2FD70D5C2636144300D13C75 /* LogoutMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutMessage.swift; sourceTree = ""; }; 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginatedList.swift; sourceTree = ""; }; 2FEF5DAD262D1C4900434763 /* MemberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberTests.swift; sourceTree = ""; }; - 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLRequestBuilder.swift; sourceTree = ""; }; 3A150A6626365A9A0052934B /* StatusRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusRecord.swift; sourceTree = ""; }; 3A150A6B26365B670052934B /* TagRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagRecord.swift; sourceTree = ""; }; + 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLRequestBuilder.swift; sourceTree = ""; }; 5A79FFE0262E562A00D04320 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 5A79FFE5262E563F00D04320 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 5A9FF88826386D1400378550 /* Ticket.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Ticket.storyboard; sourceTree = ""; }; @@ -224,6 +226,7 @@ 7D9963C1261EF3A5002338E7 /* Info.plist */, 7D1354FB262D026E00D38EE0 /* UserTests.swift */, 2FEF5DAD262D1C4900434763 /* MemberTests.swift */, + 2F44E21C2639FA6B006CA85D /* TicketTests.swift */, ); path = SluggoTests; sourceTree = ""; @@ -437,6 +440,7 @@ 7D1354FC262D026E00D38EE0 /* UserTests.swift in Sources */, 2FEF5DAE262D1C4900434763 /* MemberTests.swift in Sources */, 7D9963C0261EF3A5002338E7 /* SluggoTests.swift in Sources */, + 2F44E21D2639FA6B006CA85D /* TicketTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sluggo/Models/Codables/TicketRecord.swift b/Sluggo/Models/Codables/TicketRecord.swift index d90f0e0..149d78a 100644 --- a/Sluggo/Models/Codables/TicketRecord.swift +++ b/Sluggo/Models/Codables/TicketRecord.swift @@ -11,13 +11,14 @@ import Foundation struct TicketRecord: Codable { var id: Int var ticket_number: Int - var taglist: [TagRecord]? + var tag_list: [TagRecord]? var object_uuid: UUID + var assigned_user: MemberRecord? var status: StatusRecord? - var assigned_user: UserRecord? var title: String var description: String? // var comments? Future Future Implementation + var due_date: Date? var created: Date var activated: Date? var deactivated: Date? diff --git a/SluggoTests/TicketTests.swift b/SluggoTests/TicketTests.swift new file mode 100644 index 0000000..7abc334 --- /dev/null +++ b/SluggoTests/TicketTests.swift @@ -0,0 +1,128 @@ +// +// TicketTests.swift +// SluggoTests +// +// Created by Andrew Gavgavian on 4/28/21. +// + +import XCTest +@testable import Sluggo + +class TicketTests: XCTestCase { + let testWorkingJson = """ + { + "id": 2, + "ticket_number": 2, + "object_uuid": "cefdf703-40ff-40d0-b07a-1583cc33d7d4", + "title": "Hi", + "description": "this is a description", + "created": "2021-04-28T20:31:43+0000", + "activated": "2021-04-28T20:31:43+0000", + } + """ + + let testWorkingJsonWithExtraProps = """ + { + "id": 2, + "ticket_number": 2, + "tag_list": [], + "object_uuid": "cefdf703-40ff-40d0-b07a-1583cc33d7d4", + "assigned_user": { + "id": "c4ca4238a0b923820dcc509a6f75849bfce3644a63f700b351c5afeb783c1cc2", + "owner": { + "id": 1, + "email": "agavgavi@ucsc.edu", + "first_name": "Andrew", + "last_name": "Gavgavian", + "username": "agavgavi" + }, + "team_id": 1, + "object_uuid": "bfe12225-d80c-415e-a7ff-1b4db7fe6f7e", + "role": "AD", + "bio": "ADMIN", + "created": "2021-04-28T20:24:17+0000", + "activated": "2021-04-28T20:24:15+0000", + "deactivated": null + }, + "status": null, + "title": "Hi", + "description": "this is a description", + "due_date": null, + "created": "2021-04-28T20:31:43+0000", + "activated": "2021-04-28T20:31:43+0000", + "deactivated": null + } + """ + + func testTicketDoesDeserialize() { + + let ticket: TicketRecord? = JsonLoader.decode(data: testWorkingJson.data(using: .utf8)!) + + XCTAssertNotNil(ticket) + + XCTAssertEqual(ticket!.ticket_number, 2) + XCTAssertEqual(ticket!.title, "Hi") + XCTAssertEqual(ticket!.id, 2) + + + XCTAssertNil(ticket!.status) + XCTAssertNil(ticket!.assigned_user) + + + } + + func testTicketDeserializesWithExtraProps() { + + + let memberJson = """ + { + "id": "c4ca4238a0b923820dcc509a6f75849bfce3644a63f700b351c5afeb783c1cc2", + "owner": { + "id": 1, + "email": "agavgavi@ucsc.edu", + "first_name": "Andrew", + "last_name": "Gavgavian", + "username": "agavgavi" + }, + "team_id": 1, + "object_uuid": "bfe12225-d80c-415e-a7ff-1b4db7fe6f7e", + "role": "AD", + "bio": "ADMIN", + "created": "2021-04-28T20:24:17+0000", + "activated": "2021-04-28T20:24:15+0000", + "deactivated": null + } + """ + let ticket: TicketRecord? = JsonLoader.decode(data: testWorkingJsonWithExtraProps.data(using: .utf8)!) + + let member: MemberRecord? = JsonLoader.decode(data: memberJson.data(using: .utf8)!) + + XCTAssertNotNil(ticket) + + XCTAssertEqual(ticket!.ticket_number, 2) + XCTAssertEqual(ticket!.title, "Hi") + XCTAssertEqual(ticket!.id, 2) + + XCTAssertEqual(ticket!.assigned_user!.id, member!.id) + XCTAssertEqual(ticket!.object_uuid, UUID(uuidString: "cefdf703-40ff-40d0-b07a-1583cc33d7d4")) + + } + + func testTicketDoesSerialize() { + + let ticket = TicketRecord(id: 1, ticket_number: 1, tag_list: nil, object_uuid: UUID(uuidString: "aa80fe7c-d346-4944-9d9e-3d7286fc4ca7")!, assigned_user: nil, status: nil, title: "Howdy Partner", description: "This is a ticket", due_date: Date(), created: Date(), activated: Date(), deactivated: Date()) + + let json = JsonLoader.encode(object: ticket) + XCTAssertNotNil(json) + print(json!) + + let ticketDuplicate: TicketRecord? = JsonLoader.decode(data: json!) + XCTAssertNotNil(ticketDuplicate) + + XCTAssertEqual(ticket.id, ticketDuplicate!.id) + XCTAssertEqual(ticket.ticket_number, ticketDuplicate!.ticket_number) + XCTAssertEqual(ticket.title, ticketDuplicate!.title) + XCTAssertEqual(ticket.description, ticketDuplicate!.description) + XCTAssertEqual(ticket.object_uuid, ticketDuplicate!.object_uuid) + } +} From 0915895c0d38629c7d234efafc9c471ff9603593 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 28 Apr 2021 18:30:49 -0700 Subject: [PATCH 066/224] isaac needs water --- Sluggo/Components/TicketTableViewCell.swift | 23 +++++++ Sluggo/Storyboards/Ticket.storyboard | 64 +++++++++++++++---- .../TicketListController.swift | 5 +- 3 files changed, 76 insertions(+), 16 deletions(-) create mode 100644 Sluggo/Components/TicketTableViewCell.swift diff --git a/Sluggo/Components/TicketTableViewCell.swift b/Sluggo/Components/TicketTableViewCell.swift new file mode 100644 index 0000000..c9b743a --- /dev/null +++ b/Sluggo/Components/TicketTableViewCell.swift @@ -0,0 +1,23 @@ +// +// TicketTableViewCell.swift +// Sluggo +// +// Created by Samuel Schmidt on 4/28/21. +// + +import UIKit + +class TicketTableViewCell: UITableViewCell { + @IBOutlet var containerView: UIView! { + didSet { + containerView.layer.cornerRadius = 5 + containerView.layer.shadowRadius = 5 + containerView.layer.shadowOpacity = 1 + containerView.layer.shadowColor = UIColor.systemGray.cgColor + containerView.layer.shadowOffset = CGSize(width: 3, height: 3) + } + } + + @IBOutlet var titleLabel: UILabel! + @IBOutlet var assignedLabel: UILabel! +} diff --git a/Sluggo/Storyboards/Ticket.storyboard b/Sluggo/Storyboards/Ticket.storyboard index 723045b..f7744b8 100644 --- a/Sluggo/Storyboards/Ticket.storyboard +++ b/Sluggo/Storyboards/Ticket.storyboard @@ -7,31 +7,67 @@ - + - + - - + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -39,11 +75,11 @@ - + - + diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index 7db5948..f8f5085 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -32,8 +32,9 @@ class TicketListController: UITableViewController { } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "Ticket", for: indexPath) - cell.textLabel?.text = tickets[indexPath.row].title + let cell = tableView.dequeueReusableCell(withIdentifier: "Ticket", for: indexPath) as! TicketTableViewCell + cell.titleLabel.text = tickets[indexPath.row].title + cell.assignedLabel.text = tickets[indexPath.row].assigned_user?.owner.email return cell } From 9a5cab1c689a1479a0feb63028aeedf7ed7f7daf Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Thu, 29 Apr 2021 14:00:20 -0700 Subject: [PATCH 067/224] tickets --- Sluggo/Components/TicketTableViewCell.swift | 9 +++- Sluggo/Storyboards/Ticket.storyboard | 52 +++++++------------ .../TicketListController.swift | 4 +- 3 files changed, 27 insertions(+), 38 deletions(-) diff --git a/Sluggo/Components/TicketTableViewCell.swift b/Sluggo/Components/TicketTableViewCell.swift index c9b743a..6d76160 100644 --- a/Sluggo/Components/TicketTableViewCell.swift +++ b/Sluggo/Components/TicketTableViewCell.swift @@ -11,13 +11,18 @@ class TicketTableViewCell: UITableViewCell { @IBOutlet var containerView: UIView! { didSet { containerView.layer.cornerRadius = 5 - containerView.layer.shadowRadius = 5 + containerView.layer.shadowColor = #colorLiteral(red: 0.2549019754, green: 0.2745098174, blue: 0.3019607961, alpha: 1) containerView.layer.shadowOpacity = 1 - containerView.layer.shadowColor = UIColor.systemGray.cgColor containerView.layer.shadowOffset = CGSize(width: 3, height: 3) + containerView.layer.shadowRadius = 5 } } + @IBOutlet var ticketStatus: UIView! { + didSet { + ticketStatus.layer.cornerRadius = 5 + } + } @IBOutlet var titleLabel: UILabel! @IBOutlet var assignedLabel: UILabel! } diff --git a/Sluggo/Storyboards/Ticket.storyboard b/Sluggo/Storyboards/Ticket.storyboard index f7744b8..df18ff0 100644 --- a/Sluggo/Storyboards/Ticket.storyboard +++ b/Sluggo/Storyboards/Ticket.storyboard @@ -3,6 +3,7 @@ + @@ -15,6 +16,7 @@ + @@ -23,50 +25,26 @@ - - + + + - - - - - - - + + + + + + - - - - - - - - - - - - - - + @@ -104,6 +82,12 @@ + + + + + + diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index f8f5085..6acd229 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -33,8 +33,8 @@ class TicketListController: UITableViewController { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Ticket", for: indexPath) as! TicketTableViewCell - cell.titleLabel.text = tickets[indexPath.row].title - cell.assignedLabel.text = tickets[indexPath.row].assigned_user?.owner.email +// cell.titleLabel.text = tickets[indexPath.row].title.capitalized +// cell.assignedLabel.text = tickets[indexPath.row].assigned_user?.owner.email return cell } From a62681eb4eb0fa556e18d9cc1d298b5b2a67fcbf Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Fri, 30 Apr 2021 01:05:22 -0700 Subject: [PATCH 068/224] initial design. pagination. reloading. infinite scrolling --- Sluggo.xcodeproj/project.pbxproj | 12 ++++ Sluggo/Components/TicketTableViewCell.swift | 18 ++++-- Sluggo/Models/AppIdentity.swift | 1 + Sluggo/Models/Managers/TicketManager.swift | 9 +-- Sluggo/Storyboards/Ticket.storyboard | 56 +++++++++++++++---- .../TicketListController.swift | 50 +++++++++++++---- 6 files changed, 117 insertions(+), 29 deletions(-) diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index cf913f0..6a61d20 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ 3A150A6726365A9A0052934B /* StatusRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A6626365A9A0052934B /* StatusRecord.swift */; }; 3A150A6C26365B670052934B /* TagRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A6B26365B670052934B /* TagRecord.swift */; }; 5A00A0142639EDEA0085C8C6 /* URLRequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */; }; + 5A00A045263A34CC0085C8C6 /* TicketTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A00A044263A34CC0085C8C6 /* TicketTableViewCell.swift */; }; 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A79FFE0262E562A00D04320 /* Main.storyboard */; }; 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A79FFE5262E563F00D04320 /* LoginViewController.swift */; }; 5A9FF88926386D1400378550 /* Ticket.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A9FF88826386D1400378550 /* Ticket.storyboard */; }; @@ -82,6 +83,7 @@ 3A150A6626365A9A0052934B /* StatusRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusRecord.swift; sourceTree = ""; }; 3A150A6B26365B670052934B /* TagRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagRecord.swift; sourceTree = ""; }; 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLRequestBuilder.swift; sourceTree = ""; }; + 5A00A044263A34CC0085C8C6 /* TicketTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketTableViewCell.swift; sourceTree = ""; }; 5A79FFE0262E562A00D04320 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 5A79FFE5262E563F00D04320 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 5A9FF88826386D1400378550 /* Ticket.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Ticket.storyboard; sourceTree = ""; }; @@ -157,6 +159,14 @@ path = Models; sourceTree = ""; }; + 5A00A043263A349F0085C8C6 /* Components */ = { + isa = PBXGroup; + children = ( + 5A00A044263A34CC0085C8C6 /* TicketTableViewCell.swift */, + ); + path = Components; + sourceTree = ""; + }; 5A79FFED262E5FEF00D04320 /* Codables */ = { isa = PBXGroup; children = ( @@ -209,6 +219,7 @@ 7D9963A4261EF3A4002338E7 /* Sluggo */ = { isa = PBXGroup; children = ( + 5A00A043263A349F0085C8C6 /* Components */, 7DC2A2202629442B0002CEE6 /* View Controllers */, 7DC2A21526293F1E0002CEE6 /* Storyboards */, 2F81E6E62627E85000B6D86D /* Models */, @@ -428,6 +439,7 @@ 5AB1C8E5262FE2E60010F85E /* TicketManager.swift in Sources */, 2F3AD7A82627C89B00B61A97 /* Member.swift in Sources */, 5AB1C8DD262FE2DD0010F85E /* TeamManager.swift in Sources */, + 5A00A045263A34CC0085C8C6 /* TicketTableViewCell.swift in Sources */, 5AB1C909262FFF500010F85E /* UserManager.swift in Sources */, 2FEF5DA9262D0E7C00434763 /* PaginatedList.swift in Sources */, ); diff --git a/Sluggo/Components/TicketTableViewCell.swift b/Sluggo/Components/TicketTableViewCell.swift index 6d76160..59bad16 100644 --- a/Sluggo/Components/TicketTableViewCell.swift +++ b/Sluggo/Components/TicketTableViewCell.swift @@ -11,10 +11,6 @@ class TicketTableViewCell: UITableViewCell { @IBOutlet var containerView: UIView! { didSet { containerView.layer.cornerRadius = 5 - containerView.layer.shadowColor = #colorLiteral(red: 0.2549019754, green: 0.2745098174, blue: 0.3019607961, alpha: 1) - containerView.layer.shadowOpacity = 1 - containerView.layer.shadowOffset = CGSize(width: 3, height: 3) - containerView.layer.shadowRadius = 5 } } @@ -25,4 +21,18 @@ class TicketTableViewCell: UITableViewCell { } @IBOutlet var titleLabel: UILabel! @IBOutlet var assignedLabel: UILabel! + + func loadFromTicketRecord(ticket: TicketRecord) { + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeStyle = .none + formatter.locale = Locale(identifier: "en_US") + + titleLabel.text = "\(ticket.ticket_number) | \(ticket.title.capitalized)" + if let date = ticket.due_date { + assignedLabel.text = formatter.string(from: date) + } else { + assignedLabel.text = "" + } + } } diff --git a/Sluggo/Models/AppIdentity.swift b/Sluggo/Models/AppIdentity.swift index 5f6e620..ce5a2b0 100644 --- a/Sluggo/Models/AppIdentity.swift +++ b/Sluggo/Models/AppIdentity.swift @@ -12,6 +12,7 @@ class AppIdentity: Codable { var team: TeamRecord? var token: String? var baseAddress: String = Constants.Config.URL_BASE + var pageSize = 10 private static var persistencePath: URL { get { diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index 56aac5e..02b7c94 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -19,12 +19,13 @@ class TicketManager { return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + TicketManager.urlBase + "\(ticketRecord.id)/")! } - private func makeListUrl() -> URL { - return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + TicketManager.urlBase)! + private func makeListUrl(page: Int) -> URL { + let urlString = identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + TicketManager.urlBase + "?page=\(page)" + return URL(string: urlString)! } - public func listTeamTickets(completionHandler: @escaping (Result, Error>) -> Void) -> Void { - let requestBuilder = URLRequestBuilder(url: makeListUrl()) + public func listTeamTickets(page: Int, completionHandler: @escaping (Result, Error>) -> Void) -> Void { + let requestBuilder = URLRequestBuilder(url: makeListUrl(page: page)) .setMethod(method: .GET) .setIdentity(identity: self.identity) diff --git a/Sluggo/Storyboards/Ticket.storyboard b/Sluggo/Storyboards/Ticket.storyboard index df18ff0..f2b5c26 100644 --- a/Sluggo/Storyboards/Ticket.storyboard +++ b/Sluggo/Storyboards/Ticket.storyboard @@ -25,26 +25,55 @@ - + - - - - - - - + + + + + + - + + + + + + + + + + + + + + + + + + + + + - + @@ -71,6 +100,9 @@ + + + @@ -81,6 +113,7 @@ + @@ -91,5 +124,8 @@ + + + diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index 6acd229..501e76e 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -9,13 +9,13 @@ import UIKit class TicketListController: UITableViewController { - var identity: AppIdentity - var tickets: [TicketRecord] + var maxNumber: Int = 0 + var tickets: [TicketRecord] = [] + var isFetching: Bool = false init? (coder: NSCoder, identity: AppIdentity) { self.identity = identity - self.tickets = [] super.init(coder: coder) } @@ -23,8 +23,28 @@ class TicketListController: UITableViewController { fatalError("must be initialized with identity") } - override func viewDidAppear(_ animated: Bool) { - loadData() + // MARK: initial actions + override func viewDidLoad() { + configureRefreshControl() + loadData(page: 1) + } + + func configureRefreshControl() { + refreshControl = UIRefreshControl() + refreshControl?.addTarget(self, action: #selector(handleRefreshAction), for: .valueChanged) + } + + @objc func handleRefreshAction() { + tickets = [] + self.loadData(page: 1) + } + + + // MARK: Table view stuff + + // @stephan this is probably where you'll spawn the detail views once you get going on that. + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + print("selected row!") } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { @@ -33,20 +53,28 @@ class TicketListController: UITableViewController { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Ticket", for: indexPath) as! TicketTableViewCell -// cell.titleLabel.text = tickets[indexPath.row].title.capitalized -// cell.assignedLabel.text = tickets[indexPath.row].assigned_user?.owner.email + cell.loadFromTicketRecord(ticket: tickets[indexPath.row]) + + if (indexPath.row == tickets.count - 1 && tickets.count < maxNumber) { + DispatchQueue.main.async { + self.loadData(page: ((indexPath.row + 1) / self.identity.pageSize) + 1) + } + } + return cell } - private func loadData() { + // MARK: API calls + private func loadData(page: Int) { let ticketManager = TicketManager(identity) - ticketManager.listTeamTickets() { result in + ticketManager.listTeamTickets(page: page) { result in switch(result) { case .success(let record): - print("Successful login and retrieved tickets") - self.tickets = record.results + self.tickets += record.results + self.maxNumber = record.count DispatchQueue.main.async { self.tableView.reloadData() + self.refreshControl?.endRefreshing() } break; case .failure(let error): From f487998c9fcb1cf6a2055e4d84254a9e34d14e33 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Fri, 30 Apr 2021 01:12:43 -0700 Subject: [PATCH 069/224] color from hex. tired. --- Sluggo.xcodeproj/project.pbxproj | 12 +++++++ Sluggo/Components/TicketTableViewCell.swift | 10 +++--- Sluggo/Extensions/UIColor.swift | 39 +++++++++++++++++++++ Sluggo/Models/Codables/StatusRecord.swift | 1 + 4 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 Sluggo/Extensions/UIColor.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 6a61d20..4b90e4d 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 3A150A6C26365B670052934B /* TagRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A6B26365B670052934B /* TagRecord.swift */; }; 5A00A0142639EDEA0085C8C6 /* URLRequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */; }; 5A00A045263A34CC0085C8C6 /* TicketTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A00A044263A34CC0085C8C6 /* TicketTableViewCell.swift */; }; + 5A673570263BF2D200C4C778 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A67356F263BF2D200C4C778 /* UIColor.swift */; }; 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A79FFE0262E562A00D04320 /* Main.storyboard */; }; 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A79FFE5262E563F00D04320 /* LoginViewController.swift */; }; 5A9FF88926386D1400378550 /* Ticket.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A9FF88826386D1400378550 /* Ticket.storyboard */; }; @@ -84,6 +85,7 @@ 3A150A6B26365B670052934B /* TagRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagRecord.swift; sourceTree = ""; }; 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLRequestBuilder.swift; sourceTree = ""; }; 5A00A044263A34CC0085C8C6 /* TicketTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketTableViewCell.swift; sourceTree = ""; }; + 5A67356F263BF2D200C4C778 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; 5A79FFE0262E562A00D04320 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 5A79FFE5262E563F00D04320 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 5A9FF88826386D1400378550 /* Ticket.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Ticket.storyboard; sourceTree = ""; }; @@ -167,6 +169,14 @@ path = Components; sourceTree = ""; }; + 5A67356E263BF2C600C4C778 /* Extensions */ = { + isa = PBXGroup; + children = ( + 5A67356F263BF2D200C4C778 /* UIColor.swift */, + ); + path = Extensions; + sourceTree = ""; + }; 5A79FFED262E5FEF00D04320 /* Codables */ = { isa = PBXGroup; children = ( @@ -219,6 +229,7 @@ 7D9963A4261EF3A4002338E7 /* Sluggo */ = { isa = PBXGroup; children = ( + 5A67356E263BF2C600C4C778 /* Extensions */, 5A00A043263A349F0085C8C6 /* Components */, 7DC2A2202629442B0002CEE6 /* View Controllers */, 7DC2A21526293F1E0002CEE6 /* Storyboards */, @@ -433,6 +444,7 @@ 5AB1C8F7262FE3520010F85E /* AppIdentity.swift in Sources */, 7DC2A22F26294F9E0002CEE6 /* HomeViewController.swift in Sources */, 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */, + 5A673570263BF2D200C4C778 /* UIColor.swift in Sources */, 3A150A6C26365B670052934B /* TagRecord.swift in Sources */, 5AB1C8EF262FE3210010F85E /* TagManager.swift in Sources */, 5A9FF88E26386D4700378550 /* TicketListController.swift in Sources */, diff --git a/Sluggo/Components/TicketTableViewCell.swift b/Sluggo/Components/TicketTableViewCell.swift index 59bad16..2332073 100644 --- a/Sluggo/Components/TicketTableViewCell.swift +++ b/Sluggo/Components/TicketTableViewCell.swift @@ -13,12 +13,6 @@ class TicketTableViewCell: UITableViewCell { containerView.layer.cornerRadius = 5 } } - - @IBOutlet var ticketStatus: UIView! { - didSet { - ticketStatus.layer.cornerRadius = 5 - } - } @IBOutlet var titleLabel: UILabel! @IBOutlet var assignedLabel: UILabel! @@ -34,5 +28,9 @@ class TicketTableViewCell: UITableViewCell { } else { assignedLabel.text = "" } + + if let color = ticket.status?.color { + containerView.backgroundColor = UIColor(hex: color) ?? containerView.backgroundColor + } } } diff --git a/Sluggo/Extensions/UIColor.swift b/Sluggo/Extensions/UIColor.swift new file mode 100644 index 0000000..37fdd74 --- /dev/null +++ b/Sluggo/Extensions/UIColor.swift @@ -0,0 +1,39 @@ +// +// UIColor.swift +// Sluggo +// +// Created by Samuel Schmidt on 4/30/21. +// + +import UIKit + + +// from: https://www.hackingwithswift.com/example-code/uicolor/how-to-convert-a-hex-color-to-a-uicolor +// I did not write this nor do I know what it does +extension UIColor { + public convenience init?(hex: String) { + let r, g, b, a: CGFloat + + if hex.hasPrefix("#") { + let start = hex.index(hex.startIndex, offsetBy: 1) + let hexColor = String(hex[start...]) + + if hexColor.count == 8 { + let scanner = Scanner(string: hexColor) + var hexNumber: UInt64 = 0 + + if scanner.scanHexInt64(&hexNumber) { + r = CGFloat((hexNumber & 0xff000000) >> 24) / 255 + g = CGFloat((hexNumber & 0x00ff0000) >> 16) / 255 + b = CGFloat((hexNumber & 0x0000ff00) >> 8) / 255 + a = CGFloat(hexNumber & 0x000000ff) / 255 + + self.init(red: r, green: g, blue: b, alpha: a) + return + } + } + } + + return nil + } +} diff --git a/Sluggo/Models/Codables/StatusRecord.swift b/Sluggo/Models/Codables/StatusRecord.swift index 36cd941..d8ae1c7 100644 --- a/Sluggo/Models/Codables/StatusRecord.swift +++ b/Sluggo/Models/Codables/StatusRecord.swift @@ -12,6 +12,7 @@ struct StatusRecord: Codable { var id: Int var object_uuid: UUID var title: String + var color: String var created: Date var activated: Date? var deactivated: Date? From f8c238fa85d1157f37bcb7d1df7f3f19b696ba44 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Fri, 30 Apr 2021 01:27:39 -0700 Subject: [PATCH 070/224] dumb hack for now --- Sluggo/Components/TicketTableViewCell.swift | 2 +- Sluggo/Extensions/UIColor.swift | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Sluggo/Components/TicketTableViewCell.swift b/Sluggo/Components/TicketTableViewCell.swift index 2332073..74cf9e6 100644 --- a/Sluggo/Components/TicketTableViewCell.swift +++ b/Sluggo/Components/TicketTableViewCell.swift @@ -30,7 +30,7 @@ class TicketTableViewCell: UITableViewCell { } if let color = ticket.status?.color { - containerView.backgroundColor = UIColor(hex: color) ?? containerView.backgroundColor + containerView.backgroundColor = UIColor(hex: color.lowercased()) ?? containerView.backgroundColor } } } diff --git a/Sluggo/Extensions/UIColor.swift b/Sluggo/Extensions/UIColor.swift index 37fdd74..90ac282 100644 --- a/Sluggo/Extensions/UIColor.swift +++ b/Sluggo/Extensions/UIColor.swift @@ -14,9 +14,12 @@ extension UIColor { public convenience init?(hex: String) { let r, g, b, a: CGFloat - if hex.hasPrefix("#") { - let start = hex.index(hex.startIndex, offsetBy: 1) - let hexColor = String(hex[start...]) + // TODO: this is hacky, backend needs to fix this + let hexNew = hex + "ff" + + if hexNew.hasPrefix("#") { + let start = hexNew.index(hexNew.startIndex, offsetBy: 1) + let hexColor = String(hexNew[start...]) if hexColor.count == 8 { let scanner = Scanner(string: hexColor) @@ -34,6 +37,7 @@ extension UIColor { } } + print("failed!") return nil } } From 24ec413b35ff87321c21e02c48dbc462ae810e47 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Fri, 30 Apr 2021 09:39:52 -0700 Subject: [PATCH 071/224] remove hack --- Sluggo/Extensions/UIColor.swift | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Sluggo/Extensions/UIColor.swift b/Sluggo/Extensions/UIColor.swift index 90ac282..0f752c8 100644 --- a/Sluggo/Extensions/UIColor.swift +++ b/Sluggo/Extensions/UIColor.swift @@ -14,12 +14,9 @@ extension UIColor { public convenience init?(hex: String) { let r, g, b, a: CGFloat - // TODO: this is hacky, backend needs to fix this - let hexNew = hex + "ff" - - if hexNew.hasPrefix("#") { - let start = hexNew.index(hexNew.startIndex, offsetBy: 1) - let hexColor = String(hexNew[start...]) + if hex.hasPrefix("#") { + let start = hex.index(hex.startIndex, offsetBy: 1) + let hexColor = String(hex[start...]) if hexColor.count == 8 { let scanner = Scanner(string: hexColor) From 43bc40e88f6f683c828e9ec5f6aef5cd723323d6 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Fri, 30 Apr 2021 10:30:01 -0700 Subject: [PATCH 072/224] assign color every reload. avoid duplicates. --- Sluggo/Components/TicketTableViewCell.swift | 4 +++- Sluggo/Extensions/UIColor.swift | 1 - Sluggo/Storyboards/Ticket.storyboard | 17 ++++++----------- .../View Controllers/TicketListController.swift | 7 +++++-- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/Sluggo/Components/TicketTableViewCell.swift b/Sluggo/Components/TicketTableViewCell.swift index 74cf9e6..cd6f6d9 100644 --- a/Sluggo/Components/TicketTableViewCell.swift +++ b/Sluggo/Components/TicketTableViewCell.swift @@ -30,7 +30,9 @@ class TicketTableViewCell: UITableViewCell { } if let color = ticket.status?.color { - containerView.backgroundColor = UIColor(hex: color.lowercased()) ?? containerView.backgroundColor + containerView.backgroundColor = UIColor(hex: color.lowercased()) + } else { + containerView.backgroundColor = #colorLiteral(red: 0.3333333433, green: 0.3333333433, blue: 0.3333333433, alpha: 1) } } } diff --git a/Sluggo/Extensions/UIColor.swift b/Sluggo/Extensions/UIColor.swift index 0f752c8..37fdd74 100644 --- a/Sluggo/Extensions/UIColor.swift +++ b/Sluggo/Extensions/UIColor.swift @@ -34,7 +34,6 @@ extension UIColor { } } - print("failed!") return nil } } diff --git a/Sluggo/Storyboards/Ticket.storyboard b/Sluggo/Storyboards/Ticket.storyboard index f2b5c26..4823137 100644 --- a/Sluggo/Storyboards/Ticket.storyboard +++ b/Sluggo/Storyboards/Ticket.storyboard @@ -3,7 +3,6 @@ - @@ -16,7 +15,6 @@ - @@ -45,7 +43,8 @@ - + + @@ -60,7 +59,8 @@ - + + @@ -69,7 +69,8 @@ - + + @@ -115,12 +116,6 @@ - - - - - - diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index 501e76e..785a8e0 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -55,7 +55,7 @@ class TicketListController: UITableViewController { let cell = tableView.dequeueReusableCell(withIdentifier: "Ticket", for: indexPath) as! TicketTableViewCell cell.loadFromTicketRecord(ticket: tickets[indexPath.row]) - if (indexPath.row == tickets.count - 1 && tickets.count < maxNumber) { + if (indexPath.row == tickets.count - 1 && tickets.count < maxNumber && !isFetching) { DispatchQueue.main.async { self.loadData(page: ((indexPath.row + 1) / self.identity.pageSize) + 1) } @@ -67,14 +67,17 @@ class TicketListController: UITableViewController { // MARK: API calls private func loadData(page: Int) { let ticketManager = TicketManager(identity) + isFetching = true ticketManager.listTeamTickets(page: page) { result in switch(result) { case .success(let record): self.tickets += record.results self.maxNumber = record.count + DispatchQueue.main.async { - self.tableView.reloadData() self.refreshControl?.endRefreshing() + self.tableView.reloadData() + self.isFetching = false } break; case .failure(let error): From b492d3a4dec2be6635f69ddf0ecbdd043c27ff5b Mon Sep 17 00:00:00 2001 From: Stephan Martin Date: Sat, 1 May 2021 20:51:36 -0700 Subject: [PATCH 073/224] Trying to figure out how to pass ticket information from TicketListController to TicketDetail --- Sluggo.xcodeproj/project.pbxproj | 8 ++ Sluggo/Models/Managers/TicketManager.swift | 16 ++- Sluggo/Storyboards/Ticket.storyboard | 8 ++ Sluggo/Storyboards/TicketDetail.storyboard | 32 ++++++ .../TicketDetailViewController.swift | 100 ++++++++++++++++++ .../TicketListController.swift | 31 +++++- 6 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 Sluggo/Storyboards/TicketDetail.storyboard create mode 100644 Sluggo/View Controllers/TicketDetailViewController.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 4b90e4d..bbe452a 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -20,6 +20,8 @@ 2FEF5DAE262D1C4900434763 /* MemberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DAD262D1C4900434763 /* MemberTests.swift */; }; 3A150A6726365A9A0052934B /* StatusRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A6626365A9A0052934B /* StatusRecord.swift */; }; 3A150A6C26365B670052934B /* TagRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A6B26365B670052934B /* TagRecord.swift */; }; + 3A150A77263CB5F70052934B /* TicketDetail.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3A150A76263CB5F70052934B /* TicketDetail.storyboard */; }; + 3A150A7F263CBA860052934B /* TicketDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A7E263CBA860052934B /* TicketDetailViewController.swift */; }; 5A00A0142639EDEA0085C8C6 /* URLRequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */; }; 5A00A045263A34CC0085C8C6 /* TicketTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A00A044263A34CC0085C8C6 /* TicketTableViewCell.swift */; }; 5A673570263BF2D200C4C778 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A67356F263BF2D200C4C778 /* UIColor.swift */; }; @@ -83,6 +85,8 @@ 2FEF5DAD262D1C4900434763 /* MemberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberTests.swift; sourceTree = ""; }; 3A150A6626365A9A0052934B /* StatusRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusRecord.swift; sourceTree = ""; }; 3A150A6B26365B670052934B /* TagRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagRecord.swift; sourceTree = ""; }; + 3A150A76263CB5F70052934B /* TicketDetail.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TicketDetail.storyboard; sourceTree = ""; }; + 3A150A7E263CBA860052934B /* TicketDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketDetailViewController.swift; sourceTree = ""; }; 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLRequestBuilder.swift; sourceTree = ""; }; 5A00A044263A34CC0085C8C6 /* TicketTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketTableViewCell.swift; sourceTree = ""; }; 5A67356F263BF2D200C4C778 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; @@ -271,6 +275,7 @@ 7DC2A21B262943A70002CEE6 /* Sidebar.storyboard */, 5A9FF88826386D1400378550 /* Ticket.storyboard */, 7DEA17842639E7B5004E5A71 /* LaunchScreen.storyboard */, + 3A150A76263CB5F70052934B /* TicketDetail.storyboard */, ); path = Storyboards; sourceTree = ""; @@ -285,6 +290,7 @@ 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */, 7DC2A22E26294F9E0002CEE6 /* HomeViewController.swift */, 5A9FF88D26386D4700378550 /* TicketListController.swift */, + 3A150A7E263CBA860052934B /* TicketDetailViewController.swift */, ); path = "View Controllers"; sourceTree = ""; @@ -397,6 +403,7 @@ 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */, 7D9963AD261EF3A4002338E7 /* Root.storyboard in Resources */, 7DEA17852639E7B5004E5A71 /* LaunchScreen.storyboard in Resources */, + 3A150A77263CB5F70052934B /* TicketDetail.storyboard in Resources */, 7DC2A21726293F360002CEE6 /* Home.storyboard in Resources */, 7DC2A21C262943A70002CEE6 /* Sidebar.storyboard in Resources */, ); @@ -425,6 +432,7 @@ files = ( 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */, 5AB1C8FC262FF1690010F85E /* TicketRecord.swift in Sources */, + 3A150A7F263CBA860052934B /* TicketDetailViewController.swift in Sources */, 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */, 5A00A0142639EDEA0085C8C6 /* URLRequestBuilder.swift in Sources */, 2FD70D5D2636144300D13C75 /* LogoutMessage.swift in Sources */, diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index 02b7c94..faab233 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -32,7 +32,21 @@ class TicketManager { JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } - public func updateTicket(_ ticket: TicketRecord, completionHandler: @escaping(Result) -> Void)-> Void { + public func makeTicket(ticket: TicketRecord, completionHandler: @escaping(Result) -> Void)->Void{ + guard let body = JsonLoader.encode(object: ticket) else { + completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize ticket JSON for makeTicket in TicketManager"))) + return + } + + let requestBuilder = URLRequestBuilder(url: makeListUrl(page: 1)) + .setMethod(method: .POST) + .setData(data: body) + .setIdentity(identity: self.identity) + + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) + } + + public func updateTicket(ticket: TicketRecord, completionHandler: @escaping(Result) -> Void)-> Void { guard let body = JsonLoader.encode(object: ticket) else { completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize ticket JSON for updateTicket in TicketManager"))) return diff --git a/Sluggo/Storyboards/Ticket.storyboard b/Sluggo/Storyboards/Ticket.storyboard index 4823137..c6f994b 100644 --- a/Sluggo/Storyboards/Ticket.storyboard +++ b/Sluggo/Storyboards/Ticket.storyboard @@ -89,6 +89,14 @@ + + + + + + + + diff --git a/Sluggo/Storyboards/TicketDetail.storyboard b/Sluggo/Storyboards/TicketDetail.storyboard new file mode 100644 index 0000000..2a1f961 --- /dev/null +++ b/Sluggo/Storyboards/TicketDetail.storyboard @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sluggo/View Controllers/TicketDetailViewController.swift b/Sluggo/View Controllers/TicketDetailViewController.swift new file mode 100644 index 0000000..52cf601 --- /dev/null +++ b/Sluggo/View Controllers/TicketDetailViewController.swift @@ -0,0 +1,100 @@ +// +// TicketDetailViewController.swift +// Sluggo +// +// Created by Stephan Martin on 4/30/21. +// + +import UIKit + +class TicketDetailViewController: UIViewController, UITextViewDelegate { + + public var identity: AppIdentity + public var ticket: TicketRecord? + let stackView = UIStackView() + var ticketDescription = UITextView() + + init? (coder: NSCoder, identity: AppIdentity) { + self.identity = identity + super.init(coder: coder) + } + + required init? (coder: NSCoder) { + fatalError("must be initialized with identity") + } + + override func viewDidLoad() { + super.viewDidLoad() + + stackView.axis = .vertical + stackView.spacing = 5 + stackView.translatesAutoresizingMaskIntoConstraints = false + let LightGrayColor = UIColor(displayP3Red: 12, green: 12, blue: 12, alpha: 1) + + view.backgroundColor = .white + let ticketTitle = UITextField() + // title.translatesAutoresizingMaskIntoConstraints = false + ticketTitle.borderStyle = .roundedRect + ticketTitle.contentHorizontalAlignment = .center + ticketTitle.contentVerticalAlignment = .top + ticketTitle.textAlignment = .center + ticketTitle.font = UIFont.systemFont(ofSize: 30, weight: .bold) + ticketTitle.borderStyle = .bezel + ticketTitle.backgroundColor = LightGrayColor + ticketTitle.adjustsFontSizeToFitWidth = true + ticketTitle.placeholder = "Title of Ticket" + + ticketDescription.delegate = self + ticketDescription.textAlignment = .center + ticketDescription.font = UIFont.systemFont(ofSize: 20) + + // This is placeholder text, make sure to account for it when accepting submissions + ticketDescription.textColor = .lightGray + ticketDescription.text = "Description of Ticket" + // Real text should be in black textColor, not lightGray. Use that to determine submissions + + + + //view.addSubview(testLabel) + + stackView.addArrangedSubview(ticketTitle) + stackView.addArrangedSubview(ticketDescription) + + stackView.backgroundColor = .white + view.addSubview(stackView) + stackView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true + stackView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true + stackView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true + stackView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true + + + //stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true + // stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true + //stackView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.7).isActive = true + //stackView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.45).isActive = true + + + + + + + } + // MARK: Placeholder text for description + // Whoever decided to not code placeholder text for UITextView deserves to be beaten + func textViewDidBeginEditing (_ textView: UITextView) { + if ticketDescription.textColor == .lightGray{ + ticketDescription.text = nil + ticketDescription.textColor = .black + } + } + + func textViewDidEndEditing (_ textView: UITextView) { + if ticketDescription.text.isEmpty || ticketDescription.text == "" { + ticketDescription.textColor = .lightGray + ticketDescription.text = "Description of Ticket" + } + } + +} + + diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index 785a8e0..a4d1d53 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -27,6 +27,7 @@ class TicketListController: UITableViewController { override func viewDidLoad() { configureRefreshControl() loadData(page: 1) + navigationItem.rightBarButtonItem = UIBarButtonItem( barButtonSystemItem: .add, target: self, action: #selector(connectPopUp)) } func configureRefreshControl() { @@ -44,9 +45,36 @@ class TicketListController: UITableViewController { // @stephan this is probably where you'll spawn the detail views once you get going on that. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - print("selected row!") +// print("selected row!") +// print(tickets[indexPath.row]) + //self.performSegue(withIdentifier: "TicketDetailSegue", sender: tickets[indexPath.row]) + let identity = self.identity + if let vc = storyboard?.instantiateViewController(identifier: "TicketDetail", creator:{ coder in + return TicketDetailViewController(coder: coder, identity: identity) + }) { + print("Just in case") + vc.ticket = tickets[indexPath.row] + vc.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext + + navigationController?.pushViewController(vc, animated: true) + } + } + + @objc func connectPopUp() { + //self.performSegue(withIdentifier: "TicketDetailSegue", sender: self) } +// @IBSegueAction func passToDetail(_ coder: NSCoder, sender: Any?) -> TicketDetailViewController? { +// print ("Sender Stuff: /n") +// print(sender) +// print("Type is") +// print(sender is UITableViewCell) +// if(sender is UITableViewCell){ +// +// } +// return TicketDetailViewController(coder: coder, identity: identity, ticket: nil) +// } + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.tickets.count } @@ -88,4 +116,5 @@ class TicketListController: UITableViewController { } } } + } From 21c06684fd831082198b6c197c073d4e90ce9061 Mon Sep 17 00:00:00 2001 From: Stephan Martin Date: Sun, 2 May 2021 23:06:43 -0700 Subject: [PATCH 074/224] Title, description, and assignedUser UI setup for create and edit ticket. AssignedUser still needs work --- Sluggo/Models/Codables/User.swift | 3 + Sluggo/Storyboards/TicketDetail.storyboard | 175 +++++++++++++++++- .../TicketDetailViewController.swift | 133 +++++++------ .../TicketListController.swift | 34 +++- 4 files changed, 282 insertions(+), 63 deletions(-) diff --git a/Sluggo/Models/Codables/User.swift b/Sluggo/Models/Codables/User.swift index 5210120..cd99305 100644 --- a/Sluggo/Models/Codables/User.swift +++ b/Sluggo/Models/Codables/User.swift @@ -13,5 +13,8 @@ struct UserRecord: Codable { var email: String var first_name: String? var last_name: String? + var full_name: String? { + "\(first_name as String?) \(last_name as String?)" + } var username: String } diff --git a/Sluggo/Storyboards/TicketDetail.storyboard b/Sluggo/Storyboards/TicketDetail.storyboard index 2a1f961..7eef2db 100644 --- a/Sluggo/Storyboards/TicketDetail.storyboard +++ b/Sluggo/Storyboards/TicketDetail.storyboard @@ -15,18 +15,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sluggo/View Controllers/TicketDetailViewController.swift b/Sluggo/View Controllers/TicketDetailViewController.swift index 52cf601..c0907ff 100644 --- a/Sluggo/View Controllers/TicketDetailViewController.swift +++ b/Sluggo/View Controllers/TicketDetailViewController.swift @@ -7,12 +7,18 @@ import UIKit +var teamMembers: [String] = ["No Assigned User", "User1", "User2", "User3", "User4"] +var currentMember: String = "No Assigned User" + class TicketDetailViewController: UIViewController, UITextViewDelegate { - public var identity: AppIdentity - public var ticket: TicketRecord? + @IBOutlet weak var ticketTitle: UITextField! + @IBOutlet weak var ticketDescription: UITextView! + @IBOutlet weak var currentAssignedUserLabel: UILabel! + + var identity: AppIdentity + var ticket: TicketRecord? let stackView = UIStackView() - var ticketDescription = UITextView() init? (coder: NSCoder, identity: AppIdentity) { self.identity = identity @@ -26,59 +32,28 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { override func viewDidLoad() { super.viewDidLoad() - stackView.axis = .vertical - stackView.spacing = 5 - stackView.translatesAutoresizingMaskIntoConstraints = false - let LightGrayColor = UIColor(displayP3Red: 12, green: 12, blue: 12, alpha: 1) - - view.backgroundColor = .white - let ticketTitle = UITextField() - // title.translatesAutoresizingMaskIntoConstraints = false - ticketTitle.borderStyle = .roundedRect - ticketTitle.contentHorizontalAlignment = .center - ticketTitle.contentVerticalAlignment = .top - ticketTitle.textAlignment = .center - ticketTitle.font = UIFont.systemFont(ofSize: 30, weight: .bold) - ticketTitle.borderStyle = .bezel - ticketTitle.backgroundColor = LightGrayColor - ticketTitle.adjustsFontSizeToFitWidth = true - ticketTitle.placeholder = "Title of Ticket" + NotificationCenter.default.addObserver(self, selector: #selector(changeLabel), name: .changeAssignedUser, object: nil) ticketDescription.delegate = self - ticketDescription.textAlignment = .center - ticketDescription.font = UIFont.systemFont(ofSize: 20) - - // This is placeholder text, make sure to account for it when accepting submissions - ticketDescription.textColor = .lightGray - ticketDescription.text = "Description of Ticket" - // Real text should be in black textColor, not lightGray. Use that to determine submissions - - - - //view.addSubview(testLabel) - - stackView.addArrangedSubview(ticketTitle) - stackView.addArrangedSubview(ticketDescription) - - stackView.backgroundColor = .white - view.addSubview(stackView) - stackView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true - stackView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true - stackView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true - stackView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true - - - //stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true - // stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true - //stackView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.7).isActive = true - //stackView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.45).isActive = true - - - - - + if(ticket != nil){ + ticketTitle.text = ticket?.title + ticketDescription.text = ticket?.description + if(ticket?.assigned_user != nil){ + currentAssignedUserLabel.text = ticket?.assigned_user?.owner.username + } + + } + else{ + ticketDescription.text = "Description of ticket" + ticketDescription.textColor = .lightGray + } } + + @objc func changeLabel(_notification: Notification){ + currentAssignedUserLabel.text = currentMember + } + // MARK: Placeholder text for description // Whoever decided to not code placeholder text for UITextView deserves to be beaten func textViewDidBeginEditing (_ textView: UITextView) { @@ -87,14 +62,66 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { ticketDescription.textColor = .black } } - + func textViewDidEndEditing (_ textView: UITextView) { if ticketDescription.text.isEmpty || ticketDescription.text == "" { ticketDescription.textColor = .lightGray ticketDescription.text = "Description of Ticket" } } +} + +//MARK: PopupController manager + +class PopupVC: UIViewController { + + @IBOutlet weak var assignedUsersPicker: UIPickerView! + @IBOutlet weak var pickerViewController: UIPickerView! + + override func viewDidLoad(){ + super.viewDidLoad() + + currentMember = "No Assigned User" // This was done because pickerView is weird, thus this needs to be forced. + + pickerViewController.dataSource = self + pickerViewController.delegate = self + } + + // Buttons in popup, submit button passes notification to trigger change in assignedUserLabel + @IBAction func dismissPopup(_ sender: Any) { + dismiss(animated: true, completion: nil) + } + @IBAction func submitButton(_ sender: Any) { + NotificationCenter.default.post(name: .changeAssignedUser, object:self) + dismiss(animated: true, completion: nil) + } +} + + +//MARK: UIPickerView manager + +extension PopupVC: UIPickerViewDelegate, UIPickerViewDataSource { + + func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + + func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return teamMembers.count + } + + func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + //print(teamMembers[row]) + //currentMember = teamMembers[row] + return teamMembers[row] + } + func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + currentMember = teamMembers[row] + } } +extension Notification.Name { + static let changeAssignedUser = Notification.Name(rawValue: "changeAssignedUserNotification") +} diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index a4d1d53..053ee7e 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -45,23 +45,39 @@ class TicketListController: UITableViewController { // @stephan this is probably where you'll spawn the detail views once you get going on that. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { -// print("selected row!") -// print(tickets[indexPath.row]) - //self.performSegue(withIdentifier: "TicketDetailSegue", sender: tickets[indexPath.row]) + let identity = self.identity - if let vc = storyboard?.instantiateViewController(identifier: "TicketDetail", creator:{ coder in + let detailStoryboard = UIStoryboard(name: "TicketDetail", bundle: nil) + if let vc = detailStoryboard.instantiateViewController(identifier: "TicketDetail", creator:{ coder in return TicketDetailViewController(coder: coder, identity: identity) - }) { - print("Just in case") + }) as TicketDetailViewController? { vc.ticket = tickets[indexPath.row] - vc.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext - navigationController?.pushViewController(vc, animated: true) + self.present(vc, animated: true, completion: nil) } + + + +// let tempStory = UIStoryboard(name: "TicketDetail", bundle: nil) +// let vc = tempStory.instantiateViewController(identifier: "TicketDetail", creator: { coder in +// return TicketDetailViewController(coder: coder, identity: self.identity) +// }) +// +// vc.ticket = tickets[indexPath.row] +// +// self.present(vc, animated: true, completion: nil) + } @objc func connectPopUp() { - //self.performSegue(withIdentifier: "TicketDetailSegue", sender: self) + let identity = self.identity + let detailStoryboard = UIStoryboard(name: "TicketDetail", bundle: nil) + if let vc = detailStoryboard.instantiateViewController(identifier: "TicketDetail", creator:{ coder in + return TicketDetailViewController(coder: coder, identity: identity) + }) as TicketDetailViewController? { + vc.ticket = nil + self.present(vc, animated: true, completion: nil) + } } // @IBSegueAction func passToDetail(_ coder: NSCoder, sender: Any?) -> TicketDetailViewController? { From acd3054074c06b2eaca80e3f62f21741ee4432ea Mon Sep 17 00:00:00 2001 From: Stephan Martin Date: Sun, 2 May 2021 23:26:05 -0700 Subject: [PATCH 075/224] I found a bug in memberManager :D --- Sluggo/Models/Managers/MemberManager.swift | 2 +- Sluggo/Storyboards/TicketDetail.storyboard | 2 +- .../TicketDetailViewController.swift | 14 +++++++++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Sluggo/Models/Managers/MemberManager.swift b/Sluggo/Models/Managers/MemberManager.swift index 556d421..dc07607 100644 --- a/Sluggo/Models/Managers/MemberManager.swift +++ b/Sluggo/Models/Managers/MemberManager.swift @@ -11,7 +11,7 @@ class MemberManager { static let urlBase = "/members/" private var identity: AppIdentity - init(_ identity: AppIdentity) { + init(identity: AppIdentity) { self.identity = identity } diff --git a/Sluggo/Storyboards/TicketDetail.storyboard b/Sluggo/Storyboards/TicketDetail.storyboard index 7eef2db..1703ced 100644 --- a/Sluggo/Storyboards/TicketDetail.storyboard +++ b/Sluggo/Storyboards/TicketDetail.storyboard @@ -127,7 +127,7 @@ - + @@ -84,25 +85,25 @@ - - + - + + - - - + + - - - - + + + - - + + - - + + + + @@ -131,13 +132,13 @@ - + + + + + + + + + + + + + + - - + + - + + + + @@ -187,10 +225,11 @@ + - + @@ -205,6 +244,7 @@ + diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index e6edb13..a298cc0 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -88,7 +88,7 @@ class LoginViewController: UIViewController { // Segue out of VC DispatchQueue.main.async { //self.performSegue(withIdentifier: "loginToRoot", sender: self) - self.dismiss(animated: true, completion: self.completion) + self.launchTeamSelect() } break; @@ -100,6 +100,18 @@ class LoginViewController: UIViewController { } } } + + private func launchTeamSelect() { + if let vc = storyboard?.instantiateViewController(identifier: "TeamSelect", creator: { coder in + return TeamTableViewController(coder: coder, identity: self.identity) { + self.dismiss(animated: true, completion: self.completion) + } + }) { + vc.isModalInPresentation = true + self.present(vc, animated: true) + } + } + @IBAction func persistLoginButton(_ sender: Any) { self.persistButton.isSelected.toggle() print(self.persistButton.isSelected) diff --git a/Sluggo/View Controllers/RootViewController.swift b/Sluggo/View Controllers/RootViewController.swift index 3a42801..ed87460 100644 --- a/Sluggo/View Controllers/RootViewController.swift +++ b/Sluggo/View Controllers/RootViewController.swift @@ -11,6 +11,16 @@ class RootViewController: UIViewController { private var identity: AppIdentity @IBOutlet weak var mainContainerView: UIView! // contains the main app and tab bar controller @IBOutlet weak var sidebarContainerView: UIView! // contains the sidebar container view controller + @IBOutlet var swipeRight: UISwipeGestureRecognizer! + @IBOutlet var swipeLeft: UISwipeGestureRecognizer! + +// lazy var menuBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "sidebar.leading")?.withRenderingMode(.alwaysOriginal), style: .done, +// target: self, action: #selector(menuBarButtonItemTapped)) +// +// @objc func menuBarButtonItemTapped() { +// print("tapped") +// } + init? (coder: NSCoder, identity: AppIdentity) { self.identity = identity @@ -37,6 +47,7 @@ class RootViewController: UIViewController { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(onSidebarNotificationRecieved), name: .onSidebarTrigger, object: nil) +// navigationItem.setLeftBarButton(menuBarButtonItem, animated: false) // Do any additional setup after loading the view. } @@ -56,4 +67,14 @@ class RootViewController: UIViewController { @IBSegueAction func createTicket(_ coder: NSCoder) -> TicketListController? { return TicketListController(coder: coder, identity: identity) } + + @IBAction func receivedGesture() { + print("swiped right") + } + @IBAction func receieveLeft() { + print("swiped left") + } + @IBSegueAction func showSidebar(_ coder: NSCoder) -> UIViewController? { + return SluggoSidebarContainerViewController(coder: coder) + } } diff --git a/Sluggo/View Controllers/TeamTableViewController.swift b/Sluggo/View Controllers/TeamTableViewController.swift index 884b248..26b2240 100644 --- a/Sluggo/View Controllers/TeamTableViewController.swift +++ b/Sluggo/View Controllers/TeamTableViewController.swift @@ -9,13 +9,50 @@ import UIKit class TeamTableViewController: UITableViewController { private var identity: AppIdentity + private var completion: (() -> Void)? + private var teams: [TeamRecord] = [] - init? (coder: NSCoder, identity: AppIdentity) { + init? (coder: NSCoder, identity: AppIdentity, completion: (() -> Void)?) { self.identity = identity + self.completion = completion super.init(coder: coder) } required init? (coder: NSCoder) { fatalError("must be called w/ identity") } + + override func viewDidLoad() { + let teamManager = TeamManager(identity: self.identity) + + teamManager.listUserTeams() { result in + switch result { + case .success(let teams): + print("success!") + DispatchQueue.main.sync { // TODO: handle pagination + self.teams = teams.results + self.tableView.reloadData() + } + break + case .failure(let error): + print(error) + } + } + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + self.identity.team = self.teams[indexPath.row] + self.dismiss(animated: true, completion: self.completion) + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return teams.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "Team", for: indexPath) as UITableViewCell + cell.textLabel?.text = self.teams[indexPath.row].name + return cell + } + } From 5d8e5bad2fcb21ee6924ab546396cfa404ba9e07 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Mon, 3 May 2021 22:43:27 -0700 Subject: [PATCH 080/224] Goodbye, button --- Sluggo/Storyboards/Home.storyboard | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/Sluggo/Storyboards/Home.storyboard b/Sluggo/Storyboards/Home.storyboard index f840da6..54fa6ad 100644 --- a/Sluggo/Storyboards/Home.storyboard +++ b/Sluggo/Storyboards/Home.storyboard @@ -1,8 +1,8 @@ - + - + @@ -15,21 +15,8 @@ - - - - - - - From d921ba7795c78897f54c1bb27218445a29d82b08 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Mon, 3 May 2021 22:58:48 -0700 Subject: [PATCH 081/224] Added mock cell to Home --- Sluggo.xcodeproj/project.pbxproj | 8 +- Sluggo/Storyboards/Home.storyboard | 111 ++++++++++++++---- .../HomeTableViewController.swift | 100 ++++++++++++++++ .../View Controllers/HomeViewController.swift | 46 -------- .../View Controllers/RootViewController.swift | 4 +- 5 files changed, 195 insertions(+), 74 deletions(-) create mode 100644 Sluggo/View Controllers/HomeTableViewController.swift delete mode 100644 Sluggo/View Controllers/HomeViewController.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 4b90e4d..d58e6f1 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -39,6 +39,7 @@ 7D1354FC262D026E00D38EE0 /* UserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1354FB262D026E00D38EE0 /* UserTests.swift */; }; 7D1DA6AE262B82C200E7C12D /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */; }; 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */; }; + 7D23834B264119050091C7E8 /* HomeTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D23834A264119050091C7E8 /* HomeTableViewController.swift */; }; 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */; }; 7D9963AD261EF3A4002338E7 /* Root.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7D9963AB261EF3A4002338E7 /* Root.storyboard */; }; 7D9963B2261EF3A5002338E7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7D9963B1261EF3A5002338E7 /* Assets.xcassets */; }; @@ -48,7 +49,6 @@ 7DC2A21C262943A70002CEE6 /* Sidebar.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DC2A21B262943A70002CEE6 /* Sidebar.storyboard */; }; 7DC2A2222629447D0002CEE6 /* SluggoSidebarTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC2A2212629447D0002CEE6 /* SluggoSidebarTableViewController.swift */; }; 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */; }; - 7DC2A22F26294F9E0002CEE6 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC2A22E26294F9E0002CEE6 /* HomeViewController.swift */; }; 7DEA17852639E7B5004E5A71 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DEA17842639E7B5004E5A71 /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ @@ -102,6 +102,7 @@ 7D1354FB262D026E00D38EE0 /* UserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTests.swift; sourceTree = ""; }; 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RootViewController.swift; path = "Sluggo/View Controllers/RootViewController.swift"; sourceTree = SOURCE_ROOT; }; 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarData.swift; sourceTree = ""; }; + 7D23834A264119050091C7E8 /* HomeTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HomeTableViewController.swift; path = "Sluggo/View Controllers/HomeTableViewController.swift"; sourceTree = SOURCE_ROOT; }; 7D9963A2261EF3A4002338E7 /* Sluggo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sluggo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7D9963AC261EF3A4002338E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Root.storyboard; sourceTree = ""; }; @@ -117,7 +118,6 @@ 7DC2A21B262943A70002CEE6 /* Sidebar.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Sidebar.storyboard; sourceTree = ""; }; 7DC2A2212629447D0002CEE6 /* SluggoSidebarTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SluggoSidebarTableViewController.swift; sourceTree = ""; }; 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SluggoSidebarContainerViewController.swift; sourceTree = ""; }; - 7DC2A22E26294F9E0002CEE6 /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; 7DEA17842639E7B5004E5A71 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXFileReference section */ @@ -283,8 +283,8 @@ 7DC2A2212629447D0002CEE6 /* SluggoSidebarTableViewController.swift */, 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */, 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */, - 7DC2A22E26294F9E0002CEE6 /* HomeViewController.swift */, 5A9FF88D26386D4700378550 /* TicketListController.swift */, + 7D23834A264119050091C7E8 /* HomeTableViewController.swift */, ); path = "View Controllers"; sourceTree = ""; @@ -442,10 +442,10 @@ 3A150A6726365A9A0052934B /* StatusRecord.swift in Sources */, 5ACC0F062630CA2700DCF83E /* Exceptions.swift in Sources */, 5AB1C8F7262FE3520010F85E /* AppIdentity.swift in Sources */, - 7DC2A22F26294F9E0002CEE6 /* HomeViewController.swift in Sources */, 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */, 5A673570263BF2D200C4C778 /* UIColor.swift in Sources */, 3A150A6C26365B670052934B /* TagRecord.swift in Sources */, + 7D23834B264119050091C7E8 /* HomeTableViewController.swift in Sources */, 5AB1C8EF262FE3210010F85E /* TagManager.swift in Sources */, 5A9FF88E26386D4700378550 /* TicketListController.swift in Sources */, 5AB1C8E5262FE2E60010F85E /* TicketManager.swift in Sources */, diff --git a/Sluggo/Storyboards/Home.storyboard b/Sluggo/Storyboards/Home.storyboard index 54fa6ad..0e4abf5 100644 --- a/Sluggo/Storyboards/Home.storyboard +++ b/Sluggo/Storyboards/Home.storyboard @@ -3,29 +3,10 @@ - - - - - - - - - - - - - - - - - - - @@ -34,8 +15,8 @@ - - + + @@ -43,18 +24,104 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift new file mode 100644 index 0000000..1881ab6 --- /dev/null +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -0,0 +1,100 @@ +// +// HomeTableViewController.swift +// Sluggo +// +// Created by Isaac Trimble-Pederson on 5/3/21. +// + +import UIKit + +class HomeTableViewController: UITableViewController { + var identity: AppIdentity! + + // Injection for identity + init? (coder: NSCoder, identity: AppIdentity) { + self.identity = identity + super.init(coder: coder) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + + override func viewDidLoad() { + super.viewDidLoad() + + // Uncomment the following line to preserve selection between presentations + // self.clearsSelectionOnViewWillAppear = false + + // Uncomment the following line to display an Edit button in the navigation bar for this view controller. + // self.navigationItem.rightBarButtonItem = self.editButtonItem + } + + // MARK: - Table view data source + + override func numberOfSections(in tableView: UITableView) -> Int { + // #warning Incomplete implementation, return the number of sections + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + // #warning Incomplete implementation, return the number of rows + return 1 + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "Ticket", for: indexPath) as! TicketTableViewCell + + let mockRecord = TicketRecord(id: 1, ticket_number: 1, tag_list: nil, object_uuid: UUID(), assigned_user: nil, status: nil, title: "Test Ticket", description: "A Sample Description", due_date: nil, created: Date(), activated: nil, deactivated: nil) + + cell.loadFromTicketRecord(ticket: mockRecord) + + return cell + } + + /* + // Override to support conditional editing of the table view. + override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { + // Return false if you do not want the specified item to be editable. + return true + } + */ + + /* + // Override to support editing the table view. + override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { + if editingStyle == .delete { + // Delete the row from the data source + tableView.deleteRows(at: [indexPath], with: .fade) + } else if editingStyle == .insert { + // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view + } + } + */ + + /* + // Override to support rearranging the table view. + override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) { + + } + */ + + /* + // Override to support conditional rearranging of the table view. + override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { + // Return false if you do not want the item to be re-orderable. + return true + } + */ + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + +} diff --git a/Sluggo/View Controllers/HomeViewController.swift b/Sluggo/View Controllers/HomeViewController.swift deleted file mode 100644 index 4b1c552..0000000 --- a/Sluggo/View Controllers/HomeViewController.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// HomeViewController.swift -// Sluggo -// -// Created by Isaac Trimble-Pederson on 4/15/21. -// - -import UIKit - -class HomeViewController: UIViewController { - - public var identity: AppIdentity - - init?(coder: NSCoder, identity: AppIdentity) { - self.identity = identity - super.init(coder: coder) - } - - required init?(coder: NSCoder) { - fatalError("identity must be passed by ctor") - } - - @IBAction func pushDaButton(_ sender: Any) { - NotificationCenter.default.post(Notification(name: .onSidebarTrigger, userInfo: [Sidebar.USER_INFO_KEY : SidebarStatus.open])) - } - - override func viewDidLoad() { - super.viewDidLoad() - print(identity.token!) - - - // Do any additional setup after loading the view. - } - - - /* - // MARK: - Navigation - - // In a storyboard-based application, you will often want to do a little preparation before navigation - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - // Get the new view controller using segue.destination. - // Pass the selected object to the new view controller. - } - */ - -} diff --git a/Sluggo/View Controllers/RootViewController.swift b/Sluggo/View Controllers/RootViewController.swift index 3a42801..5ba4f69 100644 --- a/Sluggo/View Controllers/RootViewController.swift +++ b/Sluggo/View Controllers/RootViewController.swift @@ -49,8 +49,8 @@ class RootViewController: UIViewController { // did not actually call these, wrapping each tab in a navigation controlller // in the other storybaord and *then* connecting them to these handlers seems // to have worked. - @IBSegueAction func createHome(_ coder: NSCoder) -> HomeViewController? { - return HomeViewController(coder: coder, identity: identity) + @IBSegueAction func createHome(_ coder: NSCoder) -> HomeTableViewController? { + return HomeTableViewController(coder: coder, identity: identity) } @IBSegueAction func createTicket(_ coder: NSCoder) -> TicketListController? { From ba69bb7ba02f51abe573ba4367c6f06da46287f5 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Mon, 3 May 2021 23:10:11 -0700 Subject: [PATCH 082/224] First pass at placeholder functionality for ticket cells --- .../View Controllers/HomeTableViewController.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index 1881ab6..188ecf4 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -51,6 +51,18 @@ class HomeTableViewController: UITableViewController { return cell } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if let _ = self.tableView(self.tableView, cellForRowAt: indexPath) as? TicketTableViewCell { + // Present error + let error = Exception.runtimeError(message: "Opening ticket details from Home not yet implemented!") + let errorController = UIAlertController.errorController(error: error) + self.present(errorController, animated: true) + + // Deselect row + tableView.deselectRow(at: indexPath, animated: true) + } + } /* // Override to support conditional editing of the table view. From 7d91fcf5e5b7a3d9ead09248cc90657b74a1c05d Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Mon, 3 May 2021 23:19:32 -0700 Subject: [PATCH 083/224] Improvements to error controller including handler for OK action --- Sluggo/Models/Exceptions.swift | 14 ++++++++++++-- .../View Controllers/HomeTableViewController.swift | 9 ++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Sluggo/Models/Exceptions.swift b/Sluggo/Models/Exceptions.swift index 9572e2b..cd936dc 100644 --- a/Sluggo/Models/Exceptions.swift +++ b/Sluggo/Models/Exceptions.swift @@ -17,7 +17,7 @@ enum Exception: Error { } extension UIAlertController { - static func errorController(error: Error) -> UIAlertController { + static func errorController(error: Error, handler: ((UIAlertAction) -> Void)?) -> UIAlertController { // Setup message var message: String? @@ -36,8 +36,18 @@ extension UIAlertController { } let alert = UIAlertController(title: "Error", message: message ?? "Error message not provided", preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: handler)) return alert } + + static func errorController(error: Error) -> UIAlertController { + return self.errorController(error: error, handler: nil) + } + + static func createAndPresentError(vc: UIViewController, error: Error, completion: ((UIAlertAction) -> Void)?) { + let alertController = UIAlertController.errorController(error: error, handler: completion) + + vc.present(alertController, animated: true, completion: nil) + } } diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index 188ecf4..91c4ca1 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -56,11 +56,10 @@ class HomeTableViewController: UITableViewController { if let _ = self.tableView(self.tableView, cellForRowAt: indexPath) as? TicketTableViewCell { // Present error let error = Exception.runtimeError(message: "Opening ticket details from Home not yet implemented!") - let errorController = UIAlertController.errorController(error: error) - self.present(errorController, animated: true) - - // Deselect row - tableView.deselectRow(at: indexPath, animated: true) + UIAlertController.createAndPresentError(vc: self, error: error) { action in + // Deselect row once alert acknowledged + tableView.deselectRow(at: indexPath, animated: true) + } } } From a045c5f73d60bbf2b95aa147329f0422efe04224 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Tue, 4 May 2021 00:55:21 -0700 Subject: [PATCH 084/224] selection of differnt teams now complete --- Sluggo/Components/TeamTableViewCell.swift | 24 +++++ Sluggo/Models/Codables/Team.swift | 12 ++- Sluggo/Models/Managers/TeamManager.swift | 4 +- Sluggo/Storyboards/Main.storyboard | 98 ++++++++++++------- Sluggo/Storyboards/Sidebar.storyboard | 8 +- .../LaunchViewController.swift | 2 +- .../LoginViewController.swift | 8 +- .../View Controllers/RootViewController.swift | 11 +-- ...SluggoSidebarContainerViewController.swift | 21 +++- .../SluggoSidebarTableViewController.swift | 87 ---------------- .../TeamSelectorContainerViewController.swift | 48 +++++++++ .../TeamTableViewContainer.swift | 48 +++++++++ .../TeamTableViewController.swift | 81 ++++++++++----- 13 files changed, 281 insertions(+), 171 deletions(-) create mode 100644 Sluggo/Components/TeamTableViewCell.swift delete mode 100644 Sluggo/View Controllers/SluggoSidebarTableViewController.swift create mode 100644 Sluggo/View Controllers/TeamSelectorContainerViewController.swift create mode 100644 Sluggo/View Controllers/TeamTableViewContainer.swift diff --git a/Sluggo/Components/TeamTableViewCell.swift b/Sluggo/Components/TeamTableViewCell.swift new file mode 100644 index 0000000..8d68e39 --- /dev/null +++ b/Sluggo/Components/TeamTableViewCell.swift @@ -0,0 +1,24 @@ +// +// TeamTableViewCell.swift +// Sluggo +// +// Created by Samuel Schmidt on 5/4/21. +// + +import UIKit + +class TeamTableViewCell: UITableViewCell { + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + // Configure the view for the selected state + self.accessoryType = (selected) ? .checkmark : .none + } + +} diff --git a/Sluggo/Models/Codables/Team.swift b/Sluggo/Models/Codables/Team.swift index d948013..aa2f1b2 100644 --- a/Sluggo/Models/Codables/Team.swift +++ b/Sluggo/Models/Codables/Team.swift @@ -8,7 +8,7 @@ import Foundation -struct TeamRecord: Codable { +struct TeamRecord: Codable, Equatable { var id: Int var name: String var object_uuid: UUID @@ -16,5 +16,13 @@ struct TeamRecord: Codable { var created: Date var activated: Date? var deactivated: Date? - + + static func == (lhs: TeamRecord, rhs: TeamRecord) -> Bool { + return lhs.id == rhs.id && + lhs.object_uuid == rhs.object_uuid && + lhs.ticket_head == rhs.ticket_head && + lhs.created == rhs.created && + lhs.activated == rhs.activated && + lhs.deactivated == rhs.deactivated + } } diff --git a/Sluggo/Models/Managers/TeamManager.swift b/Sluggo/Models/Managers/TeamManager.swift index 83a6db5..bc01042 100644 --- a/Sluggo/Models/Managers/TeamManager.swift +++ b/Sluggo/Models/Managers/TeamManager.swift @@ -15,9 +15,9 @@ class TeamManager { self.identity = identity } - public func listUserTeams(completionHandler: @escaping(Result, Error>) -> Void) -> Void { + public func listUserTeams(page: Int, completionHandler: @escaping(Result, Error>) -> Void) -> Void { - let requestBuilder = URLRequestBuilder(url: URL(string: identity.baseAddress + TeamManager.urlBase)!) + let requestBuilder = URLRequestBuilder(url: URL(string: identity.baseAddress + TeamManager.urlBase + "?page=\(page)")!) .setIdentity(identity: identity) .setMethod(method: .GET) diff --git a/Sluggo/Storyboards/Main.storyboard b/Sluggo/Storyboards/Main.storyboard index 02365e3..fc08f2a 100644 --- a/Sluggo/Storyboards/Main.storyboard +++ b/Sluggo/Storyboards/Main.storyboard @@ -170,40 +170,12 @@ - + - - - - - - - - - - - - - - - - - - - - + + @@ -229,7 +201,67 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -244,7 +276,7 @@ - + diff --git a/Sluggo/Storyboards/Sidebar.storyboard b/Sluggo/Storyboards/Sidebar.storyboard index 41eec11..701c00e 100644 --- a/Sluggo/Storyboards/Sidebar.storyboard +++ b/Sluggo/Storyboards/Sidebar.storyboard @@ -11,16 +11,16 @@ - + - + - + @@ -111,7 +111,7 @@ - + diff --git a/Sluggo/View Controllers/LaunchViewController.swift b/Sluggo/View Controllers/LaunchViewController.swift index 78bac15..da3da34 100644 --- a/Sluggo/View Controllers/LaunchViewController.swift +++ b/Sluggo/View Controllers/LaunchViewController.swift @@ -33,7 +33,7 @@ class LaunchViewController: UIViewController { // Call login function from remembered. If failed go to login userManager.getUser() { result in switch result { - case .success(let record): + case .success( _): DispatchQueue.main.sync { self.performSegue(withIdentifier: "automaticLogin", sender: self) } diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index a298cc0..7cd71d7 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -102,8 +102,8 @@ class LoginViewController: UIViewController { } private func launchTeamSelect() { - if let vc = storyboard?.instantiateViewController(identifier: "TeamSelect", creator: { coder in - return TeamTableViewController(coder: coder, identity: self.identity) { + if let vc = storyboard?.instantiateViewController(identifier: "TableViewContainer", creator: { coder in + return TeamSelectorContainerViewController(coder: coder, identity: self.identity) { self.dismiss(animated: true, completion: self.completion) } }) { @@ -116,9 +116,5 @@ class LoginViewController: UIViewController { self.persistButton.isSelected.toggle() print(self.persistButton.isSelected) } - - @IBSegueAction func createRootViewController(_ coder: NSCoder) -> UIViewController? { - return RootViewController(coder: coder, identity: identity) - } } diff --git a/Sluggo/View Controllers/RootViewController.swift b/Sluggo/View Controllers/RootViewController.swift index ed87460..c5fcb44 100644 --- a/Sluggo/View Controllers/RootViewController.swift +++ b/Sluggo/View Controllers/RootViewController.swift @@ -14,14 +14,6 @@ class RootViewController: UIViewController { @IBOutlet var swipeRight: UISwipeGestureRecognizer! @IBOutlet var swipeLeft: UISwipeGestureRecognizer! -// lazy var menuBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "sidebar.leading")?.withRenderingMode(.alwaysOriginal), style: .done, -// target: self, action: #selector(menuBarButtonItemTapped)) -// -// @objc func menuBarButtonItemTapped() { -// print("tapped") -// } - - init? (coder: NSCoder, identity: AppIdentity) { self.identity = identity super.init(coder: coder) @@ -47,7 +39,6 @@ class RootViewController: UIViewController { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(onSidebarNotificationRecieved), name: .onSidebarTrigger, object: nil) -// navigationItem.setLeftBarButton(menuBarButtonItem, animated: false) // Do any additional setup after loading the view. } @@ -75,6 +66,6 @@ class RootViewController: UIViewController { print("swiped left") } @IBSegueAction func showSidebar(_ coder: NSCoder) -> UIViewController? { - return SluggoSidebarContainerViewController(coder: coder) + return SluggoSidebarContainerViewController(coder: coder, identity: self.identity) } } diff --git a/Sluggo/View Controllers/SluggoSidebarContainerViewController.swift b/Sluggo/View Controllers/SluggoSidebarContainerViewController.swift index f5e1179..0281162 100644 --- a/Sluggo/View Controllers/SluggoSidebarContainerViewController.swift +++ b/Sluggo/View Controllers/SluggoSidebarContainerViewController.swift @@ -14,6 +14,17 @@ class SluggoSidebarContainerViewController: UIViewController { @IBOutlet weak var sidebarWidthConstraint: NSLayoutConstraint! @IBOutlet weak var sidebarContainerLeadingConstraint: NSLayoutConstraint! + private var identity: AppIdentity + + init? (coder: NSCoder, identity: AppIdentity) { + self.identity = identity + super.init(coder: coder) + } + + required init? (coder: NSCoder) { + fatalError("must include identity") + } + // MARK: Properties var sidebarPresenting: SidebarStatus = .closed @@ -87,7 +98,15 @@ class SluggoSidebarContainerViewController: UIViewController { // Pass the selected object to the new view controller. } */ - + @IBSegueAction func launchSidebar(_ coder: NSCoder) -> UITableViewController? { + let vc = TeamTableViewController(coder: coder) + vc?.identity = self.identity + vc?.completion = { team in + self.identity.team = team + } + return vc + } + } extension Notification.Name { diff --git a/Sluggo/View Controllers/SluggoSidebarTableViewController.swift b/Sluggo/View Controllers/SluggoSidebarTableViewController.swift deleted file mode 100644 index 689c92a..0000000 --- a/Sluggo/View Controllers/SluggoSidebarTableViewController.swift +++ /dev/null @@ -1,87 +0,0 @@ -// -// SluggoSidebarTableViewController.swift -// Sluggo -// -// Created by Isaac Trimble-Pederson on 4/15/21. -// - -import UIKit - -class SluggoSidebarTableViewController: UITableViewController { - - override func viewDidLoad() { - super.viewDidLoad() - - // Uncomment the following line to preserve selection between presentations - // self.clearsSelectionOnViewWillAppear = false - - // Uncomment the following line to display an Edit button in the navigation bar for this view controller. - // self.navigationItem.rightBarButtonItem = self.editButtonItem - } - - // MARK: - Table view data source - - override func numberOfSections(in tableView: UITableView) -> Int { - // #warning Incomplete implementation, return the number of sections - return 1 - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - // #warning Incomplete implementation, return the number of rows - return 1 - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "SLGSidebarCell", for: indexPath) - - cell.textLabel?.text = "Sluggo Team!" - - return cell - } - - /* - // Override to support conditional editing of the table view. - override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { - // Return false if you do not want the specified item to be editable. - return true - } - */ - - /* - // Override to support editing the table view. - override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { - if editingStyle == .delete { - // Delete the row from the data source - tableView.deleteRows(at: [indexPath], with: .fade) - } else if editingStyle == .insert { - // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view - } - } - */ - - /* - // Override to support rearranging the table view. - override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) { - - } - */ - - /* - // Override to support conditional rearranging of the table view. - override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { - // Return false if you do not want the item to be re-orderable. - return true - } - */ - - /* - // MARK: - Navigation - - // In a storyboard-based application, you will often want to do a little preparation before navigation - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - // Get the new view controller using segue.destination. - // Pass the selected object to the new view controller. - } - */ - -} diff --git a/Sluggo/View Controllers/TeamSelectorContainerViewController.swift b/Sluggo/View Controllers/TeamSelectorContainerViewController.swift new file mode 100644 index 0000000..15386d2 --- /dev/null +++ b/Sluggo/View Controllers/TeamSelectorContainerViewController.swift @@ -0,0 +1,48 @@ +// +// TeamTableViewContainer.swift +// Sluggo +// +// Created by Samuel Schmidt on 5/3/21. +// + +import UIKit + +class TeamSelectorContainerViewController: UIViewController { + private var identity: AppIdentity + private var completion: (() -> Void)? + + init? (coder: NSCoder, identity: AppIdentity, completion: (() -> Void)?) { + self.identity = identity + self.completion = completion + super.init(coder: coder) + } + + required init? (coder: NSCoder) { + fatalError("must init with identity") + } + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + } + + + + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + if let vc = segue.destination as? TeamTableViewController { + vc.identity = self.identity + vc.completion = { team in + self.identity.team = team + self.dismiss(animated: true, completion: self.completion) + } + } + } + + +} diff --git a/Sluggo/View Controllers/TeamTableViewContainer.swift b/Sluggo/View Controllers/TeamTableViewContainer.swift new file mode 100644 index 0000000..15386d2 --- /dev/null +++ b/Sluggo/View Controllers/TeamTableViewContainer.swift @@ -0,0 +1,48 @@ +// +// TeamTableViewContainer.swift +// Sluggo +// +// Created by Samuel Schmidt on 5/3/21. +// + +import UIKit + +class TeamSelectorContainerViewController: UIViewController { + private var identity: AppIdentity + private var completion: (() -> Void)? + + init? (coder: NSCoder, identity: AppIdentity, completion: (() -> Void)?) { + self.identity = identity + self.completion = completion + super.init(coder: coder) + } + + required init? (coder: NSCoder) { + fatalError("must init with identity") + } + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + } + + + + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + if let vc = segue.destination as? TeamTableViewController { + vc.identity = self.identity + vc.completion = { team in + self.identity.team = team + self.dismiss(animated: true, completion: self.completion) + } + } + } + + +} diff --git a/Sluggo/View Controllers/TeamTableViewController.swift b/Sluggo/View Controllers/TeamTableViewController.swift index 26b2240..ca09478 100644 --- a/Sluggo/View Controllers/TeamTableViewController.swift +++ b/Sluggo/View Controllers/TeamTableViewController.swift @@ -8,41 +8,40 @@ import UIKit class TeamTableViewController: UITableViewController { - private var identity: AppIdentity - private var completion: (() -> Void)? + var identity: AppIdentity! + var completion: ((TeamRecord) -> Void)? + private var isFetching: Bool = false + private var maxNumber: Int = 0 private var teams: [TeamRecord] = [] - init? (coder: NSCoder, identity: AppIdentity, completion: (() -> Void)?) { - self.identity = identity - self.completion = completion - super.init(coder: coder) + override func viewDidLoad() { + self.configureRefreshControl() + self.loadData(page: 1) } - required init? (coder: NSCoder) { - fatalError("must be called w/ identity") + func configureRefreshControl() { + refreshControl = UIRefreshControl() + refreshControl?.addTarget(self, action: #selector(handleRefreshAction), for: .valueChanged) } - override func viewDidLoad() { - let teamManager = TeamManager(identity: self.identity) - - teamManager.listUserTeams() { result in - switch result { - case .success(let teams): - print("success!") - DispatchQueue.main.sync { // TODO: handle pagination - self.teams = teams.results - self.tableView.reloadData() - } + @objc func handleRefreshAction() { + teams = [] + self.loadData(page: 1) + } + + private func preselectRow() { + for i in 0...teams.count-1 { + let team = teams[i] + let indexPath = IndexPath(row: i, section: 0) + if team == self.identity.team { + tableView.selectRow(at: indexPath, animated: true, scrollPosition: .top) break - case .failure(let error): - print(error) } } } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - self.identity.team = self.teams[indexPath.row] - self.dismiss(animated: true, completion: self.completion) + self.completion?(self.teams[indexPath.row]) } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { @@ -50,9 +49,41 @@ class TeamTableViewController: UITableViewController { } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "Team", for: indexPath) as UITableViewCell - cell.textLabel?.text = self.teams[indexPath.row].name + let cell = tableView.dequeueReusableCell(withIdentifier: "SLGSidebarCell", for: indexPath) as UITableViewCell + let team = self.teams[indexPath.row] + cell.textLabel?.text = team.name + cell.accessoryType = (team == self.identity.team) ? .checkmark : .none + return cell } + // MARK: API calls + private func loadData(page: Int) { + let teamManager = TeamManager(identity: identity) + teamManager.listUserTeams(page: page) { result in + switch(result) { + case .success(let record): + self.teams += record.results + self.maxNumber = record.count + + DispatchQueue.main.async { + if (self.teams.count >= self.maxNumber) { + self.refreshControl?.endRefreshing() + self.tableView.reloadData() + self.isFetching = false + self.preselectRow() + } else { + self.loadData(page: page + 1) + } + } + break; + case .failure(let error): + DispatchQueue.main.async { + let alert = UIAlertController.errorController(error: error) + self.present(alert, animated: true, completion: nil) + } + } + } + } + } From d9b2baf6d6d3b043bdbaff516dfa61b6982232ad Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Tue, 4 May 2021 00:55:43 -0700 Subject: [PATCH 085/224] slightly more convenient persistence --- Sluggo/Models/AppIdentity.swift | 64 ++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/Sluggo/Models/AppIdentity.swift b/Sluggo/Models/AppIdentity.swift index ce5a2b0..ee50157 100644 --- a/Sluggo/Models/AppIdentity.swift +++ b/Sluggo/Models/AppIdentity.swift @@ -8,11 +8,59 @@ import Foundation class AppIdentity: Codable { - var authenticatedUser: UserRecord? - var team: TeamRecord? - var token: String? - var baseAddress: String = Constants.Config.URL_BASE - var pageSize = 10 + + private var _authenticatedUser: UserRecord? + private var _team: TeamRecord? + private var _token: String? + private var _pageSize = 10 + private var _baseAddress: String = Constants.Config.URL_BASE + + // MARK: Computed Properties + var authenticatedUser: UserRecord? { + set(newUser) { + _authenticatedUser = newUser + enqueueSave() + } + get { + return _authenticatedUser + } + } + var team: TeamRecord? { + set(newTeam) { + _team = newTeam + enqueueSave() + } + get { + return _team + } + } + var token: String? { + set (newToken) { + _token = newToken + enqueueSave() + } + get { + return _token + } + } + var pageSize: Int { + set(newPageSize) { + _pageSize = newPageSize + enqueueSave() + } + get { + return _pageSize + } + } + var baseAddress: String { + set(newBaseAddress) { + _baseAddress = newBaseAddress + enqueueSave() + } + get { + return _baseAddress + } + } private static var persistencePath: URL { get { @@ -22,6 +70,12 @@ class AppIdentity: Codable { } } + private func enqueueSave() { + DispatchQueue.global().async { + let _ = self.saveToDisk() + } + } + static func loadFromDisk() -> AppIdentity? { guard let persistenceFileContents = try? String(contentsOf: persistencePath) else { return nil From cbd82b7399a1f57b4c000d4d21bdb40900df6001 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Tue, 4 May 2021 01:06:29 -0700 Subject: [PATCH 086/224] Revert "slightly more convenient persistence" This reverts commit d9b2baf6d6d3b043bdbaff516dfa61b6982232ad. --- Sluggo/Models/AppIdentity.swift | 64 +++------------------------------ 1 file changed, 5 insertions(+), 59 deletions(-) diff --git a/Sluggo/Models/AppIdentity.swift b/Sluggo/Models/AppIdentity.swift index ee50157..ce5a2b0 100644 --- a/Sluggo/Models/AppIdentity.swift +++ b/Sluggo/Models/AppIdentity.swift @@ -8,59 +8,11 @@ import Foundation class AppIdentity: Codable { - - private var _authenticatedUser: UserRecord? - private var _team: TeamRecord? - private var _token: String? - private var _pageSize = 10 - private var _baseAddress: String = Constants.Config.URL_BASE - - // MARK: Computed Properties - var authenticatedUser: UserRecord? { - set(newUser) { - _authenticatedUser = newUser - enqueueSave() - } - get { - return _authenticatedUser - } - } - var team: TeamRecord? { - set(newTeam) { - _team = newTeam - enqueueSave() - } - get { - return _team - } - } - var token: String? { - set (newToken) { - _token = newToken - enqueueSave() - } - get { - return _token - } - } - var pageSize: Int { - set(newPageSize) { - _pageSize = newPageSize - enqueueSave() - } - get { - return _pageSize - } - } - var baseAddress: String { - set(newBaseAddress) { - _baseAddress = newBaseAddress - enqueueSave() - } - get { - return _baseAddress - } - } + var authenticatedUser: UserRecord? + var team: TeamRecord? + var token: String? + var baseAddress: String = Constants.Config.URL_BASE + var pageSize = 10 private static var persistencePath: URL { get { @@ -70,12 +22,6 @@ class AppIdentity: Codable { } } - private func enqueueSave() { - DispatchQueue.global().async { - let _ = self.saveToDisk() - } - } - static func loadFromDisk() -> AppIdentity? { guard let persistenceFileContents = try? String(contentsOf: persistencePath) else { return nil From 26b169f574169524a893ccb14fcfbd1c67154880 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Tue, 4 May 2021 01:19:34 -0700 Subject: [PATCH 087/224] wrap team list in nav controller --- Sluggo/Storyboards/Main.storyboard | 83 +++++++------------ .../TeamSelectorContainerViewController.swift | 31 ++++--- 2 files changed, 49 insertions(+), 65 deletions(-) diff --git a/Sluggo/Storyboards/Main.storyboard b/Sluggo/Storyboards/Main.storyboard index fc08f2a..b330a68 100644 --- a/Sluggo/Storyboards/Main.storyboard +++ b/Sluggo/Storyboards/Main.storyboard @@ -3,7 +3,6 @@ - @@ -165,12 +164,12 @@ - + - + @@ -197,67 +196,24 @@ + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + @@ -273,10 +229,27 @@ + + + + + + + + + + + + + + + + + + - diff --git a/Sluggo/View Controllers/TeamSelectorContainerViewController.swift b/Sluggo/View Controllers/TeamSelectorContainerViewController.swift index 15386d2..116a0bb 100644 --- a/Sluggo/View Controllers/TeamSelectorContainerViewController.swift +++ b/Sluggo/View Controllers/TeamSelectorContainerViewController.swift @@ -31,17 +31,28 @@ class TeamSelectorContainerViewController: UIViewController { // MARK: - Navigation - // In a storyboard-based application, you will often want to do a little preparation before navigation - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - // Get the new view controller using segue.destination. - // Pass the selected object to the new view controller. - if let vc = segue.destination as? TeamTableViewController { - vc.identity = self.identity - vc.completion = { team in - self.identity.team = team - self.dismiss(animated: true, completion: self.completion) - } +// // In a storyboard-based application, you will often want to do a little preparation before navigation +// override func prepare(for segue: UIStoryboardSegue, sender: Any?) { +// // Get the new view controller using segue.destination. +// // Pass the selected object to the new view controller. +// if let vc = segue.destination as? TeamTableViewController { +// vc.identity = self.identity +// vc.completion = { team in +// self.identity.team = team +// self.dismiss(animated: true, completion: self.completion) +// } +// } +// } + + @IBSegueAction func launchTeamSelect(_ coder: NSCoder) -> TeamTableViewController? { + let vc = TeamTableViewController(coder: coder) + vc?.identity = self.identity + vc?.completion = { team in + self.identity.team = team + self.dismiss(animated: true, completion: self.completion) } + + return vc } From 58a5814e62810f8f63724a85e2487588b29737ce Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Tue, 4 May 2021 01:20:17 -0700 Subject: [PATCH 088/224] xcodeproj --- Sluggo.xcodeproj/project.pbxproj | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 45468a4..f4bd048 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A79FFE0262E562A00D04320 /* Main.storyboard */; }; 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A79FFE5262E563F00D04320 /* LoginViewController.swift */; }; 5A9C4C1C2640BC4E00B270AB /* TeamTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9C4C1B2640BC4E00B270AB /* TeamTableViewController.swift */; }; + 5A9C4C212641113D00B270AB /* TeamSelectorContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9C4C202641113D00B270AB /* TeamSelectorContainerViewController.swift */; }; 5A9FF88926386D1400378550 /* Ticket.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A9FF88826386D1400378550 /* Ticket.storyboard */; }; 5A9FF88E26386D4700378550 /* TicketListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9FF88D26386D4700378550 /* TicketListController.swift */; }; 5AB1C8DD262FE2DD0010F85E /* TeamManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8DC262FE2DD0010F85E /* TeamManager.swift */; }; @@ -35,6 +36,7 @@ 5AB1C8F7262FE3520010F85E /* AppIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8F6262FE3520010F85E /* AppIdentity.swift */; }; 5AB1C8FC262FF1690010F85E /* TicketRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8FB262FF1690010F85E /* TicketRecord.swift */; }; 5AB1C909262FFF500010F85E /* UserManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C908262FFF500010F85E /* UserManager.swift */; }; + 5AC6459026412F0C00F46137 /* TeamTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AC6458F26412F0C00F46137 /* TeamTableViewCell.swift */; }; 5ACC0F012630CA1400DCF83E /* ErrorMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ACC0F002630CA1400DCF83E /* ErrorMessage.swift */; }; 5ACC0F062630CA2700DCF83E /* Exceptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ACC0F052630CA2700DCF83E /* Exceptions.swift */; }; 7D1354FC262D026E00D38EE0 /* UserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1354FB262D026E00D38EE0 /* UserTests.swift */; }; @@ -47,7 +49,6 @@ 7D9963CB261EF3A5002338E7 /* SluggoUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963CA261EF3A5002338E7 /* SluggoUITests.swift */; }; 7DC2A21726293F360002CEE6 /* Home.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DC2A21626293F360002CEE6 /* Home.storyboard */; }; 7DC2A21C262943A70002CEE6 /* Sidebar.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DC2A21B262943A70002CEE6 /* Sidebar.storyboard */; }; - 7DC2A2222629447D0002CEE6 /* SluggoSidebarTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC2A2212629447D0002CEE6 /* SluggoSidebarTableViewController.swift */; }; 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */; }; 7DC2A22F26294F9E0002CEE6 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC2A22E26294F9E0002CEE6 /* HomeViewController.swift */; }; 7DEA17852639E7B5004E5A71 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DEA17842639E7B5004E5A71 /* LaunchScreen.storyboard */; }; @@ -90,6 +91,7 @@ 5A79FFE0262E562A00D04320 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 5A79FFE5262E563F00D04320 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 5A9C4C1B2640BC4E00B270AB /* TeamTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamTableViewController.swift; sourceTree = ""; }; + 5A9C4C202641113D00B270AB /* TeamSelectorContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamSelectorContainerViewController.swift; sourceTree = ""; }; 5A9FF88826386D1400378550 /* Ticket.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Ticket.storyboard; sourceTree = ""; }; 5A9FF88D26386D4700378550 /* TicketListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketListController.swift; sourceTree = ""; }; 5AB1C8DC262FE2DD0010F85E /* TeamManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamManager.swift; sourceTree = ""; }; @@ -99,6 +101,7 @@ 5AB1C8F6262FE3520010F85E /* AppIdentity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIdentity.swift; sourceTree = ""; }; 5AB1C8FB262FF1690010F85E /* TicketRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketRecord.swift; sourceTree = ""; }; 5AB1C908262FFF500010F85E /* UserManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserManager.swift; sourceTree = ""; }; + 5AC6458F26412F0C00F46137 /* TeamTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamTableViewCell.swift; sourceTree = ""; }; 5ACC0F002630CA1400DCF83E /* ErrorMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMessage.swift; sourceTree = ""; }; 5ACC0F052630CA2700DCF83E /* Exceptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Exceptions.swift; sourceTree = ""; }; 7D1354FB262D026E00D38EE0 /* UserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTests.swift; sourceTree = ""; }; @@ -117,7 +120,6 @@ 7D9963CC261EF3A5002338E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7DC2A21626293F360002CEE6 /* Home.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Home.storyboard; sourceTree = ""; }; 7DC2A21B262943A70002CEE6 /* Sidebar.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Sidebar.storyboard; sourceTree = ""; }; - 7DC2A2212629447D0002CEE6 /* SluggoSidebarTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SluggoSidebarTableViewController.swift; sourceTree = ""; }; 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SluggoSidebarContainerViewController.swift; sourceTree = ""; }; 7DC2A22E26294F9E0002CEE6 /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; 7DEA17842639E7B5004E5A71 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; @@ -167,6 +169,7 @@ isa = PBXGroup; children = ( 5A00A044263A34CC0085C8C6 /* TicketTableViewCell.swift */, + 5AC6458F26412F0C00F46137 /* TeamTableViewCell.swift */, ); path = Components; sourceTree = ""; @@ -282,12 +285,12 @@ children = ( 0522586F2636100B00B49CE4 /* LaunchViewController.swift */, 5A79FFE5262E563F00D04320 /* LoginViewController.swift */, - 7DC2A2212629447D0002CEE6 /* SluggoSidebarTableViewController.swift */, 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */, 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */, 7DC2A22E26294F9E0002CEE6 /* HomeViewController.swift */, 5A9FF88D26386D4700378550 /* TicketListController.swift */, 5A9C4C1B2640BC4E00B270AB /* TeamTableViewController.swift */, + 5A9C4C202641113D00B270AB /* TeamSelectorContainerViewController.swift */, ); path = "View Controllers"; sourceTree = ""; @@ -426,6 +429,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5A9C4C212641113D00B270AB /* TeamSelectorContainerViewController.swift in Sources */, 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */, 5AB1C8FC262FF1690010F85E /* TicketRecord.swift in Sources */, 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */, @@ -435,6 +439,7 @@ 5A9C4C1C2640BC4E00B270AB /* TeamTableViewController.swift in Sources */, 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */, 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */, + 5AC6459026412F0C00F46137 /* TeamTableViewCell.swift in Sources */, 051AAB0B2630FD6500BCB9C7 /* Constants.swift in Sources */, 2F3F66E2262DD3BA0040404C /* JsonLoader.swift in Sources */, 2F2247AC262920AC006C6EE6 /* Token.swift in Sources */, @@ -442,7 +447,6 @@ 5ACC0F012630CA1400DCF83E /* ErrorMessage.swift in Sources */, 2F3AD7A32627C86A00B61A97 /* User.swift in Sources */, 052258702636100B00B49CE4 /* LaunchViewController.swift in Sources */, - 7DC2A2222629447D0002CEE6 /* SluggoSidebarTableViewController.swift in Sources */, 3A150A6726365A9A0052934B /* StatusRecord.swift in Sources */, 5ACC0F062630CA2700DCF83E /* Exceptions.swift in Sources */, 5AB1C8F7262FE3520010F85E /* AppIdentity.swift in Sources */, From 54b40d7df37741dc4052f1af62143c0331241515 Mon Sep 17 00:00:00 2001 From: Stephan Martin Date: Tue, 4 May 2021 16:54:57 -0700 Subject: [PATCH 089/224] This is for you Andrew again again --- Sluggo/Models/Managers/MemberManager.swift | 9 --- Sluggo/Storyboards/TicketDetail.storyboard | 32 +++++++-- .../TicketDetailViewController.swift | 68 ++++++++++++++----- 3 files changed, 77 insertions(+), 32 deletions(-) diff --git a/Sluggo/Models/Managers/MemberManager.swift b/Sluggo/Models/Managers/MemberManager.swift index dc07607..13eb0ac 100644 --- a/Sluggo/Models/Managers/MemberManager.swift +++ b/Sluggo/Models/Managers/MemberManager.swift @@ -23,15 +23,6 @@ class MemberManager { return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + MemberManager.urlBase)! } - public func fetchTeamMembers(completionHandler: @escaping(Result) -> Void) -> Void { - - let requestBuilder = URLRequestBuilder(url: makeListUrl()) - .setMethod(method: .GET) - .setIdentity(identity: identity) - - JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) - } - public func updateMemberRecord(_ memberRecord: MemberRecord, completionHandler: @escaping(Result) -> Void) -> Void { guard let body = JsonLoader.encode(object: memberRecord) else { diff --git a/Sluggo/Storyboards/TicketDetail.storyboard b/Sluggo/Storyboards/TicketDetail.storyboard index 4b42671..a5c075e 100644 --- a/Sluggo/Storyboards/TicketDetail.storyboard +++ b/Sluggo/Storyboards/TicketDetail.storyboard @@ -13,7 +13,7 @@ - + @@ -72,7 +72,7 @@ + + + + + + + - + + @@ -94,11 +111,15 @@ + + + + @@ -189,7 +210,7 @@ - + @@ -205,8 +226,5 @@ - - - diff --git a/Sluggo/View Controllers/TicketDetailViewController.swift b/Sluggo/View Controllers/TicketDetailViewController.swift index 3008041..297da18 100644 --- a/Sluggo/View Controllers/TicketDetailViewController.swift +++ b/Sluggo/View Controllers/TicketDetailViewController.swift @@ -7,7 +7,7 @@ import UIKit -var teamMembers: [String] = ["No Assigned User", "User1", "User2", "User3", "User4"] +var teamMembers: [String] = ["No Assigned User"] var currentMember: String = "No Assigned User" class TicketDetailViewController: UIViewController, UITextViewDelegate { @@ -18,7 +18,6 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { var identity: AppIdentity var ticket: TicketRecord? - let stackView = UIStackView() init? (coder: NSCoder, identity: AppIdentity) { self.identity = identity @@ -31,19 +30,23 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { override func viewDidLoad() { super.viewDidLoad() -// let memberManager = MemberManager(identity: self.identity) -// memberManager.fetchTeamMembers(){ result in -// switch(result){ -// case .success(let record): -// print (record) -// -// case .failure(let error): -// DispatchQueue.main.async { -// let alert = UIAlertController.errorController(error: error) -// self.present(alert, animated: true, completion: nil) -// } -// } -// } + let memberManager = MemberManager(identity: self.identity) + memberManager.listTeamMembers(){ result in + switch(result){ + case .success(let record): + teamMembers = ["No Assigned User"] + for user in record.results{ + teamMembers.append(user.owner.username) + } + // print (record) + + case .failure(let error): + DispatchQueue.main.async { + let alert = UIAlertController.errorController(error: error) + self.present(alert, animated: true, completion: nil) + } + } + } NotificationCenter.default.addObserver(self, selector: #selector(changeLabel), name: .changeAssignedUser, object: nil) ticketDescription.delegate = self @@ -66,8 +69,41 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { currentAssignedUserLabel.text = currentMember } +// func createDatePicker() { +// // create toolbar +// let toolbar = UIToolbar() +// toolbar.sizeToFit() +// toolbar.translatesAutoresizingMaskIntoConstraints = false +// +// // create bar button +// //let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(doneDatePressed)) +// //toolbar.setItems([doneButton], animated: true) +// //assign toolbar +// dueDateTextField.inputAccessoryView = toolbar +// +// // assign date picker to text field +// dueDateTextField.inputView = datePicker +// +// // date picker date only mode +// datePicker.datePickerMode = .date +// +// } +// +// +// @objc func doneDatePressed(){ +// // date formatter +// let formatter = DateFormatter() +// formatter.dateStyle = .medium +// formatter.timeStyle = .none +// +// +// dueDateTextField.text = formatter.string(from: datePicker.date) +// self.view.endEditing(true) +// } +// + // MARK: Placeholder text for description - // Whoever decided to not code placeholder text for UITextView deserves to be beaten + // Whoever decided to code UITextView deserves to be beaten func textViewDidBeginEditing (_ textView: UITextView) { if ticketDescription.textColor == .lightGray{ ticketDescription.text = nil From c11d3a0df5162cc7d741965845dbc4b6f99ad52c Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Tue, 4 May 2021 17:39:09 -0700 Subject: [PATCH 090/224] Added Icons --- .../AppIcon.appiconset/Contents.json | 18 ++++++++++++++++++ .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 804 bytes .../Icon-App-20x20@2x-1.png | Bin 0 -> 2148 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 2148 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 3691 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 1361 bytes .../Icon-App-29x29@2x-1.png | Bin 0 -> 3589 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 3589 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 6098 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 2148 bytes .../Icon-App-40x40@2x-1.png | Bin 0 -> 5472 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 5472 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 9168 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 9168 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 15448 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 5121 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 12570 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 14093 bytes .../AppIcon.appiconset/ItunesArtwork@2x.png | Bin 0 -> 103057 bytes 19 files changed, 18 insertions(+) create mode 100644 Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png create mode 100644 Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png create mode 100644 Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png create mode 100644 Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 Sluggo/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png diff --git a/Sluggo/Assets.xcassets/AppIcon.appiconset/Contents.json b/Sluggo/Assets.xcassets/AppIcon.appiconset/Contents.json index 9221b9b..78d34c2 100644 --- a/Sluggo/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Sluggo/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,91 +1,109 @@ { "images" : [ { + "filename" : "Icon-App-20x20@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "20x20" }, { + "filename" : "Icon-App-20x20@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "20x20" }, { + "filename" : "Icon-App-29x29@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "29x29" }, { + "filename" : "Icon-App-29x29@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "29x29" }, { + "filename" : "Icon-App-40x40@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "40x40" }, { + "filename" : "Icon-App-40x40@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "40x40" }, { + "filename" : "Icon-App-60x60@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "60x60" }, { + "filename" : "Icon-App-60x60@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "60x60" }, { + "filename" : "Icon-App-20x20@1x.png", "idiom" : "ipad", "scale" : "1x", "size" : "20x20" }, { + "filename" : "Icon-App-20x20@2x-1.png", "idiom" : "ipad", "scale" : "2x", "size" : "20x20" }, { + "filename" : "Icon-App-29x29@1x.png", "idiom" : "ipad", "scale" : "1x", "size" : "29x29" }, { + "filename" : "Icon-App-29x29@2x-1.png", "idiom" : "ipad", "scale" : "2x", "size" : "29x29" }, { + "filename" : "Icon-App-40x40@1x.png", "idiom" : "ipad", "scale" : "1x", "size" : "40x40" }, { + "filename" : "Icon-App-40x40@2x-1.png", "idiom" : "ipad", "scale" : "2x", "size" : "40x40" }, { + "filename" : "Icon-App-76x76@1x.png", "idiom" : "ipad", "scale" : "1x", "size" : "76x76" }, { + "filename" : "Icon-App-76x76@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "76x76" }, { + "filename" : "Icon-App-83.5x83.5@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" }, { + "filename" : "ItunesArtwork@2x.png", "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" diff --git a/Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..2675adbf7cec75c4bd35eabddf631269f0b5f144 GIT binary patch literal 804 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 TYjp3&00000NkvXXu0mjfG7pma literal 0 HcmV?d00001 diff --git a/Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png b/Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png new file mode 100644 index 0000000000000000000000000000000000000000..09f7543fbfc681e3011067b1bf55c12b4a826a7c GIT binary patch literal 3589 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 Y2X*}kMFK$$*#H0l07*qoM6N<$f_*{i1poj5 literal 0 HcmV?d00001 diff --git a/Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..7e4d72410d124a76f38e6b2d3410d99f4432738f GIT binary patch literal 2148 zcmV-q2%GnbP)V;M?SSf`NFg-cS{uiI2E}^senwyYH@^(jupoOL*gnr4u zL5(g}GT_YUQD(-TqPew~o=tb6ltc(^`4`~y=u!4OwTqA4aUY#s{fh>^!PY7(SyHdZQl)~I=Ax18)v!viY-LV_}gOfVwtR|%mC;{6oAGx>lr`v z1m)>orl%$u|IZ$_eEes`>RSOwwe-AWQ0Gbw42FNpibL`EBPeTxlcRY$S~X6OTYO+s z8fZkt28g%qAlkeMO#nzN%f@la#9}7NWb$1Cs3Z!(+@bFhtBjDitc!(G1<=qmh}G#R z&%twSoP_{oZbr3ynw+za1#3DiO9+}$DZ0D6(X`byuj&Am1ffx$J&1ea8#H!q0M7;e zE!U}FF;g`igredUD9+gAf}3ztpF$8Ikhq>tS63HpZEdT{?iI`lu~+PIqNafgB#00c zGF;&~3SD}qJeuLdxd|YEAfP_gNYdYnPyw2z<2Vk5LLpRM1B)wWvEw*QOiWNJ z*+>y1Y6$A0f{CdK{=DZ|{yDstb*9gaABpg#PqY$CB_U8@tSS(O&dkI#r-H8$Z@Pkt zU!tzAf&Tvfnpmt_s9o3PER>gzSmygWqu+AGmDjZ&$EWmzPX$raswUVuw4&CJZ;`96kW z5R1jwf8Z4!dhj8(*l`|B4su;F0#r(oNF@CyyMY zlSW?2H$tUMm??mgR05sZ**W$M4PlxlLI{e*B97x!b@K%o6hhG7-%l==BNB;V*(J7L z`ytNsHgLqABCbaX7Ds|Q%|Mw6nyh*5I(D4VN;9}F2oWwRnVRu2%{b4$@B-OvmS{9e z5Cr6Mxn)vn2DoHVPfrg*2yEL%0m(!iTX$~fG3OLQXv8#wm~Nm;MWi%ODWY?>X$^rN zf(mtkD*KF02AF1qx!F19=jYLNeK{7ZwZ988sFXt2b^7}Hn4X>{8jWJvHak9i1Bcfd zj4#X((PIcHm^LH)V)JHpe*SKLx9Msw3v4h9P#|=T!mP(@Cmo7~99y?;C7n*=x^7K% zu&Rpz(&;pnN(IMp2xLG@s+rBV+`unOuX8$>=O@m;xpl|QeE;@4IkP@Py<4UuVL>z& zwm;qBrGLGNr?v5e?|p|a-+d2<47}Prmwe$k&%7KAVAl3 zve_)LSgb08RXfQNi?-u~48;DA3Z< z!rY*&2%*W&&G`#S-*b$+unJn z!~3f42hVl&tFOL_ZQG<$DKeQ1?d|QC6xc-ptakXt#8)j+^WDsnEXCvbp5G05e_sK= a7v#Sf&Y*wP65qi90000(n~ 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 W>j(1Owkyd10000(n~ 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 f_yG*Vn%9xijbNANS6+9VcyV zU#9`(D_NF2bIzReo$v2=e!t&2W1+RyT*fb8x2r37E&63#t|RQuK!lCahp_nw@w49jn$}Lsd;PtCj~i^W3*kt^>vE?-*Jmgk)lT zSY*tgXkkVk!Z+^s+yM9I>80MZ& z{4;CTZ+S<3Tnv;stN_dM6RK|K`n!L^>4QIG;PjISA<@dcK)Zs;327ju!BF2Re*f4{ z5kfKB?JJf8&>_tX(m!y}o43a5~Cnk~5(}Ibir$ z)^#@re)|O+Cy#0R3YP^OefoR!Jog~ieBc2(Hr!okrJRBiwLnSR(Z}|z+3WP)$ zKD0AQP>!Ru;>|yNiRy?C=?l|0oaN=`{*%qy|DLuDca@q7X@XXzPz514m#TA>=NZVw zBDK6GStQHN4l?=bPst4ZFDf77@JUTuGqf!aGt{5t;IRZ1X;yzQn<%9eLWqm5zVZ=PoLqL~8AcC(4TA|P>lz7GRkC~ctF*P%u16wX>$|#RE~TuOC~WvE}cS&N&>Z;h_v28pni+jG2Y6W&1Q+kVk8m?T-QYiK`;;CO|1=fRDAkRP6wFF9&^JjaXi-(==hW#KZEaQf{3gXn z6%c4wkxeB@j*pN_=ddc*Q`vbRfrcANXI#dH-y|NN#3>TxDa%t@Lqh}2&CQFxSd@XV zLUXNh3_kPM_{~vjT9<*7pNa&mf&<6Z;4>iaPF?eU3ku{3iIcUN933P*nLt)u!+78? z$;b{Yktx>OM5&=ij&0ktw6xIJ*tn>qEu1jzf-p!A>>>8jKhnHn4bspv!Y1zth3dsS zn2Zo04f0u=iJ`M((~3;houtFJp|yj+;d~*a)|xeI*5LR17c`}}LfHb>%;@t3f&rvu z&WbUGn2OTh(VN#A90$v1P`9juXk$H*#4o9ceIJR97Jgu66+;MtQi_q0kzyB%6X=$K zu%a)?jbm6Apl1;_?{0M=o(o4SscTzJL>;1X^g9@8au#V?YYf967K@S3=NHW+%RpFx z9ce{St_!mwr!v5CvHX(Swv~j9UMj}EhtwIsV*1+=Lf|+KqobpRku8GC%4U-OXg9e` z29zp4X993FhSb!wb`lbA5*hywC?@V6wAM%|85~Gl zrw}TM_x?R!*4EZWZEbB?O zQX|)OiN#`!j*gN^rx0ckA^ixQL8=s}4BE{hr63dvqFjd~FCAdtlfR`hnBvNn7FVq@ zsjcxLLREw+LhndzN()9_JH=?=KJryN5h}sCD(mRzpt`!cY$}^KVcq~@u^0mb1LSf! zq?AZ0i*X7fuY$|DT`t8%^RB587Yx>E?J>ah$Bai@b5qP0jGN3K>JKqSJrd8Z03WaiRCg}C`C(wfgqJw6{N)b^RM6G)WBJ;j4tJy;f?&v*u-azm9!ZV zFeE|>^2#OVWYIvS^zmWYLbk30x$STH@#FC3*);idsu0Irz?}&xv3w?tHG;<;+l4x3 z|I3Vihw`Bx;^y58rd!2LD3&&a zIdbGM$BrH&6bhk=X6&hKVq$`9HoIUSHm^wk?Af!W0nO-4O5A*&t4U^iu!@Va@U^_YFRXf>x%*V(`2FK17E3@Z7L*Wpg zBpj5uj>E29y9zr_)8JV}IZ8kCA}oKftOBXah*J_dx0uQkx0ia__K5-&FFt2sSjkWJ^OcFm`wxPPg&B9qg&t|A-?v2WkA zyz#~x1OkE5*ru0$9LJfr%H~9v=NmdYI*3Ff3=9m+dT>6Ur>eS|+i%&pvDXkHY{viSRs=tk53i|G!baa&+>^0?mXT@rlXtTM1W+{o^dW?Iy>_Du~@O~+*LMblhj({^Z8h_W(~u`!(=j<8LOp~l1wJU z4L96CQ$r)a$i7Mn7p*lxX^~M5F(*q#IRvCh)btaUK4g)2Lu*tZ#NCJY)09dwsw+sP zoWfS#d2CWEjpMisj~6EI11W>cFmHEeaBy&j^A|#3+ctGe zmT=pR+t`=t<9TA38XR2mS`n5eQ8PeDT1ck0%%DJt?GLhQWRP{K3~_%Ib|yb{&va1; zfmSZrtV`dh!Z1K-jb&Mkjg4{o^ywLdLE(jFvsvQtc%i#Fs%&0?tJa!eFvzM^s~8#@ zDy^934r<%BZB$09`90hxnw!*%6Ip%uI{ks!W|oB<4Ex!rbQ7s)(+xE~JzU4-e09{z3?Hxg1MdTiJ2_ z4eU+4$zkGn+m(6-Q36{CR4#>)jB`31;v0W)GaviVUBp!$pR$q0g<>$!j!o~7%ka3u zGN&T@P$)!OTiXT02-kH>W1S1;MVP0uii!$4J3AQ|7{IpeSt_$_o1NQtV1|SIm;E}9 zRybM}%JXP!G&-9`B;y>espU&I@8I^2+{gE?+e)gU8oxC0OPfGIPTSa|^jj&7%I7%t znoA}JKFdIBh{a-T-@cuemX-@f_nzNfbdx+6-QC@|uFL4?=q#0Gvsv0ZJGg1{R(_Ls zjgzD>G}u~!0%hmex1^5G+c;hu>LzUEFVg)0(nIu&*NjPj3H(RDnP6(WQ zo^;yg#Zv$-6BCmtrMUOrd%5@CdjV#I7Sl1`!z=TyR(L9_si|T4^5yjR_p@Zll38!l zab0fPv6DT|JjH|gQ+(T6kE1mEiSys7qx^ouZH%wKnrb^sEhfq^aEmLL8hV|^aE3^v z3L&TEt==J)yv?g`X4rRVoM>f~_rL#Ewr$(S6<1t=VHmvd!V82$AzE5n$>;N>=9Xnq zRaI4*$3-D*I=Z{NIdkR=u~>}y`ub8}=!F=o*RJ7xS8w8xKmLK&D>7ILj_L_qGf(8D z#}Mm3h+Er&Yg*tsXxBlD2>Oj9tY}gMf)=89A`t{y2$Yj0pBUi9GZA*)b}M(?v5m&2 zrG&#_4jec@&&$U+ar`)~%a(EfC;x_QHjCfyCzs2ilqwZGFDhZ4${HIRX=!Ppudk2V z+S*xz5JIqH=S~hBIKU}2iK`VM41^g*B}On_-h+Mf=P>LPN*ajt1cAek;_ul-*Zmb3 zrinBRv{DFRBaA$$WQ2b8VcxUjecV`GN8Yv>A0OvC-~JZu9UTk~4Di{{e*qx`=~Rk9 zAV4;o<f@N83-n^Oh>(?)+ z>RQ$h8@ju@*}HcylarHFRaMPW*``gKSh;c~uf6u#RQW|~Fif;zB9x1aM9JkGDt(jO zu_a25b!r?F*H*RF@+O>qk z;RTnd%R-npfcExwYHDie@9!_ILLOY#rLwY;ojZ5(zylAI2Jb;iN#1ct#m6!I5$^xU zHfp-J)0Q(?lsnEEc1`zn^$KPIYxPn>TM})v8ql0)ZL#FKDji zIuz^;rl+TeXP$Y64I4HP4u@x@Cx&70-S2*vr=EI>ii(OtmDY91<#N>2)NuXv*Ry^5 zc9ykukj=SBvIumEB=1>LN({rmaU5c?7=3+xB$G+%>+4y!ZXGLDteCZ<^{#H@UI_Vo zo`)WKh)^iR%9Sf;)SkS`r|Y`>`q#ha*=L_6l}b@jQNgv>Udy&^+h}cV#kTEYehOTG zDBHFf9UY~=zn^S2OLKEGU0q!)U%q_C^Dd>sD6`-V@X(<{95`@*jT<-4C>4A6NGS=0 z3LCu1WRgfELS$_V#wVy1H1hWXY`O zFDACE)f&+65yIePRc&CSi{t*?9cdSp&dhCN3PkW3~S92{hLc(_!!T)TEH(P*@E z|MWf=8;kUD7OY#hj-H+#hKGk)wrtr2%Pi+A%1c=$CMFmd7?=?ntXZ?Blr&%LZ;U*Y zov`VppA{=saPs6y>gwtU27~9_e0aOi;JWLsWA*CQGeQIJIqwD*<$KBLN1+~m_+i4~ zFrA&9gu`K!QZptkr7VR8{r&wU5{Xi1(An9EWm%;@y*BSA7V!hq0%^}b|2zi|9;C9e zlKT33s;jH<`3m1nI*vm;9xsIkO-)Uu(7-E3UJOS*7c3fKFtr-)+qaKbUU>y5Yyx|W zKsK8NprxgS?(Xi378)!Bi%QsZoH=ub6DLj(kH>Kw2cOSJV`C#%TyX^r4GpFHE>)sD zTr|QwT7eMuY)NdHV?5`BLAqVnZKU9H;Hfyd9{$FJY~ zo{LNv4tXu9AmJ4{MI;!G2E(7t<1yuiM~9o}CYtD!n>4F07W2=tZ-RpJ{GGJMnl!ai zl-hqh*J~-aD&49)2AnznT7O=Zvaho(O+ZKTg*g``vdJ|&xUBRC36EHMo?)O@?T6&ceE>~gm; zO;%{gJ-EtE6eL)(3$g+)8uUUTNNUud7M1P3s$t%2{Zb^ml0e60^-GjqZJUeUN`~_D zD20FL#CUiVD!n_Vj`*x^U#!WPk+m~H#3s$jM5oWf@6;Kad-epHyXX4tmq2x1wKT3d zF3XRRL;kec_wi^aPGQi{PI<9*Qa0Ox*TbGCojImhyv|gE%|SpHP6zXpToJc=1BfLA`PD% z8Q^?^maKgor*)DeW!tQ2+5`UCgSGKOZMqh&u-8=hdtcZ3_D00-_*<1wZL@0i#J@+0 z-2`(tKK$e#`-8x?P5nLs3W00Om#_yu&w8G*b$OnQ)6kgWPxtSQ3ME0ll53hfM0CP^ zSD684WWka9i?9=)KOD=rQT1#POH#l*wyO!5d_8 zzo*;~n-Pmj$qBsLnB`LtybT7+*urfP5d@=bv+*`&db^u$Bfv6)v`&ipIx>`31)*<% zg+nCPpg{mX8Lae1w{4J29*frrb{*wj1*?^l0mW!T@}CDCdjFafNhP|yFrpMb3x~fG zK*!UQ2u(s}{zu|_>RPaN|M;{~)Ays7;T1n4RWJdaz@`pH%C$AboZrH&Z`b56hJ(q{ zbpLC8BjFkQLxI?E@&0QlrcOY(Ohu*~snFS#Z5<9~9HEbvaqxvf&&z-SlUi>qTA)NK zClWUXstocf_Xi~!8BP6*9--Cx9zh}EF3~r^Xc_5m;;1C-Rak~9>|fvLzv);&#p*l9 zBqBwzC6seg1%2-M2lx7K!DMs4TTI$)i%S2Eu3E+)z9shs$G(v$u@~ngD3p2cqKEWa zKmBP(v2ERhQpdRurS4kg_p%&5n@Pnx(XCF!Orzjeh*E0QsF%sWDdj6sWIC;Of58O? zvy8$gw-?W#-s9#xktgYJ-dSdmP@8AtIYd%D7QP^Gh~8H!*t!j(ys{vYIorOh8<4Mj(Np|<`dTQ` zcg%BljR)d9Na= zv*UL-Ccc@C^2%qnQQ|oodoAELA>TLLmK&TXQ$blb5ZRr8@Y*4QyR=WOcYDv8&?5>v z33~#6P?ShLU6H7FM&Fh%G`swM6&@OAmQ{pkWtbr$K%A&>Bl2Kt?EkmVLQro8lDYoC z#BX$kYD&*8oc|_%5#@WIe2gTS_3&k@X$ThLOza;m9RzhY@=EACtN}PP{ue$8g>X)* zE%w=_mWU`xNn`=?C8wvE8<#UcwBQm|M^!qHWZg-_{oDuyTYQx(qYJ z2OSdjR9T~{i6wlbJC`0i=n|k`YQKf10j_8dLqlPFfb$nFqX=~!3NaYKLvEN$J9W zRj+3<_N$APC>aPTR!;G-5=ZAi#mC6P0OVD!tL zHAOzMx>IVh9kgSHv9o9F`QhMj&J@!fPPR{$LXyOCK&xcF8Z&^xG5yWT=>k(1szJgtLsXjqiK~C_Kl7wF zT-2Y|92#WHJ#HGJ<1!?e%Mp@2h_xyu=vPJR+?Iw_S3|;w$1#)j9?hVi&SVw*r=Agf z3PyHzXf-r6I_l%n+|*V-tqi_hm;W7yXgU;2h=GM={3Y4Qt%a)JgNFYSy!uRbH-tzv zXDO88QGf6mR6L(+uN5hQLTVqVdb8RsDKyVc9Bqzpe3oa6zDX6#4i2xdu~u-NE~ zqpw&E$Aj^Ui}(*(DkO5LdvWC25TxF-xuvC%U#<3;fhvpUNO6Dcbji&A$x}*3h-FyJ z^?B_M)ydgXmneo_N;5~`?Mg*_(*YUDA_hGWF|U}=@%rIZs|*sbAGvXjEjF0EfB$~E ziC)iMm5{H+FnxQS?%ySqiks+L5EI8=eLG1@YQzX$q?JEv4@ zsZ^W~iq9Y^DK^R*(6&!71INe5;~@1n(Gjo8dImuC2P<|23SBxT+xJ_y5D5_ci`x2WhEM3Kkl}-WlVnrG zkR^pb>=@G-sPHW8l&r-z%D+O3xC|$GqMG?|%=S#HJ?|BvYgS|-%>UZsi^rKB@ zzh#d}mbJ_>f4?Pd+TXn{PrzY>z9sQP_9LZj9wU4u?e4Yi5A8Dpr%ilRD#vT=sntEk zslAxwoy(-kr z`{F2A6ahlMFs;<-6aNW)6T79!MZJ6po)##TY*O+5>|mXv&d~UzGnUr-r_uZ>n6u7v zE|!hBg{WUfGehB@!^mMTmPdpVOs!5Gc~18aEjr=bK%;l4sY$$JN8;w){xj5*0frwP zhR`AqTJ;@QI>xlAvz%UNX!E6C(IAdU5Dg38qjDt~Ze6ifm5N_FbfuS3m%{E`?JQA&9@7ts|+3P}>PT-yoQhFim1`XOv**BH*9oRa$G@Lt3G> z>=zgmt8)sY*sJkLI^jV#HbNJ6cxWp!Z9t@bVvehVpI{?W?cT~J6^5nQ(3|+zF*q8F zBk?$9yr;N4Oov6)mW7Kw2s zb9CLjF2g!v%&zTFb#2;gvb*v|19P%l_}(IxBbcHu0Hlf#5yOk2G7;T!oe^pV1~HXT ze$V5P_qUEgZlINt`+8drpbicg5I z;FJJUnO{UC-nQ*;6Pm727+bvV4#8)?{nh)3Fm!YBm(R?TeNwi*d-acHeN3$AZ%} z9#EI{Q4a$X&`))%;UAemZ5s_4$6YEaDx7?Le9a+JAdi+58oWzX)L+uoq*6M#U6C{F zQjZeLqr7}2{jP)$AStL~44t8I&>=&fNLOYn3mdXO-I`!1q*z~LUNqrju4Om3rEoK7 zFgn^xEFl~RsebwZ#ud)2p}Zznbrqc3o*Q4coWo~-aC~g9$KqA|_AZQ7yH)?Axp^4S zCSg<(N-?%_=!_-KlJiZ92TFsSs4a~%P3WrFJRDYgO~p_Jh+?@0W4Z6mP^OR1zFL8= z73G3mLfQ7uImmL)uZ@QZ6{nux=AM0ub@f@KlNxN=2S;baG3l-Q==v5yL|y{}4Z>fx zrd=kt%ecQX?iP9;SfS(j`}-TD7`h}A2Y%nFUOLVb@l640NrQg)_D6S^Z<=TF*+`y@ zQ9(LIt^7hHQ`Ggj5F+n=J!gH(gm=1tI-i>GLDQKPWc2H4%`Db_IxCR(`- zw5z!x)xmq@o+cX^Ls8Jg(yu23ZTuv=D=|SQD1L@xs^(VLVTiqR2d}HEOaEuA5pUSg zCoVz3F*4^KNpNHw)ne{+RoVcrDzu6qT`8jQX@}wDcV}wg<4tiZeY%nv2vupb%I16X(n0bbn~mdNj7=*4uv{IbGL-gpWUABTrmc-TqEMBbW~VZ+Z5@63GM53 z`fG?LQDnfr;cKKBxD3ifi%>_vcd9{S8A40Rx9FCC0+DG}PPj}Gk+jy{x1B9MeRm^|lA-QDIAFvUIfqLWy#YIu3rBujwdBrbbkoQ|gJ*|iM)Oi~ zYF`C)#`1B?&Zy7+VL@4;c6;jU590n=#(>gYa1Ach4)|+Qarh%kC6Sm*HPu7s?{CD{ zFU_1WY0@9NzE`v|y*Mqg5C83mQyG`5y3b@A9v&Xubz_qSc7?skxWRqnTc|80B|dku zf~g*nd?LjzY=mO_VuaG#!69CTI&}LTsQiPz1|5|I53So%EG*JL0sbLeS{7v<6_%8+ z6-gg8#PGECq^X)b{GF3GqG{_bnd-|wZ=PkMyHKm49KvNLhU#>-2T3?-D2p~&SWHbP zYSQ_21h?(axk13c!9JOwF3vN&aA>S*E`RcvrT%ykt_oCMXb8~aFsft2ELO4qJkq|;^*%M(|hFdcG&PiR#s76NHLZ(x5^iNou z8w#J)SuV|JU7n(08EbZU@OKaoOIi+cjJqE?)d4r{KY;@6JYos$bqWlGmxeC|CSWCA zg;KEV6eMiXC(!NJvo_yUmd^$wBO_y$+}7fzsIW35d@1#s1xA7r=RNb$-FH~6`UxC? zhsr0vTI2NVh2GI>H-`vJDo^Tj>8T@rv z4$JyKVT;C}Jzh~%YLtuAh0)RIim0b6ef&@!?E@!%+c^yjehu>L^5^93F#DF~1;Ytg z)}7EfIeiLVOgCiduEJ6O&Hz#A!idT@eXgBVx55p7lCS_Tnb(>F% z`!H6T$Lr^UzR)ycBBCy@G*eyNLAYPku@Hijd)o?csT#C%1Xbe}3< zp|4i!-zal4d1#vm&Z}^eQs~#@!s5-$rtG$k4@1lkK46H7)T!I_3`bU=+Ts-*;PF#( zYkyEKyJ!oJzZ`jCiNFs0>=Dc%9#lWB!GUDSgS-nRd^N1gf}7(75n85%8R`Rj8bul8 zBWWT7K{5k#b8*49M5b9QUYnv|k?W?ZPw}b(Z*cwfSGoH_figv32t=WSl2bs zqk-g$6sufHH05<)>nO2vumjH;k-xX8A)h@=JzZIh#Ns4xPGO_yw{+|IbPFA7c zcN{89n-u)5-Ds{X)!4DV0J9j$@oc!4Vv?m!29N_0VECr1{Jq*w{`O>j)|hil4)m>Y ziPS8PT`Ey&yveFni4_$AXvNBU_eieFuGHqc7`e4(7E>t?wB?~JNQec~QJ)i_*W+Nb zXqj!}D>FQMj@|oAC6QYt1$w%wjA}-4-QPC+>?{cfrgPD?V1XNX0&TCP6HwIyjNep=VwRZ9jE-vdho! zM#PZDxr1EW)fJ!X&NIrM^5ZJNSA|n)LaIDs*Sfv0j%pfG6lnKkGnQjj)zsQ#bPDL) zlOv?ympq9byC!zI;ox&rspsBBQL(U*+w$`asM|A-SI&TbZ)1F*R#1w|+f2&v;-N?P zSf*Nc0Hq8RS!FwnzIR<`d1O+I`9d%=SSuo{l+x{-SSq01V{JbVFGx2OJTlSlprQ;75?#X(V)VQZ{Y`F2Z z?Y;px&W{N5ni3F?s+y2@1Kc=eWr|U@sVUmLvkAjXkaD>q6VRH5LWOHtX!#qyiLKJ< zm*~$`9|CxPHsJrOl{R#HW5K|3A_z|LpuNtCyBy@F)^~~;ZqD5Em-v7rha~YlCs}AL|}Fc%;6A4^YfORjU@Ke??n}16*{re zgtp|W5C!EptU23`sc79wt0bPd7hHb>PYIRO@T7C19kBb)>kc?K zvI8Mf65wzjPwxvPdt>hm+iqicx+bHOFh~Ax#+Sz%$o9Z z8eH$wp}@SN2!3*vlb44%`v1|cCG5I1bO{^6UYwl;I6PxHn2gLI>CKf$A|Z>m%bcpf zhx&z45`OGR1|Ml1SI#eoHZ@=auq7snfy{U47b|2vL1|=LR79M~_+|n>Bz;ISZA6*F zs$t+3rjzIsGu^6O1_LTy{^$!|9(kRsgW={fLc?D+zhZ^jUkjun!9UJsVeliQtFHZjF}TxYJJ;9*MGau=yNB`kz(=Z?~Q%( zS~xE}mRZx}rs_ZBWTi^}{6QtGHbx~~_k}T0Y6GeT@A+2U-XZfuf#zp2eBaHF)KMsf z7DuTH%2#umh1ikeZM0RqyMjdHdFo_^O8OZ_Og^3ILw&+bI|~03uFLzUu#D;S4^BV7y)hba3n{SD8*U}lL1~Pxevf0uF z`}lNp4k?KhR%+MeYcb)_EEcZ2Pm9u~C_1pct7SRCkRL@u*Fl+F9>+mITp6XRER;6W0YVSFvZO-3*l5( zfUj?0(&CFGaMyQXEN^6ulc(^tlw73j_+sRnQJd1xTG7{6s8yqliv$nM4>2*ZGgI-Y z*fxiifz~BsX*T#r1cVlq z_RZ?%&0vG;!VWFrgcL(JC!XSPCBkpH0LnW_j~OY9h)60>GG4W(Dy)P{&6fxE?Q%iK zzR8aYI%*1?4T~%71drSKA!jt0yl$TV6#h=J=m|CAKA{ZL#cQZ&FT3 z87!+GtauVsyX7OjO~c?euC+x~Tx>k#bRg%_s1=>mQdB~uqhp%h4Zc6r=vbgG=1dyn zjetr_Cg_{U5IMjjQkPtz1clgwDkg;7lAUKw4V1>F@`Z`ZvZ9qmi3b7VXee*LX84yP z64HCd!^H(NW%0V-&2%3k)KRa~f|d0Pgq6;J02TdV)PoDx*AST`FI%J1o@#{UYRFpm z_A=3;V#=;={z2q`U4O@A9wX;YF40D>>PTKdC0oT{2Pxs-K`J2NX?wf+;dhwcTPZ^m z9DP4to*#*MQ7OE4_3W6YQw)HRG1JIP&prFs_wO?9*Eaz;*o&Xv3WHkd6UN;Aaw_NF z>9N`gQO%>_An9-Bm*fBwsFarr-(r#&U|eA2wQEv=w}JV773hPCo4$|_34G(oxx{(v zAw^QMU>Ty51Q^aMhOboKhqKjoXUUYSIJ#XuXxPwtp6Bu6?CNS96m7{fJ1@^LiLgeK z*)Z^W0g%Le+p!H%^0`-^J>L#Vw(%+`rdl@havAW!HoBdmAM6t>vZ7UpmlX#jHt&<; z;Aj*=hn^+Zp&eK7`Ym3V$NPGs*x(mpBaPzXsN6ZpBZ z5(AC3YaO4TpYI_K14l}>>hDI9IhhVdVs>~Eo7nVHPz>RGkPV_IGANRb`HL4#yD&6k zq4YTKsUZJVZSm^vxBp+u_s$$KhpuUhn89s#nY)XkDu@-aIRe8aC8q|M(&Q6fUS8}r z@@sdx<(jFc%As*TH`N8`UE3X2ezurzZ*QY>uSw!0On_H_HXhsrl>tVOlf zXzthl$S1dS;~@e0H)>xf5#h09x2%8ltSbF<6eT`YXyS``c*(28qAodaUHhRuR;t^# zi&Aoapy==BataNDG(CE3v*Rpnx#jk7LY$YUBo6Cc*3ec74l6|(n?YNWTIH<1MwKQ} zOP;EdBCzhgTxTnn1!jb$(%7xwaCumSWb{CO`iFwu&oeT1@rN@s4TzfH8I79a@_zB2 zZRVhB9r0o3r=L-Gv(*chM|>WJNT?zz8vJ~QQmm#55me_00UY1B-*8Z%xjeUL+>Wr< z1Hb|_cb?zZUdFJmb;qV$TZf60xdH;R(H0gKS6GG?Bwp{|V|)L0MOpAm$TPdJ@k!Cq zim{xF&9~*Wrti2amJh|jefXzkKc9yb>^X@UutC!|_3C>2=u;m|x8j!&e_W!1pZvpo z8TI^VxsCQPY~67kdx7FkWqZ~eD*1d(@kOXF_}Ga2pS%zawXvCJPL|R533dKd;v*m?X ztuoXx?VDwMiD!QXHFjv)3F{hF&=Rx^7!4kQG&~g zr9F4ZOD5ptP|qLB(7*4GHGKsJgEuk^oQe(Wk{lI$KUZ}^3&6=`zbu%)XlTQaQI%C1 zZ(mP2y*#hoUc9W|t>2$?J6X}DS9C+^Nst&>Le=)E&GoQ~W}(zIzM6GX@>B7wStYQM z-5w{3-KhB*c18KdTlLYqmH}?-Eug|kGn_~W5Ox@PuXzSsrWPU*?eA^1<&If4`M#hh zWY~P@0Zy*5?{0dq1tlX^mGJ!d*4y)$^4!r|jHC~-1<(Rcj1OvfW`dx(SoR7&)!7VQu2=@tW4?#3 z)OSQa#-B`GtXL}GnZD>8hf78$A&dVxmggd$jY}-7F>aV!U!Q)yU7tn~zmp8vle=)W zwHA{-y1QC&Xd~J0Jw@dg-ZkzE5^d;xu;VGsbI#1t9dCA86Z&uVQ>=8RInbCshF#)-Xp;t|itnp(w#fdnoE#nO?X;~!j(t5;-xtlj{i2CgOgPGO!fKucPL&6R zg)BVF@d~n)5W!iiz!x7<)RZ%4Fo7rR3jzhH(xZRZ7&L4@irFvLZcYiG?JOENe*|)( zw257-JsU7`wn`)?2H@CRzdiOrE^BYUtt}KSv_VBjc3JtD>3T3zT#;tIJDz^ESTq9v zDth~S5Ig7`@du8Y`u6Lk$(Fe8>Ons|yw=U>CZjptZkd5eF43y5-`*29s zMPndxvPl#aFkVO*x5tX~Qrar8lklo5S%A4?H^Z(l!1?c=W4^K}%vS9@#S8|kc0w5#od(P#61wGUKcHPgv(w|{1r_EOr7;#{7 znMEZsfc5r@X*U>8{-^04H5-h&y^OV0loY=jTLZS)e;xF69wknjId=JlbTdR}XRQgF zY|Nc``pS6Sda!#{mVXp*pj+YDL(_j=9 z?4wPzbMUdj(^_WVk66NIhp#8x$S5f9m7CuEWhOvdZd-d=<-q8-vH;iyxu{GIO8sdF)o7b#(+tjmHhY+cg|`^1U(Kxdf_krb?Jn0j%#Xy3 zL4v={3l||x%HOla1Z7>eQi$uy;>(2q_~dwdFv$5C|+z{~7ffBmQcPJV?gb@;~F>kCW3HH*pS^X=LQTl=5knHii+ zdQ_0yJAO>n)4|6dV&xE=HMO{clqyb^%b+ zy(E-l``AwvJEWHPF4S=qu-a$V*ZYdBJfTWnC(Hc>qW&QVfl=1gWHu3AP~8UcINy^u zVXZn+b#P|Uz`X^?FEvT(Ph3IZ$B3v#lK_Hl{uq0P4nO5I5U@e{jr$$)Ym8TRNMSgG zE~p}@if2mIU)#4=3|z{n|JkQ~J@~WDVPL5F+$?^Ne{;4Pwcq))==_TH=~K;e&>$v~ zWXQJ6DU$gyAv!9?AQcEfPEHO?LQwl9w{@S3$SC~L5UsqxMz%b8L(PK{n1gd=96c7Ta&I~-&PTpa$ z5A>HSIs0l`f;nt-I3e@r>}-ES;M2BG!BTwt7KgE6e1ZXTd}5-&1o@J&KO2j7Z3p8q z4abojMkO^xh>o#u1Ve!j!fG4LF?Vxw(-&I>9J%8r_;Nw{nmir+as{}@_jNP`c$-?N z+K#0`Yx3&(h@(H>-6$uTzkR|Ts4L3n1=HinR#kUA+r_=!t=h)!CKaTwS#~vthay0d zkU*PwU&wu5(deI!zeFH{aH(Six`U5FbZzEJOhAhq9v=^0_1Juh(O@+)tOmHIrMWqv zrGazn_jL+r^)ugTfA(Kns~Ip;&Qu}i<;Kv$*Qb3TgS2ZU1zM}Ox7Sw>w%qEl-kXm} z#{Z+Q@{_iWaD?v@9ryZ6JMeUVTqMv)_EyvnUrm>h_BXrCb7y5)vS? zFqr1ello_&qZ*qjK;?s@O3HK}Cv$jFdwaVrY^%ghr3oO3cXvL`47zzZK>AOpW7vOl zy1M>{rJFcbcbtcpw*@$6hOl11?_VHLlbIKah^Pg{6fWI9@2z7D+62X5 zy%qWnNP=8y_-KB-2?EdX%sx!a2Cs8=qBn)&%Xu+3d) z;EtFJta;%7mjmGA<6m)j+wk4qYG5TJ92M+63(y_>P^h^4DCDnV=Z9(0YR`ytMm1ww z=E5Z#Vbj+>6r5;rjL#r6WS$IzaiK*FkPDDTLIMK=F|e_lUAKqn^09>Q^*@^e7}IP$ zO~E>04mkVu_4RKQeY?85JFljM?=Lg^;0<`cD=@hP_D4!iKDPO%)gYwz#-{G& zYmOVBrYE=-#C*)52b!Rw0`MzJHJKsdHtaImN?e?r-xli(^xB<_+ynGO4QEP_;<$Ku z|Jw%C!6YE|fa%g^PE0EIQ7ZcakjGCEaNb|h^N~wXmHZK@SH4(xTZ7U-vFR0At=d36 zM%9Kbsq0TPY)25^-mA(iARzDJ;=;;1-t&~UlRIaa-BiHfuo~0_4FL$`oNJ~q5czN+BM#PbQFT`Y1=FEbu;J+8? z2jULkL=_ZJ@H;GXHkeXcX-iWIx`n?Fewh0Y8QQ-JU(aq|E!go5aQ-J5eh1#J{q~qs z_3-xAs987{!5-Ar<@f2@N67ge>-~_Qm*>zWUaskGHnBIEn|n-bMY%ryJr>3a;^*S( zzwN#KS|Q^D{75D&o2Mki$Da>z1i=IoEcLXn^31^eqZXVZB0m5TyWATn3@{*I+8G80 zv2%EO$$>stYy&+9z(Ov|+V(J~k&zJ~IAOMP6&2ro1#wyAl<_gFjK?h!MHe;o|J~jK z9IngPfGcZ70o@d^4@B;FQhR{;KRLGRz&{a*N!D?{m)i-}nKDFuZUAoV7PNG6hXxiT>Ofw9vMsDCb1d?vcV!NIG)Izo?z5_1a#aX|2g1_u=C zz#q4m15}IAH9himy5N$8lJY;N3qQI;YJI#s(p_=_s1Es|IErFxXDk&20lX&5>!Stq zCUY+HB~qOsh+DF2{z(81i&G0R68wEYkXjX_evTxv|7fA6vlaptx%OShvKiwZGB5*1 z_J2-z4t;8BDzHutJB_(43D-zu9=aI!{s3jnHpgb$Ir=tW`K(Hq`(Ym?8-2bl)#^z* zJ97YRU6pD2+qZ8ewaZoeV#Gh#KX=tnjE;`xL8Vpxi@w?%SHTn%Vvbdtr!uwRi2>Px z`{c{Kv&YbEsC1pSML@t4n=)oDzoYp?2HPZCVy62>FAgg@I4Pf4?Tmhe=GC9rQQ5pi#zVptkD9l%ZQ4K z5`T})S6guDf=bug0mWNAKU_OA^F5W|vJ5v-gMTRa{^wFPPm9HEzsq~)8a6dGb@hNp zTwcHIBhe;?faqOafAMM#S^$&-U8bSilmY5Aea618 zikjLK6Cd0pbP%{$MM6OVcw(=rw@Y&2AFcb%ncmm3FL!%$j9I1tZOS@fM`S|$P(rM4 z2PP#gsi~>)bqH{FbekM%YBuGxpabOO`u&tQTn^gw%S+H=t=^o2zXyFut1>1r#YBoJ zr*u={^NF>{<4JQFw91MoQ>~JOoV*NhQaiZ=+C&)Icwv0Cn#{mDzgI^BE2^m8ByOE| zVb^U#`g9=6Qoao2m%v@P$%MIW!66lTX=`gBx2x!cFw3|n_nryx%$URO=(VH`?UozO z5HmjVHJ~_}vms3sn7V#@9i>U+U#&UVmeDJM{30%na@C47z(N5kl1Go`Umdjp4obD;K^EdyB;V3@optzx}#<;)*6u^JmJeVz2 zQB~dSa&oWAbzB8*_8}u90~HT8=}W-KFWMlw@fvmS!IAUFo`6Gi^E+1fd8jaoAI47b z#|tUWm4B-xrj^%c70-cR4fA|eSBSN{s${50wK^^;!+w2!cop3-tkqR=mR^^PnBf0@ cym&@~ literal 0 HcmV?d00001 diff --git a/Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/Sluggo/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b3a9d6e9a6225ae66847f6d6cdef8836de4b02f7 GIT binary patch literal 14093 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 literal 0 HcmV?d00001 diff --git a/Sluggo/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png b/Sluggo/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..243addc0cb88ec470962d3b85ca61e6ffda85586 GIT binary patch literal 103057 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

v+&{VgQWoO#D?nPmeunkzM1kGb5SLYi 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 O)7xpJo2z5{`~LvY^~K`= literal 0 HcmV?d00001 From 8532d1a2095e387e31d37f7ea05f9f2c2280770a Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Tue, 4 May 2021 17:45:12 -0700 Subject: [PATCH 091/224] Revert "Revert "slightly more convenient persistence"" This reverts commit cbd82b7399a1f57b4c000d4d21bdb40900df6001. keeping persistence changes after all but modifying it so that it works with "remember me" --- Sluggo/Models/AppIdentity.swift | 64 ++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/Sluggo/Models/AppIdentity.swift b/Sluggo/Models/AppIdentity.swift index ce5a2b0..ee50157 100644 --- a/Sluggo/Models/AppIdentity.swift +++ b/Sluggo/Models/AppIdentity.swift @@ -8,11 +8,59 @@ import Foundation class AppIdentity: Codable { - var authenticatedUser: UserRecord? - var team: TeamRecord? - var token: String? - var baseAddress: String = Constants.Config.URL_BASE - var pageSize = 10 + + private var _authenticatedUser: UserRecord? + private var _team: TeamRecord? + private var _token: String? + private var _pageSize = 10 + private var _baseAddress: String = Constants.Config.URL_BASE + + // MARK: Computed Properties + var authenticatedUser: UserRecord? { + set(newUser) { + _authenticatedUser = newUser + enqueueSave() + } + get { + return _authenticatedUser + } + } + var team: TeamRecord? { + set(newTeam) { + _team = newTeam + enqueueSave() + } + get { + return _team + } + } + var token: String? { + set (newToken) { + _token = newToken + enqueueSave() + } + get { + return _token + } + } + var pageSize: Int { + set(newPageSize) { + _pageSize = newPageSize + enqueueSave() + } + get { + return _pageSize + } + } + var baseAddress: String { + set(newBaseAddress) { + _baseAddress = newBaseAddress + enqueueSave() + } + get { + return _baseAddress + } + } private static var persistencePath: URL { get { @@ -22,6 +70,12 @@ class AppIdentity: Codable { } } + private func enqueueSave() { + DispatchQueue.global().async { + let _ = self.saveToDisk() + } + } + static func loadFromDisk() -> AppIdentity? { guard let persistenceFileContents = try? String(contentsOf: persistencePath) else { return nil From fda8bfca51bb3e732e228b8b1dcc2241e1af2eb0 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Tue, 4 May 2021 17:46:38 -0700 Subject: [PATCH 092/224] Changed login flow, handling for nil team with persist on --- Sluggo/Models/Managers/UserManager.swift | 2 +- .../LaunchViewController.swift | 30 +++++++++++++++---- .../LoginViewController.swift | 23 +++++++------- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/Sluggo/Models/Managers/UserManager.swift b/Sluggo/Models/Managers/UserManager.swift index 7ceb108..62e5885 100644 --- a/Sluggo/Models/Managers/UserManager.swift +++ b/Sluggo/Models/Managers/UserManager.swift @@ -21,7 +21,7 @@ class UserManager { .setIdentity(identity: self.identity) JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completitonHandler) - } + } public func doLogin(username: String, password: String, completionHandler: @escaping(Result) -> Void) -> Void { let params = ["username":username, "password":password] as Dictionary diff --git a/Sluggo/View Controllers/LaunchViewController.swift b/Sluggo/View Controllers/LaunchViewController.swift index da3da34..39d35f4 100644 --- a/Sluggo/View Controllers/LaunchViewController.swift +++ b/Sluggo/View Controllers/LaunchViewController.swift @@ -31,12 +31,22 @@ class LaunchViewController: UIViewController { if(remember) { // Call login function from remembered. If failed go to login - userManager.getUser() { result in - switch result { + userManager.getUser() { loginResult in + switch loginResult { case .success( _): - DispatchQueue.main.sync { - self.performSegue(withIdentifier: "automaticLogin", sender: self) + + //Need to also check for invalid saved team + if(self.identity.team == nil) { + DispatchQueue.main.sync { + self.showTeams() + } + } else { + DispatchQueue.main.sync { + self.continueLogin() + } + } + break case .failure(let error): print(error) @@ -53,6 +63,17 @@ class LaunchViewController: UIViewController { func showLogin() { if let vc = self.storyboard?.instantiateViewController(identifier: "loginPage", creator: {coder in return LoginViewController(coder: coder, identity: self.identity, completion: { + self.showTeams() + }) + }) { + vc.isModalInPresentation = true + self.present(vc, animated: true) + } + } + + func showTeams() { + if let vc = self.storyboard?.instantiateViewController(identifier: "TableViewContainer", creator: {coder in + return TeamSelectorContainerViewController(coder: coder, identity: self.identity, completion: { self.continueLogin() }) }) { @@ -62,7 +83,6 @@ class LaunchViewController: UIViewController { } func continueLogin() { -// sleep(3) self.performSegue(withIdentifier: "automaticLogin", sender: self) } diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index 7cd71d7..b5acff3 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -87,8 +87,7 @@ class LoginViewController: UIViewController { // Segue out of VC DispatchQueue.main.async { - //self.performSegue(withIdentifier: "loginToRoot", sender: self) - self.launchTeamSelect() + self.dismiss(animated: true, completion: self.completion) } break; @@ -101,16 +100,16 @@ class LoginViewController: UIViewController { } } - private func launchTeamSelect() { - if let vc = storyboard?.instantiateViewController(identifier: "TableViewContainer", creator: { coder in - return TeamSelectorContainerViewController(coder: coder, identity: self.identity) { - self.dismiss(animated: true, completion: self.completion) - } - }) { - vc.isModalInPresentation = true - self.present(vc, animated: true) - } - } +// private func launchTeamSelect() { +// if let vc = storyboard?.instantiateViewController(identifier: "TableViewContainer", creator: { coder in +// return TeamSelectorContainerViewController(coder: coder, identity: self.identity) { +// self.dismiss(animated: true, completion: self.completion) +// } +// }) { +// vc.isModalInPresentation = true +// self.present(vc, animated: true) +// } +// } @IBAction func persistLoginButton(_ sender: Any) { self.persistButton.isSelected.toggle() From 5c62535865985b3ddf67087e4115c75e96da1354 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Tue, 4 May 2021 18:19:24 -0700 Subject: [PATCH 093/224] reshow team selector if team becomes invalid --- Sluggo/Models/Managers/TeamManager.swift | 8 ++++ .../LaunchViewController.swift | 37 +++++++++++++------ 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/Sluggo/Models/Managers/TeamManager.swift b/Sluggo/Models/Managers/TeamManager.swift index bc01042..227996d 100644 --- a/Sluggo/Models/Managers/TeamManager.swift +++ b/Sluggo/Models/Managers/TeamManager.swift @@ -23,4 +23,12 @@ class TeamManager { JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } + + public func getTeam(team: TeamRecord, completionHandler: @escaping(Result) -> Void) -> Void { + let requestBuilder = URLRequestBuilder(url: URL(string: identity.baseAddress + TeamManager.urlBase + "\(team.id)/")!) + .setIdentity(identity: identity) + .setMethod(method: .GET) + + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) + } } diff --git a/Sluggo/View Controllers/LaunchViewController.swift b/Sluggo/View Controllers/LaunchViewController.swift index 39d35f4..0421c2a 100644 --- a/Sluggo/View Controllers/LaunchViewController.swift +++ b/Sluggo/View Controllers/LaunchViewController.swift @@ -34,19 +34,10 @@ class LaunchViewController: UIViewController { userManager.getUser() { loginResult in switch loginResult { case .success( _): - //Need to also check for invalid saved team - if(self.identity.team == nil) { - DispatchQueue.main.sync { - self.showTeams() - } - } else { - DispatchQueue.main.sync { - self.continueLogin() - } - + DispatchQueue.main.sync { + self.tryTeam() } - break case .failure(let error): print(error) @@ -60,6 +51,30 @@ class LaunchViewController: UIViewController { } } + // runs on a background thread + private func tryTeam() { + if let team = identity.team{ + let teamManager = TeamManager(identity: self.identity) + teamManager.getTeam(team: team) { result in + switch result { + case .success(let teamRecord): + self.identity.team = teamRecord + DispatchQueue.main.sync { + self.continueLogin() + } + break + case .failure(let error): + print(error) + DispatchQueue.main.sync { + self.showTeams() + } + } + } + } else { + self.showTeams() + } + } + func showLogin() { if let vc = self.storyboard?.instantiateViewController(identifier: "loginPage", creator: {coder in return LoginViewController(coder: coder, identity: self.identity, completion: { From f0daafc925cc563792843d8725d64de3992e19f8 Mon Sep 17 00:00:00 2001 From: Stephan Martin Date: Tue, 4 May 2021 20:18:05 -0700 Subject: [PATCH 094/224] Create ticket working besides assign user --- Sluggo/Models/Codables/Member.swift | 2 +- Sluggo/Models/Codables/TicketRecord.swift | 9 ++ Sluggo/Models/Managers/TicketManager.swift | 4 +- Sluggo/Storyboards/TicketDetail.storyboard | 39 ++++++-- .../TicketDetailViewController.swift | 94 +++++++++++++++++-- .../TicketListController.swift | 2 +- 6 files changed, 126 insertions(+), 24 deletions(-) diff --git a/Sluggo/Models/Codables/Member.swift b/Sluggo/Models/Codables/Member.swift index 54eb4ca..b31ccf2 100644 --- a/Sluggo/Models/Codables/Member.swift +++ b/Sluggo/Models/Codables/Member.swift @@ -8,7 +8,7 @@ import Foundation -struct MemberRecord: Codable { +struct MemberRecord: Codable{ var id: String var owner: UserRecord var team_id: Int diff --git a/Sluggo/Models/Codables/TicketRecord.swift b/Sluggo/Models/Codables/TicketRecord.swift index 149d78a..ac563d6 100644 --- a/Sluggo/Models/Codables/TicketRecord.swift +++ b/Sluggo/Models/Codables/TicketRecord.swift @@ -25,3 +25,12 @@ struct TicketRecord: Codable { } + +struct WriteTicketRecord: Codable{ + var tag_list: [TagRecord]? + var assigned_user: MemberRecord? + var status: StatusRecord? + var title: String + var description: String? + var due_date: Date? +} diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index faab233..5d9f798 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -32,12 +32,12 @@ class TicketManager { JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } - public func makeTicket(ticket: TicketRecord, completionHandler: @escaping(Result) -> Void)->Void{ + public func makeTicket(ticket: WriteTicketRecord, completionHandler: @escaping(Result) -> Void)->Void{ guard let body = JsonLoader.encode(object: ticket) else { completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize ticket JSON for makeTicket in TicketManager"))) return } - + print(NSString(data: body, encoding: String.Encoding.utf8.rawValue)) let requestBuilder = URLRequestBuilder(url: makeListUrl(page: 1)) .setMethod(method: .POST) .setData(data: body) diff --git a/Sluggo/Storyboards/TicketDetail.storyboard b/Sluggo/Storyboards/TicketDetail.storyboard index a5c075e..96c65c4 100644 --- a/Sluggo/Storyboards/TicketDetail.storyboard +++ b/Sluggo/Storyboards/TicketDetail.storyboard @@ -18,12 +18,12 @@ - + - + @@ -33,21 +33,21 @@ - + - + - + + + + + + + + + + + + @@ -104,12 +115,14 @@ - + + + @@ -121,6 +134,7 @@ + @@ -128,9 +142,14 @@ + + + + + diff --git a/Sluggo/View Controllers/TicketDetailViewController.swift b/Sluggo/View Controllers/TicketDetailViewController.swift index 297da18..68e9354 100644 --- a/Sluggo/View Controllers/TicketDetailViewController.swift +++ b/Sluggo/View Controllers/TicketDetailViewController.swift @@ -7,14 +7,21 @@ import UIKit -var teamMembers: [String] = ["No Assigned User"] -var currentMember: String = "No Assigned User" +//var teamMembers: [String] = ["No Assigned User"] +var teamMembers: [MemberRecord?] = [nil] +var currentMember: MemberRecord? = nil +var currentlyChosenMember: MemberRecord? = nil class TicketDetailViewController: UIViewController, UITextViewDelegate { @IBOutlet weak var ticketTitle: UITextField! @IBOutlet weak var ticketDescription: UITextView! @IBOutlet weak var currentAssignedUserLabel: UILabel! + @IBOutlet weak var navigationItemDisplay: UINavigationItem! + @IBOutlet weak var dateTimePicker: UIDatePicker! + @IBOutlet weak var navBar: UINavigationBar! + @IBOutlet weak var changeButton: UIButton! + @IBOutlet weak var ticketTitleTopConstraint: NSLayoutConstraint! var identity: AppIdentity var ticket: TicketRecord? @@ -34,9 +41,9 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { memberManager.listTeamMembers(){ result in switch(result){ case .success(let record): - teamMembers = ["No Assigned User"] + teamMembers = [nil] for user in record.results{ - teamMembers.append(user.owner.username) + teamMembers.append(user) } // print (record) @@ -52,21 +59,88 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { ticketDescription.delegate = self if(ticket != nil){ ticketTitle.text = ticket?.title - ticketDescription.text = ticket?.description + if let description = ticket?.description{ + if(description != ""){ + ticketDescription.text = description + } + else{ // Placeholder text + ticketDescription.text = "Description of ticket" + ticketDescription.textColor = .lightGray + } + } + else{ // Placeholder text + ticketDescription.text = "Description of ticket" + ticketDescription.textColor = .lightGray + } if(ticket?.assigned_user != nil){ currentAssignedUserLabel.text = ticket?.assigned_user?.owner.username + currentlyChosenMember = ticket?.assigned_user } - + if let date = ticket?.due_date{ + dateTimePicker.date = date + } + //navigationController?.setNavigationBarHidden(true, animated: true) + navBar.isHidden = true + ticketTitleTopConstraint.constant = 0 + changeButton.isHidden = true + ticketTitle.isUserInteractionEnabled = false + ticketDescription.isUserInteractionEnabled = false + dateTimePicker.isUserInteractionEnabled = false + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(setToEditMode)) } else{ - ticketDescription.text = "Description of ticket" + ticketDescription.text = "Description of ticket" // Placeholder text ticketDescription.textColor = .lightGray + navigationItemDisplay.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelMode)) + navigationItemDisplay.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(submitTicketMode)) + } + } + + @objc func goBackToPrevView(){ + navigationController?.popViewController(animated: true) + navigationController?.setNavigationBarHidden(false, animated: true) + } + + @objc func cancelMode(){ + self.dismiss(animated: true, completion: nil) + } + + @objc func submitTicketMode(){ + let title = ticketTitle.text ?? "Default title (This is an error)" + var description: String? + description = nil + if ticketDescription.textColor != .lightGray { + description = ticketDescription.text } + let date = dateTimePicker.date + + + let ticket = WriteTicketRecord(tag_list: nil, assigned_user: currentMember, status: nil, title: title, description: description, due_date: date) + //print (ticket) + let manager = TicketManager(identity) + manager.makeTicket(ticket: ticket){ result in + switch(result){ + case .success(let record): + print(record) + DispatchQueue.main.async { + self.dismiss(animated: true, completion: nil) + } + case .failure(let error): + print(error) + DispatchQueue.main.async { + let alert = UIAlertController.errorController(error: error) + self.present(alert, animated: true, completion: nil) + } + } + } + } + + @objc func setToEditMode(){ } @objc func changeLabel(_notification: Notification){ - currentAssignedUserLabel.text = currentMember + currentAssignedUserLabel.text = currentMember?.owner.username ?? "No Assigned User" } // func createDatePicker() { @@ -129,7 +203,7 @@ class PopupVC: UIViewController { override func viewDidLoad(){ super.viewDidLoad() - currentMember = "No Assigned User" // This was done because pickerView is weird, thus this needs to be forced. + currentMember = nil // This was done because pickerView is weird, thus this needs to be forced. pickerViewController.dataSource = self pickerViewController.delegate = self @@ -161,7 +235,7 @@ extension PopupVC: UIPickerViewDelegate, UIPickerViewDataSource { func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { //print(teamMembers[row]) //currentMember = teamMembers[row] - return teamMembers[row] + return teamMembers[row]?.owner.username ?? "No Assigned User" } func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index 053ee7e..fc5f640 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -53,7 +53,7 @@ class TicketListController: UITableViewController { }) as TicketDetailViewController? { vc.ticket = tickets[indexPath.row] - self.present(vc, animated: true, completion: nil) + navigationController?.pushViewController(vc, animated: true) } From 113d5a153b5300353b1e526beb5c4d832b8f863f Mon Sep 17 00:00:00 2001 From: Stephan Martin Date: Tue, 4 May 2021 20:56:34 -0700 Subject: [PATCH 095/224] Create ticket working with assign user, but with constraint errors --- Sluggo/Models/Codables/TicketRecord.swift | 2 +- Sluggo/Storyboards/TicketDetail.storyboard | 129 ++---------------- .../TicketDetailViewController.swift | 94 ++++++++----- 3 files changed, 74 insertions(+), 151 deletions(-) diff --git a/Sluggo/Models/Codables/TicketRecord.swift b/Sluggo/Models/Codables/TicketRecord.swift index ac563d6..c389928 100644 --- a/Sluggo/Models/Codables/TicketRecord.swift +++ b/Sluggo/Models/Codables/TicketRecord.swift @@ -28,7 +28,7 @@ struct TicketRecord: Codable { struct WriteTicketRecord: Codable{ var tag_list: [TagRecord]? - var assigned_user: MemberRecord? + var assigned_user: String? var status: StatusRecord? var title: String var description: String? diff --git a/Sluggo/Storyboards/TicketDetail.storyboard b/Sluggo/Storyboards/TicketDetail.storyboard index 96c65c4..9ed2674 100644 --- a/Sluggo/Storyboards/TicketDetail.storyboard +++ b/Sluggo/Storyboards/TicketDetail.storyboard @@ -3,7 +3,6 @@ - @@ -46,30 +45,6 @@ - - - + @@ -108,42 +83,47 @@ + + + + + + + + + - + - - - - + + - - - + @@ -156,92 +136,11 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Sluggo/View Controllers/TicketDetailViewController.swift b/Sluggo/View Controllers/TicketDetailViewController.swift index 68e9354..570b895 100644 --- a/Sluggo/View Controllers/TicketDetailViewController.swift +++ b/Sluggo/View Controllers/TicketDetailViewController.swift @@ -16,15 +16,15 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { @IBOutlet weak var ticketTitle: UITextField! @IBOutlet weak var ticketDescription: UITextView! - @IBOutlet weak var currentAssignedUserLabel: UILabel! @IBOutlet weak var navigationItemDisplay: UINavigationItem! + @IBOutlet weak var assignedUserTextField: UITextField! @IBOutlet weak var dateTimePicker: UIDatePicker! @IBOutlet weak var navBar: UINavigationBar! - @IBOutlet weak var changeButton: UIButton! @IBOutlet weak var ticketTitleTopConstraint: NSLayoutConstraint! var identity: AppIdentity var ticket: TicketRecord? + var pickerView: UIPickerView = UIPickerView() init? (coder: NSCoder, identity: AppIdentity) { self.identity = identity @@ -54,7 +54,11 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { } } } - NotificationCenter.default.addObserver(self, selector: #selector(changeLabel), name: .changeAssignedUser, object: nil) +// NotificationCenter.default.addObserver(self, selector: #selector(changeLabel), name: .changeAssignedUser, object: nil) + + pickerView.dataSource = self + pickerView.delegate = self + createUserPicker() ticketDescription.delegate = self if(ticket != nil){ @@ -73,7 +77,7 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { ticketDescription.textColor = .lightGray } if(ticket?.assigned_user != nil){ - currentAssignedUserLabel.text = ticket?.assigned_user?.owner.username + assignedUserTextField.text = ticket?.assigned_user?.owner.username currentlyChosenMember = ticket?.assigned_user } if let date = ticket?.due_date{ @@ -82,7 +86,6 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { //navigationController?.setNavigationBarHidden(true, animated: true) navBar.isHidden = true ticketTitleTopConstraint.constant = 0 - changeButton.isHidden = true ticketTitle.isUserInteractionEnabled = false ticketDescription.isUserInteractionEnabled = false dateTimePicker.isUserInteractionEnabled = false @@ -113,9 +116,11 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { description = ticketDescription.text } let date = dateTimePicker.date + var member: String? + member = currentMember?.id - let ticket = WriteTicketRecord(tag_list: nil, assigned_user: currentMember, status: nil, title: title, description: description, due_date: date) + let ticket = WriteTicketRecord(tag_list: nil, assigned_user: member, status: nil, title: title, description: description, due_date: date) //print (ticket) let manager = TicketManager(identity) manager.makeTicket(ticket: ticket){ result in @@ -139,10 +144,29 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { } - @objc func changeLabel(_notification: Notification){ - currentAssignedUserLabel.text = currentMember?.owner.username ?? "No Assigned User" +// @objc func changeLabel(_notification: Notification){ +// assignedUserTextField.text = currentMember?.owner.username ?? "No Assigned User" +// } + + func createUserPicker() { + //create toolbar + let toolbar = UIToolbar() + toolbar.sizeToFit() + toolbar.translatesAutoresizingMaskIntoConstraints = false + + //create bar button + let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(userPicked)) + toolbar.setItems([doneButton], animated: true) + assignedUserTextField.inputAccessoryView = toolbar + + assignedUserTextField.inputView = pickerView } + @objc func userPicked(){ + assignedUserTextField.text = currentMember?.owner.username ?? "No Assigned User" + self.view.endEditing(true) + + } // func createDatePicker() { // // create toolbar // let toolbar = UIToolbar() @@ -195,34 +219,34 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { //MARK: PopupController manager -class PopupVC: UIViewController { - - @IBOutlet weak var assignedUsersPicker: UIPickerView! - @IBOutlet weak var pickerViewController: UIPickerView! - - override func viewDidLoad(){ - super.viewDidLoad() - - currentMember = nil // This was done because pickerView is weird, thus this needs to be forced. - - pickerViewController.dataSource = self - pickerViewController.delegate = self - } - - // Buttons in popup, submit button passes notification to trigger change in assignedUserLabel - @IBAction func dismissPopup(_ sender: Any) { - dismiss(animated: true, completion: nil) - } - @IBAction func submitButton(_ sender: Any) { - NotificationCenter.default.post(name: .changeAssignedUser, object:self) - dismiss(animated: true, completion: nil) - } -} +//class PopupVC: UIViewController { +// +// @IBOutlet weak var assignedUsersPicker: UIPickerView! +// @IBOutlet weak var pickerViewController: UIPickerView! +// +// override func viewDidLoad(){ +// super.viewDidLoad() +// +// currentMember = nil // This was done because pickerView is weird, thus this needs to be forced. +// +// pickerViewController.dataSource = self +// pickerViewController.delegate = self +// } +// +// // Buttons in popup, submit button passes notification to trigger change in assignedUserLabel +// @IBAction func dismissPopup(_ sender: Any) { +// dismiss(animated: true, completion: nil) +// } +// @IBAction func submitButton(_ sender: Any) { +// NotificationCenter.default.post(name: .changeAssignedUser, object:self) +// dismiss(animated: true, completion: nil) +// } +//} //MARK: UIPickerView manager -extension PopupVC: UIPickerViewDelegate, UIPickerViewDataSource { +extension TicketDetailViewController: UIPickerViewDelegate, UIPickerViewDataSource { func numberOfComponents(in pickerView: UIPickerView) -> Int { return 1 @@ -243,7 +267,7 @@ extension PopupVC: UIPickerViewDelegate, UIPickerViewDataSource { } } -extension Notification.Name { - static let changeAssignedUser = Notification.Name(rawValue: "changeAssignedUserNotification") -} +//extension Notification.Name { +// static let changeAssignedUser = Notification.Name(rawValue: "changeAssignedUserNotification") +//} From 2f63444e03810eb83dc2d496fc9b754dea1cd11a Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Tue, 4 May 2021 21:14:30 -0700 Subject: [PATCH 096/224] modify persistence to respect rememberMe. avoid running api requests in the main thread --- Sluggo/Models/AppIdentity.swift | 27 ++++++++++++++++- .../LaunchViewController.swift | 8 ++--- .../LoginViewController.swift | 29 ++----------------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/Sluggo/Models/AppIdentity.swift b/Sluggo/Models/AppIdentity.swift index ee50157..f4f1c25 100644 --- a/Sluggo/Models/AppIdentity.swift +++ b/Sluggo/Models/AppIdentity.swift @@ -14,6 +14,7 @@ class AppIdentity: Codable { private var _token: String? private var _pageSize = 10 private var _baseAddress: String = Constants.Config.URL_BASE + private var _rememberMe: Bool = false // MARK: Computed Properties var authenticatedUser: UserRecord? { @@ -61,6 +62,15 @@ class AppIdentity: Codable { return _baseAddress } } + var rememberMe: Bool { + set (newRememberMe) { + _rememberMe = newRememberMe + enqueueSave() + } + get { + return _rememberMe + } + } private static var persistencePath: URL { get { @@ -97,7 +107,22 @@ class AppIdentity: Codable { } func saveToDisk() -> Bool { - guard let appIdentityData = JsonLoader.encode(object: self) else { + + let savedRecord = AppIdentity() + + savedRecord._pageSize = self._pageSize + savedRecord._baseAddress = self._baseAddress + savedRecord._rememberMe = self._rememberMe + + // this respects the user prefreence to store the information necessary to do api requests + if (self._rememberMe) { + savedRecord._team = self._team + savedRecord._token = self._token + savedRecord._authenticatedUser = self._authenticatedUser + } + + + guard let appIdentityData = JsonLoader.encode(object: savedRecord) else { print("Failed to encode app identity with JSON, could not persist") return false } diff --git a/Sluggo/View Controllers/LaunchViewController.swift b/Sluggo/View Controllers/LaunchViewController.swift index 0421c2a..1366580 100644 --- a/Sluggo/View Controllers/LaunchViewController.swift +++ b/Sluggo/View Controllers/LaunchViewController.swift @@ -35,9 +35,7 @@ class LaunchViewController: UIViewController { switch loginResult { case .success( _): //Need to also check for invalid saved team - DispatchQueue.main.sync { - self.tryTeam() - } + self.tryTeam() break case .failure(let error): print(error) @@ -71,7 +69,9 @@ class LaunchViewController: UIViewController { } } } else { - self.showTeams() + DispatchQueue.main.sync { + self.showTeams() + } } } diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index b5acff3..fb4ef8c 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -47,9 +47,7 @@ class LoginViewController: UIViewController { @IBAction func loginButton(_ sender: Any) { let userString = username.text let passString = password.text - - print("button pressed") - + if(userString!.isEmpty || passString!.isEmpty) { // login Error print("No username or password provided, not attempting login") @@ -72,19 +70,6 @@ class LoginViewController: UIViewController { // Save to identity self.identity.token = record.key - // Persistence - // This is hacky but better than nothing - var rememberMe: Bool = false - DispatchQueue.main.sync { - rememberMe = self.persistButton.isSelected - } - if(rememberMe) { - let persistenceResult = self.identity.saveToDisk() - if(!persistenceResult) { - print("SOMETHING WENT WRONG WITH PERSISTENCE!") - } - } - // Segue out of VC DispatchQueue.main.async { self.dismiss(animated: true, completion: self.completion) @@ -100,19 +85,9 @@ class LoginViewController: UIViewController { } } -// private func launchTeamSelect() { -// if let vc = storyboard?.instantiateViewController(identifier: "TableViewContainer", creator: { coder in -// return TeamSelectorContainerViewController(coder: coder, identity: self.identity) { -// self.dismiss(animated: true, completion: self.completion) -// } -// }) { -// vc.isModalInPresentation = true -// self.present(vc, animated: true) -// } -// } - @IBAction func persistLoginButton(_ sender: Any) { self.persistButton.isSelected.toggle() + self.identity.rememberMe = self.persistButton.isSelected print(self.persistButton.isSelected) } } From 5a0d07affdd19dd6e034719e6d33b3ad70f2c5a4 Mon Sep 17 00:00:00 2001 From: Stephan Martin Date: Tue, 4 May 2021 21:33:11 -0700 Subject: [PATCH 097/224] Create ticket working. Possible refresh issue. --- Sluggo/Models/Managers/TicketManager.swift | 1 - .../TicketDetailViewController.swift | 87 ++----------------- .../TicketListController.swift | 29 +------ 3 files changed, 11 insertions(+), 106 deletions(-) diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index 5d9f798..b79b815 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -37,7 +37,6 @@ class TicketManager { completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize ticket JSON for makeTicket in TicketManager"))) return } - print(NSString(data: body, encoding: String.Encoding.utf8.rawValue)) let requestBuilder = URLRequestBuilder(url: makeListUrl(page: 1)) .setMethod(method: .POST) .setData(data: body) diff --git a/Sluggo/View Controllers/TicketDetailViewController.swift b/Sluggo/View Controllers/TicketDetailViewController.swift index 570b895..4baea10 100644 --- a/Sluggo/View Controllers/TicketDetailViewController.swift +++ b/Sluggo/View Controllers/TicketDetailViewController.swift @@ -25,9 +25,11 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { var identity: AppIdentity var ticket: TicketRecord? var pickerView: UIPickerView = UIPickerView() + var completion: (() ->Void)? - init? (coder: NSCoder, identity: AppIdentity) { + init? (coder: NSCoder, identity: AppIdentity, completion: (() -> Void)?) { self.identity = identity + self.completion = completion super.init(coder: coder) } @@ -45,7 +47,6 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { for user in record.results{ teamMembers.append(user) } - // print (record) case .failure(let error): DispatchQueue.main.async { @@ -54,7 +55,6 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { } } } -// NotificationCenter.default.addObserver(self, selector: #selector(changeLabel), name: .changeAssignedUser, object: nil) pickerView.dataSource = self pickerView.delegate = self @@ -83,12 +83,12 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { if let date = ticket?.due_date{ dateTimePicker.date = date } - //navigationController?.setNavigationBarHidden(true, animated: true) navBar.isHidden = true ticketTitleTopConstraint.constant = 0 ticketTitle.isUserInteractionEnabled = false ticketDescription.isUserInteractionEnabled = false dateTimePicker.isUserInteractionEnabled = false + assignedUserTextField.isUserInteractionEnabled = false navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(setToEditMode)) } else{ @@ -121,17 +121,14 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { let ticket = WriteTicketRecord(tag_list: nil, assigned_user: member, status: nil, title: title, description: description, due_date: date) - //print (ticket) let manager = TicketManager(identity) manager.makeTicket(ticket: ticket){ result in switch(result){ case .success(let record): - print(record) DispatchQueue.main.async { - self.dismiss(animated: true, completion: nil) + self.dismiss(animated: true, completion: self.completion) } case .failure(let error): - print(error) DispatchQueue.main.async { let alert = UIAlertController.errorController(error: error) self.present(alert, animated: true, completion: nil) @@ -144,13 +141,9 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { } -// @objc func changeLabel(_notification: Notification){ -// assignedUserTextField.text = currentMember?.owner.username ?? "No Assigned User" -// } - func createUserPicker() { //create toolbar - let toolbar = UIToolbar() + let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 35)) toolbar.sizeToFit() toolbar.translatesAutoresizingMaskIntoConstraints = false @@ -167,41 +160,9 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { self.view.endEditing(true) } -// func createDatePicker() { -// // create toolbar -// let toolbar = UIToolbar() -// toolbar.sizeToFit() -// toolbar.translatesAutoresizingMaskIntoConstraints = false -// -// // create bar button -// //let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(doneDatePressed)) -// //toolbar.setItems([doneButton], animated: true) -// //assign toolbar -// dueDateTextField.inputAccessoryView = toolbar -// -// // assign date picker to text field -// dueDateTextField.inputView = datePicker -// -// // date picker date only mode -// datePicker.datePickerMode = .date -// -// } -// -// -// @objc func doneDatePressed(){ -// // date formatter -// let formatter = DateFormatter() -// formatter.dateStyle = .medium -// formatter.timeStyle = .none -// -// -// dueDateTextField.text = formatter.string(from: datePicker.date) -// self.view.endEditing(true) -// } -// // MARK: Placeholder text for description - // Whoever decided to code UITextView deserves to be beaten + // Whoever decided to code UITextView deserves to be beaten func textViewDidBeginEditing (_ textView: UITextView) { if ticketDescription.textColor == .lightGray{ ticketDescription.text = nil @@ -217,33 +178,6 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { } } -//MARK: PopupController manager - -//class PopupVC: UIViewController { -// -// @IBOutlet weak var assignedUsersPicker: UIPickerView! -// @IBOutlet weak var pickerViewController: UIPickerView! -// -// override func viewDidLoad(){ -// super.viewDidLoad() -// -// currentMember = nil // This was done because pickerView is weird, thus this needs to be forced. -// -// pickerViewController.dataSource = self -// pickerViewController.delegate = self -// } -// -// // Buttons in popup, submit button passes notification to trigger change in assignedUserLabel -// @IBAction func dismissPopup(_ sender: Any) { -// dismiss(animated: true, completion: nil) -// } -// @IBAction func submitButton(_ sender: Any) { -// NotificationCenter.default.post(name: .changeAssignedUser, object:self) -// dismiss(animated: true, completion: nil) -// } -//} - - //MARK: UIPickerView manager extension TicketDetailViewController: UIPickerViewDelegate, UIPickerViewDataSource { @@ -257,8 +191,6 @@ extension TicketDetailViewController: UIPickerViewDelegate, UIPickerViewDataSour } func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - //print(teamMembers[row]) - //currentMember = teamMembers[row] return teamMembers[row]?.owner.username ?? "No Assigned User" } @@ -266,8 +198,3 @@ extension TicketDetailViewController: UIPickerViewDelegate, UIPickerViewDataSour currentMember = teamMembers[row] } } - -//extension Notification.Name { -// static let changeAssignedUser = Notification.Name(rawValue: "changeAssignedUserNotification") -//} - diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index fc5f640..8321d6c 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -49,48 +49,27 @@ class TicketListController: UITableViewController { let identity = self.identity let detailStoryboard = UIStoryboard(name: "TicketDetail", bundle: nil) if let vc = detailStoryboard.instantiateViewController(identifier: "TicketDetail", creator:{ coder in - return TicketDetailViewController(coder: coder, identity: identity) + return TicketDetailViewController(coder: coder, identity: identity, completion: nil) }) as TicketDetailViewController? { vc.ticket = tickets[indexPath.row] navigationController?.pushViewController(vc, animated: true) } - - - -// let tempStory = UIStoryboard(name: "TicketDetail", bundle: nil) -// let vc = tempStory.instantiateViewController(identifier: "TicketDetail", creator: { coder in -// return TicketDetailViewController(coder: coder, identity: self.identity) -// }) -// -// vc.ticket = tickets[indexPath.row] -// -// self.present(vc, animated: true, completion: nil) - } @objc func connectPopUp() { let identity = self.identity let detailStoryboard = UIStoryboard(name: "TicketDetail", bundle: nil) if let vc = detailStoryboard.instantiateViewController(identifier: "TicketDetail", creator:{ coder in - return TicketDetailViewController(coder: coder, identity: identity) + return TicketDetailViewController(coder: coder, identity: identity, completion: { + self.handleRefreshAction() + }) }) as TicketDetailViewController? { vc.ticket = nil self.present(vc, animated: true, completion: nil) } } -// @IBSegueAction func passToDetail(_ coder: NSCoder, sender: Any?) -> TicketDetailViewController? { -// print ("Sender Stuff: /n") -// print(sender) -// print("Type is") -// print(sender is UITableViewCell) -// if(sender is UITableViewCell){ -// -// } -// return TicketDetailViewController(coder: coder, identity: identity, ticket: nil) -// } - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.tickets.count } From 11e4b9dac9149198f824cc84d9147617d6c6a0e3 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Tue, 4 May 2021 22:04:08 -0700 Subject: [PATCH 098/224] fixed fatal bug, avoid extraneous checking --- Sluggo/Models/Codables/Team.swift | 7 +------ Sluggo/View Controllers/TeamTableViewController.swift | 2 ++ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Sluggo/Models/Codables/Team.swift b/Sluggo/Models/Codables/Team.swift index aa2f1b2..ca972eb 100644 --- a/Sluggo/Models/Codables/Team.swift +++ b/Sluggo/Models/Codables/Team.swift @@ -18,11 +18,6 @@ struct TeamRecord: Codable, Equatable { var deactivated: Date? static func == (lhs: TeamRecord, rhs: TeamRecord) -> Bool { - return lhs.id == rhs.id && - lhs.object_uuid == rhs.object_uuid && - lhs.ticket_head == rhs.ticket_head && - lhs.created == rhs.created && - lhs.activated == rhs.activated && - lhs.deactivated == rhs.deactivated + return lhs.object_uuid == rhs.object_uuid } } diff --git a/Sluggo/View Controllers/TeamTableViewController.swift b/Sluggo/View Controllers/TeamTableViewController.swift index ca09478..031c899 100644 --- a/Sluggo/View Controllers/TeamTableViewController.swift +++ b/Sluggo/View Controllers/TeamTableViewController.swift @@ -30,6 +30,8 @@ class TeamTableViewController: UITableViewController { } private func preselectRow() { + if (teams.count == 0) { return } + for i in 0...teams.count-1 { let team = teams[i] let indexPath = IndexPath(row: i, section: 0) From cc3791f28216431dd0a6cd12f37ba8e77ec4de42 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Tue, 4 May 2021 22:08:28 -0700 Subject: [PATCH 099/224] Revert "modify persistence to respect rememberMe. avoid running api requests in the main thread" This reverts commit 2f63444e03810eb83dc2d496fc9b754dea1cd11a. --- Sluggo/Models/AppIdentity.swift | 27 +---------------- .../LaunchViewController.swift | 8 ++--- .../LoginViewController.swift | 29 +++++++++++++++++-- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/Sluggo/Models/AppIdentity.swift b/Sluggo/Models/AppIdentity.swift index f4f1c25..ee50157 100644 --- a/Sluggo/Models/AppIdentity.swift +++ b/Sluggo/Models/AppIdentity.swift @@ -14,7 +14,6 @@ class AppIdentity: Codable { private var _token: String? private var _pageSize = 10 private var _baseAddress: String = Constants.Config.URL_BASE - private var _rememberMe: Bool = false // MARK: Computed Properties var authenticatedUser: UserRecord? { @@ -62,15 +61,6 @@ class AppIdentity: Codable { return _baseAddress } } - var rememberMe: Bool { - set (newRememberMe) { - _rememberMe = newRememberMe - enqueueSave() - } - get { - return _rememberMe - } - } private static var persistencePath: URL { get { @@ -107,22 +97,7 @@ class AppIdentity: Codable { } func saveToDisk() -> Bool { - - let savedRecord = AppIdentity() - - savedRecord._pageSize = self._pageSize - savedRecord._baseAddress = self._baseAddress - savedRecord._rememberMe = self._rememberMe - - // this respects the user prefreence to store the information necessary to do api requests - if (self._rememberMe) { - savedRecord._team = self._team - savedRecord._token = self._token - savedRecord._authenticatedUser = self._authenticatedUser - } - - - guard let appIdentityData = JsonLoader.encode(object: savedRecord) else { + guard let appIdentityData = JsonLoader.encode(object: self) else { print("Failed to encode app identity with JSON, could not persist") return false } diff --git a/Sluggo/View Controllers/LaunchViewController.swift b/Sluggo/View Controllers/LaunchViewController.swift index 1366580..0421c2a 100644 --- a/Sluggo/View Controllers/LaunchViewController.swift +++ b/Sluggo/View Controllers/LaunchViewController.swift @@ -35,7 +35,9 @@ class LaunchViewController: UIViewController { switch loginResult { case .success( _): //Need to also check for invalid saved team - self.tryTeam() + DispatchQueue.main.sync { + self.tryTeam() + } break case .failure(let error): print(error) @@ -69,9 +71,7 @@ class LaunchViewController: UIViewController { } } } else { - DispatchQueue.main.sync { - self.showTeams() - } + self.showTeams() } } diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index fb4ef8c..b5acff3 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -47,7 +47,9 @@ class LoginViewController: UIViewController { @IBAction func loginButton(_ sender: Any) { let userString = username.text let passString = password.text - + + print("button pressed") + if(userString!.isEmpty || passString!.isEmpty) { // login Error print("No username or password provided, not attempting login") @@ -70,6 +72,19 @@ class LoginViewController: UIViewController { // Save to identity self.identity.token = record.key + // Persistence + // This is hacky but better than nothing + var rememberMe: Bool = false + DispatchQueue.main.sync { + rememberMe = self.persistButton.isSelected + } + if(rememberMe) { + let persistenceResult = self.identity.saveToDisk() + if(!persistenceResult) { + print("SOMETHING WENT WRONG WITH PERSISTENCE!") + } + } + // Segue out of VC DispatchQueue.main.async { self.dismiss(animated: true, completion: self.completion) @@ -85,9 +100,19 @@ class LoginViewController: UIViewController { } } +// private func launchTeamSelect() { +// if let vc = storyboard?.instantiateViewController(identifier: "TableViewContainer", creator: { coder in +// return TeamSelectorContainerViewController(coder: coder, identity: self.identity) { +// self.dismiss(animated: true, completion: self.completion) +// } +// }) { +// vc.isModalInPresentation = true +// self.present(vc, animated: true) +// } +// } + @IBAction func persistLoginButton(_ sender: Any) { self.persistButton.isSelected.toggle() - self.identity.rememberMe = self.persistButton.isSelected print(self.persistButton.isSelected) } } From 7da234d4aaabe3bbcded2a3987364dedfc60793f Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Tue, 4 May 2021 22:18:31 -0700 Subject: [PATCH 100/224] save every time --- Sluggo/View Controllers/LaunchViewController.swift | 4 +--- Sluggo/View Controllers/LoginViewController.swift | 11 ----------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/Sluggo/View Controllers/LaunchViewController.swift b/Sluggo/View Controllers/LaunchViewController.swift index 0421c2a..ca86bf8 100644 --- a/Sluggo/View Controllers/LaunchViewController.swift +++ b/Sluggo/View Controllers/LaunchViewController.swift @@ -35,9 +35,7 @@ class LaunchViewController: UIViewController { switch loginResult { case .success( _): //Need to also check for invalid saved team - DispatchQueue.main.sync { - self.tryTeam() - } + self.tryTeam() break case .failure(let error): print(error) diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index b5acff3..43b7f6c 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -100,17 +100,6 @@ class LoginViewController: UIViewController { } } -// private func launchTeamSelect() { -// if let vc = storyboard?.instantiateViewController(identifier: "TableViewContainer", creator: { coder in -// return TeamSelectorContainerViewController(coder: coder, identity: self.identity) { -// self.dismiss(animated: true, completion: self.completion) -// } -// }) { -// vc.isModalInPresentation = true -// self.present(vc, animated: true) -// } -// } - @IBAction func persistLoginButton(_ sender: Any) { self.persistButton.isSelected.toggle() print(self.persistButton.isSelected) From ebf1181cf77338de599e485e9b9fe440c7d3b325 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Tue, 4 May 2021 23:19:39 -0700 Subject: [PATCH 101/224] slightly looking better teams page --- Sluggo/Storyboards/Main.storyboard | 88 +++++++++++++------ .../LaunchViewController.swift | 4 +- .../TeamSelectorContainerViewController.swift | 46 +++++----- .../TeamTableViewController.swift | 1 + 4 files changed, 88 insertions(+), 51 deletions(-) diff --git a/Sluggo/Storyboards/Main.storyboard b/Sluggo/Storyboards/Main.storyboard index b330a68..8188a74 100644 --- a/Sluggo/Storyboards/Main.storyboard +++ b/Sluggo/Storyboards/Main.storyboard @@ -3,6 +3,7 @@ + @@ -164,12 +165,12 @@ - + - + @@ -196,24 +197,70 @@ - - + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -229,29 +276,12 @@ - - - - - - - - - - - - - - - - - - + diff --git a/Sluggo/View Controllers/LaunchViewController.swift b/Sluggo/View Controllers/LaunchViewController.swift index ca86bf8..1366580 100644 --- a/Sluggo/View Controllers/LaunchViewController.swift +++ b/Sluggo/View Controllers/LaunchViewController.swift @@ -69,7 +69,9 @@ class LaunchViewController: UIViewController { } } } else { - self.showTeams() + DispatchQueue.main.sync { + self.showTeams() + } } } diff --git a/Sluggo/View Controllers/TeamSelectorContainerViewController.swift b/Sluggo/View Controllers/TeamSelectorContainerViewController.swift index 116a0bb..a061955 100644 --- a/Sluggo/View Controllers/TeamSelectorContainerViewController.swift +++ b/Sluggo/View Controllers/TeamSelectorContainerViewController.swift @@ -10,6 +10,7 @@ import UIKit class TeamSelectorContainerViewController: UIViewController { private var identity: AppIdentity private var completion: (() -> Void)? + private var failure: (() -> Void)? init? (coder: NSCoder, identity: AppIdentity, completion: (() -> Void)?) { self.identity = identity @@ -31,29 +32,32 @@ class TeamSelectorContainerViewController: UIViewController { // MARK: - Navigation -// // In a storyboard-based application, you will often want to do a little preparation before navigation -// override func prepare(for segue: UIStoryboardSegue, sender: Any?) { -// // Get the new view controller using segue.destination. -// // Pass the selected object to the new view controller. -// if let vc = segue.destination as? TeamTableViewController { -// vc.identity = self.identity -// vc.completion = { team in -// self.identity.team = team -// self.dismiss(animated: true, completion: self.completion) -// } -// } -// } - - @IBSegueAction func launchTeamSelect(_ coder: NSCoder) -> TeamTableViewController? { - let vc = TeamTableViewController(coder: coder) - vc?.identity = self.identity - vc?.completion = { team in - self.identity.team = team - self.dismiss(animated: true, completion: self.completion) + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + if let vc = segue.destination as? TeamTableViewController { + vc.identity = self.identity + vc.completion = { team in + self.identity.team = team + self.dismiss(animated: true, completion: self.completion) + } } - - return vc } +// +// @IBSegueAction func launchTeamSelect(_ coder: NSCoder) -> TeamTableViewController? { +// let vc = TeamTableViewController(coder: coder) +// vc?.identity = self.identity +// vc?.completion = { team in +// self.identity.team = team +// self.dismiss(animated: true, completion: self.completion) +// } +// vc?.failure = { +// self.dismiss(animated: true, completion: self.failure) +// } +// +// return vc +// } } diff --git a/Sluggo/View Controllers/TeamTableViewController.swift b/Sluggo/View Controllers/TeamTableViewController.swift index 031c899..3144f77 100644 --- a/Sluggo/View Controllers/TeamTableViewController.swift +++ b/Sluggo/View Controllers/TeamTableViewController.swift @@ -10,6 +10,7 @@ import UIKit class TeamTableViewController: UITableViewController { var identity: AppIdentity! var completion: ((TeamRecord) -> Void)? + var failure: (() -> Void)? private var isFetching: Bool = false private var maxNumber: Int = 0 private var teams: [TeamRecord] = [] From 8bf98612dc50311d81a42cb04fb34971cf157204 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Tue, 4 May 2021 23:33:54 -0700 Subject: [PATCH 102/224] Fix injection on Home Thanks, Sam :) --- Sluggo/Storyboards/Home.storyboard | 14 ++++++------ .../HomeTableViewController.swift | 22 +++++++++++++++---- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/Sluggo/Storyboards/Home.storyboard b/Sluggo/Storyboards/Home.storyboard index 0e4abf5..babdf96 100644 --- a/Sluggo/Storyboards/Home.storyboard +++ b/Sluggo/Storyboards/Home.storyboard @@ -24,7 +24,7 @@ - + @@ -41,29 +41,29 @@ - + - + - + - + diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index 91c4ca1..a40a8f6 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -9,6 +9,7 @@ import UIKit class HomeTableViewController: UITableViewController { var identity: AppIdentity! + var tickets: [TicketRecord] = [] // Injection for identity init? (coder: NSCoder, identity: AppIdentity) { @@ -22,6 +23,20 @@ class HomeTableViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() + + // Load the tickets + let manager = TicketManager(identity) + manager.listTeamTickets(page: 1) { result in + switch(result) { + case .success(let list): + self.tickets = list.results + print(self.tickets) + break; + case .failure(let error): + UIAlertController.createAndPresentError(vc: self, error: error, completion: nil) + print("FAILURE") + } + } // Uncomment the following line to preserve selection between presentations // self.clearsSelectionOnViewWillAppear = false @@ -39,15 +54,14 @@ class HomeTableViewController: UITableViewController { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // #warning Incomplete implementation, return the number of rows - return 1 + return tickets.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Ticket", for: indexPath) as! TicketTableViewCell - - let mockRecord = TicketRecord(id: 1, ticket_number: 1, tag_list: nil, object_uuid: UUID(), assigned_user: nil, status: nil, title: "Test Ticket", description: "A Sample Description", due_date: nil, created: Date(), activated: nil, deactivated: nil) + let record = tickets[indexPath.row] - cell.loadFromTicketRecord(ticket: mockRecord) + cell.loadFromTicketRecord(ticket: record) return cell } From 81a54ae4a49576d4078bda86a209d66c47a076c1 Mon Sep 17 00:00:00 2001 From: Stephan Martin Date: Tue, 4 May 2021 23:34:43 -0700 Subject: [PATCH 103/224] Create ticket done besides some special error handling --- Sluggo/Storyboards/TicketDetail.storyboard | 23 +++++++++++++++---- .../TicketDetailViewController.swift | 19 +++++++++++++-- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/Sluggo/Storyboards/TicketDetail.storyboard b/Sluggo/Storyboards/TicketDetail.storyboard index 9ed2674..b40a8ef 100644 --- a/Sluggo/Storyboards/TicketDetail.storyboard +++ b/Sluggo/Storyboards/TicketDetail.storyboard @@ -67,9 +67,9 @@ - + - + @@ -91,6 +91,15 @@ + + + + + + + + + @@ -101,23 +110,25 @@ + + - - - + + + @@ -125,6 +136,8 @@ + + diff --git a/Sluggo/View Controllers/TicketDetailViewController.swift b/Sluggo/View Controllers/TicketDetailViewController.swift index 4baea10..1a44bc8 100644 --- a/Sluggo/View Controllers/TicketDetailViewController.swift +++ b/Sluggo/View Controllers/TicketDetailViewController.swift @@ -21,6 +21,8 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { @IBOutlet weak var dateTimePicker: UIDatePicker! @IBOutlet weak var navBar: UINavigationBar! @IBOutlet weak var ticketTitleTopConstraint: NSLayoutConstraint! + @IBOutlet weak var dueDateSwitch: UISwitch! + @IBOutlet weak var dueDateLabel: UILabel! var identity: AppIdentity var ticket: TicketRecord? @@ -39,6 +41,8 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { override func viewDidLoad() { super.viewDidLoad() + dateTimePicker.isEnabled = dueDateSwitch.isOn + let memberManager = MemberManager(identity: self.identity) memberManager.listTeamMembers(){ result in switch(result){ @@ -82,6 +86,11 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { } if let date = ticket?.due_date{ dateTimePicker.date = date + dateTimePicker.isEnabled = true + } + else{ + dateTimePicker.isHidden = true + dueDateLabel.isHidden = true } navBar.isHidden = true ticketTitleTopConstraint.constant = 0 @@ -89,6 +98,7 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { ticketDescription.isUserInteractionEnabled = false dateTimePicker.isUserInteractionEnabled = false assignedUserTextField.isUserInteractionEnabled = false + dueDateSwitch.isHidden = true navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(setToEditMode)) } else{ @@ -99,6 +109,11 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { } } + @IBAction func dueDateSwitch(_ sender: UISwitch) { + dateTimePicker.isEnabled = sender.isOn + } + + @objc func goBackToPrevView(){ navigationController?.popViewController(animated: true) navigationController?.setNavigationBarHidden(false, animated: true) @@ -115,7 +130,7 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { if ticketDescription.textColor != .lightGray { description = ticketDescription.text } - let date = dateTimePicker.date + let date = dueDateSwitch.isOn ? dateTimePicker.date : nil var member: String? member = currentMember?.id @@ -124,7 +139,7 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { let manager = TicketManager(identity) manager.makeTicket(ticket: ticket){ result in switch(result){ - case .success(let record): + case .success(_): DispatchQueue.main.async { self.dismiss(animated: true, completion: self.completion) } From 42537dae91c2862d43fe3446bcdd63229f34510c Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Tue, 4 May 2021 23:36:28 -0700 Subject: [PATCH 104/224] persist only when we tell it to. throw out data when no team is selected --- Sluggo/Models/AppIdentity.swift | 16 ++++++++++++++-- Sluggo/Storyboards/Main.storyboard | 6 ++++++ .../View Controllers/LaunchViewController.swift | 8 ++++++++ .../View Controllers/LoginViewController.swift | 14 +------------- .../TeamSelectorContainerViewController.swift | 12 ++++++++---- .../TeamTableViewController.swift | 1 - 6 files changed, 37 insertions(+), 20 deletions(-) diff --git a/Sluggo/Models/AppIdentity.swift b/Sluggo/Models/AppIdentity.swift index ee50157..aa5e922 100644 --- a/Sluggo/Models/AppIdentity.swift +++ b/Sluggo/Models/AppIdentity.swift @@ -14,6 +14,7 @@ class AppIdentity: Codable { private var _token: String? private var _pageSize = 10 private var _baseAddress: String = Constants.Config.URL_BASE + private var persist: Bool = false // MARK: Computed Properties var authenticatedUser: UserRecord? { @@ -70,9 +71,20 @@ class AppIdentity: Codable { } } + public func setPersistData(persist: Bool) { + self.persist = persist + if persist { + enqueueSave() + } else { + let _ = AppIdentity.deletePersistenceFile() + } + } + private func enqueueSave() { - DispatchQueue.global().async { - let _ = self.saveToDisk() + if self.persist { + DispatchQueue.global().async { + let _ = self.saveToDisk() + } } } diff --git a/Sluggo/Storyboards/Main.storyboard b/Sluggo/Storyboards/Main.storyboard index 8188a74..18ae89b 100644 --- a/Sluggo/Storyboards/Main.storyboard +++ b/Sluggo/Storyboards/Main.storyboard @@ -235,6 +235,9 @@ + + + @@ -261,6 +264,9 @@ + + + diff --git a/Sluggo/View Controllers/LaunchViewController.swift b/Sluggo/View Controllers/LaunchViewController.swift index 1366580..3c9a4ae 100644 --- a/Sluggo/View Controllers/LaunchViewController.swift +++ b/Sluggo/View Controllers/LaunchViewController.swift @@ -90,6 +90,14 @@ class LaunchViewController: UIViewController { if let vc = self.storyboard?.instantiateViewController(identifier: "TableViewContainer", creator: {coder in return TeamSelectorContainerViewController(coder: coder, identity: self.identity, completion: { self.continueLogin() + }, failure: { + + // reset the data, go back to the login page. + self.identity.token = nil + self.identity.authenticatedUser = nil + self.identity.team = nil + + self.showLogin() }) }) { vc.isModalInPresentation = true diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index 43b7f6c..853b983 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -72,19 +72,6 @@ class LoginViewController: UIViewController { // Save to identity self.identity.token = record.key - // Persistence - // This is hacky but better than nothing - var rememberMe: Bool = false - DispatchQueue.main.sync { - rememberMe = self.persistButton.isSelected - } - if(rememberMe) { - let persistenceResult = self.identity.saveToDisk() - if(!persistenceResult) { - print("SOMETHING WENT WRONG WITH PERSISTENCE!") - } - } - // Segue out of VC DispatchQueue.main.async { self.dismiss(animated: true, completion: self.completion) @@ -102,6 +89,7 @@ class LoginViewController: UIViewController { @IBAction func persistLoginButton(_ sender: Any) { self.persistButton.isSelected.toggle() + self.identity.setPersistData(persist: self.persistButton.isSelected) print(self.persistButton.isSelected) } } diff --git a/Sluggo/View Controllers/TeamSelectorContainerViewController.swift b/Sluggo/View Controllers/TeamSelectorContainerViewController.swift index a061955..73f8fff 100644 --- a/Sluggo/View Controllers/TeamSelectorContainerViewController.swift +++ b/Sluggo/View Controllers/TeamSelectorContainerViewController.swift @@ -11,10 +11,12 @@ class TeamSelectorContainerViewController: UIViewController { private var identity: AppIdentity private var completion: (() -> Void)? private var failure: (() -> Void)? + @IBOutlet var cancelButton: UIButton! - init? (coder: NSCoder, identity: AppIdentity, completion: (() -> Void)?) { + init? (coder: NSCoder, identity: AppIdentity, completion: (() -> Void)?, failure: (() -> Void)?) { self.identity = identity self.completion = completion + self.failure = failure super.init(coder: coder) } @@ -44,7 +46,7 @@ class TeamSelectorContainerViewController: UIViewController { } } } -// +// // @IBSegueAction func launchTeamSelect(_ coder: NSCoder) -> TeamTableViewController? { // let vc = TeamTableViewController(coder: coder) // vc?.identity = self.identity @@ -55,9 +57,11 @@ class TeamSelectorContainerViewController: UIViewController { // vc?.failure = { // self.dismiss(animated: true, completion: self.failure) // } -// +// // return vc // } - + @IBAction func didCancel(_ sender: Any) { + self.dismiss(animated: true, completion: self.failure) + } } diff --git a/Sluggo/View Controllers/TeamTableViewController.swift b/Sluggo/View Controllers/TeamTableViewController.swift index 3144f77..031c899 100644 --- a/Sluggo/View Controllers/TeamTableViewController.swift +++ b/Sluggo/View Controllers/TeamTableViewController.swift @@ -10,7 +10,6 @@ import UIKit class TeamTableViewController: UITableViewController { var identity: AppIdentity! var completion: ((TeamRecord) -> Void)? - var failure: (() -> Void)? private var isFetching: Bool = false private var maxNumber: Int = 0 private var teams: [TeamRecord] = [] From 524138fbd4a2c8eddb5c049f709ba652da17b714 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Tue, 4 May 2021 23:47:26 -0700 Subject: [PATCH 105/224] WIP test of putting all tickets on home page Will migrate to pinned momentarily, this is just a test. --- Sluggo/Storyboards/Home.storyboard | 12 ++++++------ .../View Controllers/HomeTableViewController.swift | 6 ++++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Sluggo/Storyboards/Home.storyboard b/Sluggo/Storyboards/Home.storyboard index babdf96..c66ebdd 100644 --- a/Sluggo/Storyboards/Home.storyboard +++ b/Sluggo/Storyboards/Home.storyboard @@ -41,29 +41,29 @@ - + - + - + - + diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index a40a8f6..44342df 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -29,8 +29,10 @@ class HomeTableViewController: UITableViewController { manager.listTeamTickets(page: 1) { result in switch(result) { case .success(let list): - self.tickets = list.results - print(self.tickets) + DispatchQueue.main.sync { + self.tickets = list.results + self.tableView.reloadData() + } break; case .failure(let error): UIAlertController.createAndPresentError(vc: self, error: error, completion: nil) From c748ed9a468fd34f68e1de61eab350da702aca7a Mon Sep 17 00:00:00 2001 From: Stephan Martin Date: Wed, 5 May 2021 01:12:30 -0700 Subject: [PATCH 106/224] Create and edit ticket done, but JSON errors are preventing successful ticket editing --- Sluggo/Models/Managers/TicketManager.swift | 5 +- .../TicketDetailViewController.swift | 81 +++++++++++++++---- .../TicketListController.swift | 5 ++ 3 files changed, 75 insertions(+), 16 deletions(-) diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index b79b815..b3a1ff9 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -46,11 +46,12 @@ class TicketManager { } public func updateTicket(ticket: TicketRecord, completionHandler: @escaping(Result) -> Void)-> Void { - guard let body = JsonLoader.encode(object: ticket) else { + let writeTicket = WriteTicketRecord(tag_list: ticket.tag_list, assigned_user: ticket.assigned_user?.id, status: ticket.status, title: ticket.title, description: ticket.description, due_date: ticket.due_date) + guard let body = JsonLoader.encode(object: writeTicket) else { completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize ticket JSON for updateTicket in TicketManager"))) return } - + print(NSString(data: body, encoding: String.Encoding.utf8.rawValue)) let requestBuilder = URLRequestBuilder(url: makeDetailUrl(ticket)) .setMethod(method: .PUT) .setData(data: body) diff --git a/Sluggo/View Controllers/TicketDetailViewController.swift b/Sluggo/View Controllers/TicketDetailViewController.swift index 1a44bc8..72f1959 100644 --- a/Sluggo/View Controllers/TicketDetailViewController.swift +++ b/Sluggo/View Controllers/TicketDetailViewController.swift @@ -28,6 +28,7 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { var ticket: TicketRecord? var pickerView: UIPickerView = UIPickerView() var completion: (() ->Void)? + var editingTicket = false init? (coder: NSCoder, identity: AppIdentity, completion: (() -> Void)?) { self.identity = identity @@ -126,34 +127,86 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { @objc func submitTicketMode(){ let title = ticketTitle.text ?? "Default title (This is an error)" var description: String? - description = nil + description = "" if ticketDescription.textColor != .lightGray { description = ticketDescription.text } let date = dueDateSwitch.isOn ? dateTimePicker.date : nil + print(dueDateSwitch.isOn) + print(date) var member: String? member = currentMember?.id - - let ticket = WriteTicketRecord(tag_list: nil, assigned_user: member, status: nil, title: title, description: description, due_date: date) - let manager = TicketManager(identity) - manager.makeTicket(ticket: ticket){ result in - switch(result){ - case .success(_): - DispatchQueue.main.async { - self.dismiss(animated: true, completion: self.completion) + if(editingTicket){ // Edit Ticket + + ticket!.title = title + ticket!.description = description + ticket!.due_date = date + print(ticket!.due_date) + ticket!.assigned_user = currentMember + + let manager = TicketManager(identity) + manager.updateTicket(ticket: ticket!){ result in + switch(result){ + case .success(_): + DispatchQueue.main.async { + //self.navigationController?.popViewController(animated: true) + self.noEditingMode() + NotificationCenter.default.post(name: .refreshTrigger, object: self) + + } + case .failure(let error): + DispatchQueue.main.async { + let alert = UIAlertController.errorController(error: error) + self.present(alert, animated: true, completion: nil) + } } - case .failure(let error): - DispatchQueue.main.async { - let alert = UIAlertController.errorController(error: error) - self.present(alert, animated: true, completion: nil) + } + } + else{ // Create Ticket + let ticket = WriteTicketRecord(tag_list: nil, assigned_user: member, status: nil, title: title, description: description, due_date: date) + let manager = TicketManager(identity) + manager.makeTicket(ticket: ticket){ result in + switch(result){ + case .success(_): + DispatchQueue.main.async { + self.dismiss(animated: true, completion: self.completion) + } + case .failure(let error): + DispatchQueue.main.async { + let alert = UIAlertController.errorController(error: error) + self.present(alert, animated: true, completion: nil) + } } } } } @objc func setToEditMode(){ - + editingTicket = true + navigationItem.rightBarButtonItem = nil + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(submitTicketMode)) + ticketTitle.isUserInteractionEnabled = true + ticketDescription.isUserInteractionEnabled = true + dateTimePicker.isUserInteractionEnabled = true + assignedUserTextField.isUserInteractionEnabled = true + dueDateSwitch.isHidden = false + dueDateSwitch.isOn = dateTimePicker.isEnabled + dateTimePicker.isHidden = false + dueDateLabel.isHidden = false + } + + func noEditingMode(){ + editingTicket = false + navigationItem.rightBarButtonItem = nil + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(setToEditMode)) + ticketTitle.isUserInteractionEnabled = false + ticketDescription.isUserInteractionEnabled = false + dateTimePicker.isUserInteractionEnabled = false + assignedUserTextField.isUserInteractionEnabled = false + dueDateSwitch.isHidden = true + dateTimePicker.isHidden = !dateTimePicker.isEnabled + dueDateLabel.isHidden = !dateTimePicker.isEnabled } func createUserPicker() { diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index 8321d6c..b1f8dba 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -28,6 +28,7 @@ class TicketListController: UITableViewController { configureRefreshControl() loadData(page: 1) navigationItem.rightBarButtonItem = UIBarButtonItem( barButtonSystemItem: .add, target: self, action: #selector(connectPopUp)) + NotificationCenter.default.addObserver(self, selector: #selector(handleRefreshAction), name: .refreshTrigger, object: nil) } func configureRefreshControl() { @@ -113,3 +114,7 @@ class TicketListController: UITableViewController { } } + +extension Notification.Name { + static let refreshTrigger = Notification.Name(rawValue: "SLGRefreshTriggerNotification") +} From d35a5db5e3759213316e8598aa92a7abb4e6877c Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Wed, 5 May 2021 01:14:52 -0700 Subject: [PATCH 107/224] Did Some Cleanup on stephan's gross code --- .../TicketDetailViewController.swift | 82 ++++++------------- .../TicketListController.swift | 7 +- 2 files changed, 29 insertions(+), 60 deletions(-) diff --git a/Sluggo/View Controllers/TicketDetailViewController.swift b/Sluggo/View Controllers/TicketDetailViewController.swift index 1a44bc8..bf75bc0 100644 --- a/Sluggo/View Controllers/TicketDetailViewController.swift +++ b/Sluggo/View Controllers/TicketDetailViewController.swift @@ -10,9 +10,9 @@ import UIKit //var teamMembers: [String] = ["No Assigned User"] var teamMembers: [MemberRecord?] = [nil] var currentMember: MemberRecord? = nil -var currentlyChosenMember: MemberRecord? = nil -class TicketDetailViewController: UIViewController, UITextViewDelegate { +class TicketDetailViewController: UIViewController, UITextViewDelegate, UIPickerViewDelegate, UIPickerViewDataSource { + @IBOutlet weak var ticketTitle: UITextField! @IBOutlet weak var ticketDescription: UITextView! @@ -29,9 +29,10 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { var pickerView: UIPickerView = UIPickerView() var completion: (() ->Void)? - init? (coder: NSCoder, identity: AppIdentity, completion: (() -> Void)?) { + init? (coder: NSCoder, identity: AppIdentity, ticket: TicketRecord?, completion: (() -> Void)?) { self.identity = identity self.completion = completion + self.ticket = ticket super.init(coder: coder) } @@ -42,6 +43,7 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { override func viewDidLoad() { super.viewDidLoad() dateTimePicker.isEnabled = dueDateSwitch.isOn + let memberManager = MemberManager(identity: self.identity) memberManager.listTeamMembers(){ result in @@ -65,26 +67,19 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { createUserPicker() ticketDescription.delegate = self - if(ticket != nil){ - ticketTitle.text = ticket?.title - if let description = ticket?.description{ - if(description != ""){ - ticketDescription.text = description - } - else{ // Placeholder text - ticketDescription.text = "Description of ticket" - ticketDescription.textColor = .lightGray - } - } - else{ // Placeholder text - ticketDescription.text = "Description of ticket" - ticketDescription.textColor = .lightGray + if let passedTicket = ticket { + + ticketTitle.text = passedTicket.title + + if let description = passedTicket.description { + ticketDescription.text = description } - if(ticket?.assigned_user != nil){ - assignedUserTextField.text = ticket?.assigned_user?.owner.username - currentlyChosenMember = ticket?.assigned_user + + if let assignedName = passedTicket.assigned_user?.owner.username { + assignedUserTextField.text = assignedName } - if let date = ticket?.due_date{ + + if let date = passedTicket.due_date { dateTimePicker.date = date dateTimePicker.isEnabled = true } @@ -92,6 +87,7 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { dateTimePicker.isHidden = true dueDateLabel.isHidden = true } + navBar.isHidden = true ticketTitleTopConstraint.constant = 0 ticketTitle.isUserInteractionEnabled = false @@ -102,8 +98,6 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(setToEditMode)) } else{ - ticketDescription.text = "Description of ticket" // Placeholder text - ticketDescription.textColor = .lightGray navigationItemDisplay.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelMode)) navigationItemDisplay.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(submitTicketMode)) } @@ -112,30 +106,24 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { @IBAction func dueDateSwitch(_ sender: UISwitch) { dateTimePicker.isEnabled = sender.isOn } - - - @objc func goBackToPrevView(){ - navigationController?.popViewController(animated: true) - navigationController?.setNavigationBarHidden(false, animated: true) - } - @objc func cancelMode(){ + @objc func cancelMode() { self.dismiss(animated: true, completion: nil) } @objc func submitTicketMode(){ let title = ticketTitle.text ?? "Default title (This is an error)" - var description: String? - description = nil + var description: String? = nil if ticketDescription.textColor != .lightGray { description = ticketDescription.text } + let date = dueDateSwitch.isOn ? dateTimePicker.date : nil - var member: String? - member = currentMember?.id + let member = currentMember?.id let ticket = WriteTicketRecord(tag_list: nil, assigned_user: member, status: nil, title: title, description: description, due_date: date) + let manager = TicketManager(identity) manager.makeTicket(ticket: ticket){ result in switch(result){ @@ -176,27 +164,10 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate { } - // MARK: Placeholder text for description - // Whoever decided to code UITextView deserves to be beaten - func textViewDidBeginEditing (_ textView: UITextView) { - if ticketDescription.textColor == .lightGray{ - ticketDescription.text = nil - ticketDescription.textColor = .black - } - } - - func textViewDidEndEditing (_ textView: UITextView) { - if ticketDescription.text.isEmpty || ticketDescription.text == "" { - ticketDescription.textColor = .lightGray - ticketDescription.text = "Description of Ticket" - } - } -} - -//MARK: UIPickerView manager - -extension TicketDetailViewController: UIPickerViewDelegate, UIPickerViewDataSource { - + + + + //MARK: UIPickerView manager func numberOfComponents(in pickerView: UIPickerView) -> Int { return 1 } @@ -212,4 +183,5 @@ extension TicketDetailViewController: UIPickerViewDelegate, UIPickerViewDataSour func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { currentMember = teamMembers[row] } + } diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index 8321d6c..fa4a0bd 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -49,10 +49,8 @@ class TicketListController: UITableViewController { let identity = self.identity let detailStoryboard = UIStoryboard(name: "TicketDetail", bundle: nil) if let vc = detailStoryboard.instantiateViewController(identifier: "TicketDetail", creator:{ coder in - return TicketDetailViewController(coder: coder, identity: identity, completion: nil) + return TicketDetailViewController(coder: coder, identity: identity, ticket: self.tickets[indexPath.row], completion: nil) }) as TicketDetailViewController? { - vc.ticket = tickets[indexPath.row] - navigationController?.pushViewController(vc, animated: true) } } @@ -61,11 +59,10 @@ class TicketListController: UITableViewController { let identity = self.identity let detailStoryboard = UIStoryboard(name: "TicketDetail", bundle: nil) if let vc = detailStoryboard.instantiateViewController(identifier: "TicketDetail", creator:{ coder in - return TicketDetailViewController(coder: coder, identity: identity, completion: { + return TicketDetailViewController(coder: coder, identity: identity, ticket: nil, completion: { self.handleRefreshAction() }) }) as TicketDetailViewController? { - vc.ticket = nil self.present(vc, animated: true, completion: nil) } } From 67da645d5d8d04195ebaa9bc8349cf16245ca9a1 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 5 May 2021 16:24:15 -0700 Subject: [PATCH 108/224] encodable nulls --- Sluggo.xcodeproj/project.pbxproj | 29 +++++++++++++++++++++- Sluggo/Models/Codables/Member.swift | 8 +++--- Sluggo/Models/Codables/PaginatedList.swift | 5 ++-- Sluggo/Models/Codables/StatusRecord.swift | 6 ++--- Sluggo/Models/Codables/TagRecord.swift | 6 ++--- Sluggo/Models/Codables/Team.swift | 6 ++--- Sluggo/Models/Codables/TicketRecord.swift | 25 ++++++++++--------- Sluggo/Models/Codables/User.swift | 10 ++++---- 8 files changed, 62 insertions(+), 33 deletions(-) diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 943de4d..c1e34fc 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ @@ -38,6 +38,7 @@ 5AB1C8F7262FE3520010F85E /* AppIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8F6262FE3520010F85E /* AppIdentity.swift */; }; 5AB1C8FC262FF1690010F85E /* TicketRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8FB262FF1690010F85E /* TicketRecord.swift */; }; 5AB1C909262FFF500010F85E /* UserManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C908262FFF500010F85E /* UserManager.swift */; }; + 5AB1F18F26435F9600993D1A /* NullCodable in Frameworks */ = {isa = PBXBuildFile; productRef = 5AB1F18E26435F9600993D1A /* NullCodable */; }; 5AC6459026412F0C00F46137 /* TeamTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AC6458F26412F0C00F46137 /* TeamTableViewCell.swift */; }; 5ACC0F012630CA1400DCF83E /* ErrorMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ACC0F002630CA1400DCF83E /* ErrorMessage.swift */; }; 5ACC0F062630CA2700DCF83E /* Exceptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ACC0F052630CA2700DCF83E /* Exceptions.swift */; }; @@ -134,6 +135,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 5AB1F18F26435F9600993D1A /* NullCodable in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -317,6 +319,9 @@ dependencies = ( ); name = Sluggo; + packageProductDependencies = ( + 5AB1F18E26435F9600993D1A /* NullCodable */, + ); productName = Sluggo; productReference = 7D9963A2261EF3A4002338E7 /* Sluggo.app */; productType = "com.apple.product-type.application"; @@ -388,6 +393,9 @@ Base, ); mainGroup = 7D996399261EF3A4002338E7; + packageReferences = ( + 5AB1F18D26435F9600993D1A /* XCRemoteSwiftPackageReference "NullCodable" */, + ); productRefGroup = 7D9963A3261EF3A4002338E7 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -811,6 +819,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 5AB1F18D26435F9600993D1A /* XCRemoteSwiftPackageReference "NullCodable" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/g-mark/NullCodable.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.1.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 5AB1F18E26435F9600993D1A /* NullCodable */ = { + isa = XCSwiftPackageProductDependency; + package = 5AB1F18D26435F9600993D1A /* XCRemoteSwiftPackageReference "NullCodable" */; + productName = NullCodable; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 7D99639A261EF3A4002338E7 /* Project object */; } diff --git a/Sluggo/Models/Codables/Member.swift b/Sluggo/Models/Codables/Member.swift index b31ccf2..14e4597 100644 --- a/Sluggo/Models/Codables/Member.swift +++ b/Sluggo/Models/Codables/Member.swift @@ -6,7 +6,7 @@ // import Foundation - +import NullCodable struct MemberRecord: Codable{ var id: String @@ -14,8 +14,8 @@ struct MemberRecord: Codable{ var team_id: Int var object_uuid: UUID var role: String - var bio: String? + @NullCodable var bio: String? var created: Date - var activated: Date? - var deactivated: Date? + @NullCodable var activated: Date? + @NullCodable var deactivated: Date? } diff --git a/Sluggo/Models/Codables/PaginatedList.swift b/Sluggo/Models/Codables/PaginatedList.swift index c89f7f5..8351add 100644 --- a/Sluggo/Models/Codables/PaginatedList.swift +++ b/Sluggo/Models/Codables/PaginatedList.swift @@ -6,11 +6,12 @@ // import Foundation +import NullCodable struct PaginatedList : Codable { var count: Int - var next: String? - var previous: String? + @NullCodable var next: String? + @NullCodable var previous: String? let results: [T] } diff --git a/Sluggo/Models/Codables/StatusRecord.swift b/Sluggo/Models/Codables/StatusRecord.swift index d8ae1c7..0e17f8e 100644 --- a/Sluggo/Models/Codables/StatusRecord.swift +++ b/Sluggo/Models/Codables/StatusRecord.swift @@ -6,7 +6,7 @@ // import Foundation - +import NullCodable struct StatusRecord: Codable { var id: Int @@ -14,7 +14,7 @@ struct StatusRecord: Codable { var title: String var color: String var created: Date - var activated: Date? - var deactivated: Date? + @NullCodable var activated: Date? + @NullCodable var deactivated: Date? } diff --git a/Sluggo/Models/Codables/TagRecord.swift b/Sluggo/Models/Codables/TagRecord.swift index 91d590d..3e5a814 100644 --- a/Sluggo/Models/Codables/TagRecord.swift +++ b/Sluggo/Models/Codables/TagRecord.swift @@ -6,14 +6,14 @@ // import Foundation - +import NullCodable struct TagRecord: Codable { var id: Int var object_uuid: UUID var title: String var created: Date - var activated: Date? - var deactivated: Date? + @NullCodable var activated: Date? + @NullCodable var deactivated: Date? } diff --git a/Sluggo/Models/Codables/Team.swift b/Sluggo/Models/Codables/Team.swift index ca972eb..d3d8c30 100644 --- a/Sluggo/Models/Codables/Team.swift +++ b/Sluggo/Models/Codables/Team.swift @@ -6,7 +6,7 @@ // import Foundation - +import NullCodable struct TeamRecord: Codable, Equatable { var id: Int @@ -14,8 +14,8 @@ struct TeamRecord: Codable, Equatable { var object_uuid: UUID var ticket_head: Int var created: Date - var activated: Date? - var deactivated: Date? + @NullCodable var activated: Date? + @NullCodable var deactivated: Date? static func == (lhs: TeamRecord, rhs: TeamRecord) -> Bool { return lhs.object_uuid == rhs.object_uuid diff --git a/Sluggo/Models/Codables/TicketRecord.swift b/Sluggo/Models/Codables/TicketRecord.swift index c389928..7765493 100644 --- a/Sluggo/Models/Codables/TicketRecord.swift +++ b/Sluggo/Models/Codables/TicketRecord.swift @@ -7,30 +7,31 @@ // import Foundation +import NullCodable struct TicketRecord: Codable { var id: Int var ticket_number: Int - var tag_list: [TagRecord]? + @NullCodable var tag_list: [TagRecord]? var object_uuid: UUID - var assigned_user: MemberRecord? - var status: StatusRecord? + @NullCodable var assigned_user: MemberRecord? + @NullCodable var status: StatusRecord? var title: String - var description: String? + @NullCodable var description: String? // var comments? Future Future Implementation - var due_date: Date? + @NullCodable var due_date: Date? var created: Date - var activated: Date? - var deactivated: Date? + @NullCodable var activated: Date? + @NullCodable var deactivated: Date? } struct WriteTicketRecord: Codable{ - var tag_list: [TagRecord]? - var assigned_user: String? - var status: StatusRecord? + @NullCodable var tag_list: [TagRecord]? + @NullCodable var assigned_user: String? + @NullCodable var status: StatusRecord? var title: String - var description: String? - var due_date: Date? + @NullCodable var description: String? + @NullCodable var due_date: Date? } diff --git a/Sluggo/Models/Codables/User.swift b/Sluggo/Models/Codables/User.swift index a843843..9e72725 100644 --- a/Sluggo/Models/Codables/User.swift +++ b/Sluggo/Models/Codables/User.swift @@ -6,20 +6,20 @@ // import Foundation - +import NullCodable struct UserRecord: Codable { var id: Int var email: String - var first_name: String? - var last_name: String? + @NullCodable var first_name: String? + @NullCodable var last_name: String? var username: String } struct AuthRecord: Codable { var pk: Int var email: String - var first_name: String? - var last_name: String? + @NullCodable var first_name: String? + @NullCodable var last_name: String? var username: String } From 1e7a76a14ca45a568caf9a4fc32f4b02a8dd61d6 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 5 May 2021 17:18:52 -0700 Subject: [PATCH 109/224] maybe fixed the problem? --- Sluggo/Models/Codables/TicketRecord.swift | 4 ++-- .../TicketDetailViewController.swift | 2 +- .../TicketListController.swift | 24 ++++++++++++++----- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/Sluggo/Models/Codables/TicketRecord.swift b/Sluggo/Models/Codables/TicketRecord.swift index 7765493..146c830 100644 --- a/Sluggo/Models/Codables/TicketRecord.swift +++ b/Sluggo/Models/Codables/TicketRecord.swift @@ -12,7 +12,7 @@ import NullCodable struct TicketRecord: Codable { var id: Int var ticket_number: Int - @NullCodable var tag_list: [TagRecord]? + var tag_list: [TagRecord] var object_uuid: UUID @NullCodable var assigned_user: MemberRecord? @NullCodable var status: StatusRecord? @@ -28,7 +28,7 @@ struct TicketRecord: Codable { } struct WriteTicketRecord: Codable{ - @NullCodable var tag_list: [TagRecord]? + var tag_list: [TagRecord] @NullCodable var assigned_user: String? @NullCodable var status: StatusRecord? var title: String diff --git a/Sluggo/View Controllers/TicketDetailViewController.swift b/Sluggo/View Controllers/TicketDetailViewController.swift index fc09bba..71163d9 100644 --- a/Sluggo/View Controllers/TicketDetailViewController.swift +++ b/Sluggo/View Controllers/TicketDetailViewController.swift @@ -158,7 +158,7 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate, UIPicker } } else{ // Create Ticket - let ticket = WriteTicketRecord(tag_list: nil, assigned_user: member, status: nil, title: title, description: description, due_date: date) + let ticket = WriteTicketRecord(tag_list: [], assigned_user: member, status: nil, title: title, description: description, due_date: date) let manager = TicketManager(identity) manager.makeTicket(ticket: ticket){ result in switch(result){ diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index d18b438..b9e209b 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -73,7 +73,9 @@ class TicketListController: UITableViewController { } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "Ticket", for: indexPath) as! TicketTableViewCell + cell.loadFromTicketRecord(ticket: tickets[indexPath.row]) if (indexPath.row == tickets.count - 1 && tickets.count < maxNumber && !isFetching) { @@ -92,13 +94,23 @@ class TicketListController: UITableViewController { ticketManager.listTeamTickets(page: page) { result in switch(result) { case .success(let record): - self.tickets += record.results - self.maxNumber = record.count - DispatchQueue.main.async { - self.refreshControl?.endRefreshing() - self.tableView.reloadData() - self.isFetching = false + if (self.maxNumber > record.count) { + // our maxNumber is greater than record.count + // therefore our data is invalid and we should reload the entire thing + self.maxNumber = record.count + self.handleRefreshAction() + } else { + // there is either more data ahead or no new records have been added + // should be fine to keep this the same. + self.tickets += record.results + self.maxNumber = record.count + + DispatchQueue.main.async { + self.refreshControl?.endRefreshing() + self.tableView.reloadData() + self.isFetching = false + } } break; case .failure(let error): From 216a7010afa54c898036941ecf3ad87b602c2633 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 5 May 2021 18:01:02 -0700 Subject: [PATCH 110/224] replace ticket with copy only after all the operations are done on it: --- .../TicketListController.swift | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index b9e209b..2cbc8f1 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -37,7 +37,7 @@ class TicketListController: UITableViewController { } @objc func handleRefreshAction() { - tickets = [] + self.tableView.isUserInteractionEnabled = false self.loadData(page: 1) } @@ -94,23 +94,25 @@ class TicketListController: UITableViewController { ticketManager.listTeamTickets(page: page) { result in switch(result) { case .success(let record): + self.maxNumber = record.count - if (self.maxNumber > record.count) { - // our maxNumber is greater than record.count - // therefore our data is invalid and we should reload the entire thing - self.maxNumber = record.count - self.handleRefreshAction() - } else { - // there is either more data ahead or no new records have been added - // should be fine to keep this the same. - self.tickets += record.results - self.maxNumber = record.count + var ticketsCopy = Array(self.tickets) + // remove all after starting from the beginning of the first element in this page + let pageOffset = (page - 1) * self.identity.pageSize + if (pageOffset < self.tickets.count) { + ticketsCopy.removeSubrange(pageOffset...self.tickets.count-1) + } + + for entry in record.results { + ticketsCopy.append(entry) + } - DispatchQueue.main.async { - self.refreshControl?.endRefreshing() - self.tableView.reloadData() - self.isFetching = false - } + DispatchQueue.main.sync { + self.refreshControl?.endRefreshing() + self.tickets = ticketsCopy + self.tableView.reloadData() + self.isFetching = false + self.tableView.isUserInteractionEnabled = true } break; case .failure(let error): From 31d70a4ed9a5acf97c6acf7d9a8263135a360480 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 5 May 2021 18:28:57 -0700 Subject: [PATCH 111/224] using semaphores in the team view list in order to prevent reloads from the different sections. ticket list not explicitly gauranteed to be atomic --- .../TeamTableViewController.swift | 44 +++++++++++++------ .../TicketListController.swift | 4 +- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/Sluggo/View Controllers/TeamTableViewController.swift b/Sluggo/View Controllers/TeamTableViewController.swift index 031c899..cc0a7bb 100644 --- a/Sluggo/View Controllers/TeamTableViewController.swift +++ b/Sluggo/View Controllers/TeamTableViewController.swift @@ -10,13 +10,16 @@ import UIKit class TeamTableViewController: UITableViewController { var identity: AppIdentity! var completion: ((TeamRecord) -> Void)? - private var isFetching: Bool = false private var maxNumber: Int = 0 + private var fetchingTeams: [TeamRecord] = [] private var teams: [TeamRecord] = [] + private let semaphore = DispatchSemaphore(value: 1) override func viewDidLoad() { self.configureRefreshControl() - self.loadData(page: 1) + DispatchQueue.global().async { + self.handleRefreshAction() + } } func configureRefreshControl() { @@ -24,10 +27,7 @@ class TeamTableViewController: UITableViewController { refreshControl?.addTarget(self, action: #selector(handleRefreshAction), for: .valueChanged) } - @objc func handleRefreshAction() { - teams = [] - self.loadData(page: 1) - } + private func preselectRow() { if (teams.count == 0) { return } @@ -59,25 +59,42 @@ class TeamTableViewController: UITableViewController { return cell } - // MARK: API calls + @objc func handleRefreshAction() { + // enter the critical section + self.semaphore.wait() + self.loadData(page: 1) + } + private func loadData(page: Int) { let teamManager = TeamManager(identity: identity) teamManager.listUserTeams(page: page) { result in switch(result) { case .success(let record): - self.teams += record.results self.maxNumber = record.count - DispatchQueue.main.async { - if (self.teams.count >= self.maxNumber) { + let pageOffset = (page - 1) * self.identity.pageSize + if (pageOffset < self.fetchingTeams.count) { + self.fetchingTeams.removeSubrange(pageOffset...self.fetchingTeams.count-1) + } + + for entry in record.results { + self.fetchingTeams.append(entry) + } + + if (self.fetchingTeams.count >= self.maxNumber) { + DispatchQueue.main.async { self.refreshControl?.endRefreshing() + self.teams = Array(self.fetchingTeams) + self.fetchingTeams.removeAll() self.tableView.reloadData() - self.isFetching = false self.preselectRow() - } else { - self.loadData(page: page + 1) } + self.semaphore.signal() + return + } else { + self.loadData(page: page + 1) } + break; case .failure(let error): DispatchQueue.main.async { @@ -87,5 +104,4 @@ class TeamTableViewController: UITableViewController { } } } - } diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index 2cbc8f1..32ea1e7 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -37,7 +37,6 @@ class TicketListController: UITableViewController { } @objc func handleRefreshAction() { - self.tableView.isUserInteractionEnabled = false self.loadData(page: 1) } @@ -104,7 +103,7 @@ class TicketListController: UITableViewController { } for entry in record.results { - ticketsCopy.append(entry) + ticketsCopy.append(entry) } DispatchQueue.main.sync { @@ -112,7 +111,6 @@ class TicketListController: UITableViewController { self.tickets = ticketsCopy self.tableView.reloadData() self.isFetching = false - self.tableView.isUserInteractionEnabled = true } break; case .failure(let error): From 5d5af28953edb11adc07f4024119e03f178b192f Mon Sep 17 00:00:00 2001 From: tdimhcsleumas <56523092+tdimhcsleumas@users.noreply.github.com> Date: Wed, 5 May 2021 18:31:34 -0700 Subject: [PATCH 112/224] Revert "Smarti84/ticket creation" --- Sluggo.xcodeproj/project.pbxproj | 37 +-- Sluggo/Models/Codables/Member.swift | 10 +- Sluggo/Models/Codables/PaginatedList.swift | 5 +- Sluggo/Models/Codables/StatusRecord.swift | 6 +- Sluggo/Models/Codables/TagRecord.swift | 6 +- Sluggo/Models/Codables/Team.swift | 6 +- Sluggo/Models/Codables/TicketRecord.swift | 24 +- Sluggo/Models/Codables/User.swift | 10 +- Sluggo/Models/Managers/MemberManager.swift | 11 +- Sluggo/Models/Managers/TicketManager.swift | 17 +- Sluggo/Storyboards/Ticket.storyboard | 8 - Sluggo/Storyboards/TicketDetail.storyboard | 161 ----------- .../TeamTableViewController.swift | 44 +-- .../TicketDetailViewController.swift | 263 ------------------ .../TicketListController.swift | 46 +-- 15 files changed, 59 insertions(+), 595 deletions(-) delete mode 100644 Sluggo/Storyboards/TicketDetail.storyboard delete mode 100644 Sluggo/View Controllers/TicketDetailViewController.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index c1e34fc..f4bd048 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 50; objects = { /* Begin PBXBuildFile section */ @@ -20,8 +20,6 @@ 2FEF5DAE262D1C4900434763 /* MemberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DAD262D1C4900434763 /* MemberTests.swift */; }; 3A150A6726365A9A0052934B /* StatusRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A6626365A9A0052934B /* StatusRecord.swift */; }; 3A150A6C26365B670052934B /* TagRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A6B26365B670052934B /* TagRecord.swift */; }; - 3A150A77263CB5F70052934B /* TicketDetail.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3A150A76263CB5F70052934B /* TicketDetail.storyboard */; }; - 3A150A7F263CBA860052934B /* TicketDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A7E263CBA860052934B /* TicketDetailViewController.swift */; }; 5A00A0142639EDEA0085C8C6 /* URLRequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */; }; 5A00A045263A34CC0085C8C6 /* TicketTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A00A044263A34CC0085C8C6 /* TicketTableViewCell.swift */; }; 5A673570263BF2D200C4C778 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A67356F263BF2D200C4C778 /* UIColor.swift */; }; @@ -38,7 +36,6 @@ 5AB1C8F7262FE3520010F85E /* AppIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8F6262FE3520010F85E /* AppIdentity.swift */; }; 5AB1C8FC262FF1690010F85E /* TicketRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8FB262FF1690010F85E /* TicketRecord.swift */; }; 5AB1C909262FFF500010F85E /* UserManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C908262FFF500010F85E /* UserManager.swift */; }; - 5AB1F18F26435F9600993D1A /* NullCodable in Frameworks */ = {isa = PBXBuildFile; productRef = 5AB1F18E26435F9600993D1A /* NullCodable */; }; 5AC6459026412F0C00F46137 /* TeamTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AC6458F26412F0C00F46137 /* TeamTableViewCell.swift */; }; 5ACC0F012630CA1400DCF83E /* ErrorMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ACC0F002630CA1400DCF83E /* ErrorMessage.swift */; }; 5ACC0F062630CA2700DCF83E /* Exceptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ACC0F052630CA2700DCF83E /* Exceptions.swift */; }; @@ -88,8 +85,6 @@ 2FEF5DAD262D1C4900434763 /* MemberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberTests.swift; sourceTree = ""; }; 3A150A6626365A9A0052934B /* StatusRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusRecord.swift; sourceTree = ""; }; 3A150A6B26365B670052934B /* TagRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagRecord.swift; sourceTree = ""; }; - 3A150A76263CB5F70052934B /* TicketDetail.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TicketDetail.storyboard; sourceTree = ""; }; - 3A150A7E263CBA860052934B /* TicketDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketDetailViewController.swift; sourceTree = ""; }; 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLRequestBuilder.swift; sourceTree = ""; }; 5A00A044263A34CC0085C8C6 /* TicketTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketTableViewCell.swift; sourceTree = ""; }; 5A67356F263BF2D200C4C778 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; @@ -135,7 +130,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5AB1F18F26435F9600993D1A /* NullCodable in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -282,7 +276,6 @@ 7DC2A21B262943A70002CEE6 /* Sidebar.storyboard */, 5A9FF88826386D1400378550 /* Ticket.storyboard */, 7DEA17842639E7B5004E5A71 /* LaunchScreen.storyboard */, - 3A150A76263CB5F70052934B /* TicketDetail.storyboard */, ); path = Storyboards; sourceTree = ""; @@ -296,7 +289,6 @@ 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */, 7DC2A22E26294F9E0002CEE6 /* HomeViewController.swift */, 5A9FF88D26386D4700378550 /* TicketListController.swift */, - 3A150A7E263CBA860052934B /* TicketDetailViewController.swift */, 5A9C4C1B2640BC4E00B270AB /* TeamTableViewController.swift */, 5A9C4C202641113D00B270AB /* TeamSelectorContainerViewController.swift */, ); @@ -319,9 +311,6 @@ dependencies = ( ); name = Sluggo; - packageProductDependencies = ( - 5AB1F18E26435F9600993D1A /* NullCodable */, - ); productName = Sluggo; productReference = 7D9963A2261EF3A4002338E7 /* Sluggo.app */; productType = "com.apple.product-type.application"; @@ -393,9 +382,6 @@ Base, ); mainGroup = 7D996399261EF3A4002338E7; - packageReferences = ( - 5AB1F18D26435F9600993D1A /* XCRemoteSwiftPackageReference "NullCodable" */, - ); productRefGroup = 7D9963A3261EF3A4002338E7 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -417,7 +403,6 @@ 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */, 7D9963AD261EF3A4002338E7 /* Root.storyboard in Resources */, 7DEA17852639E7B5004E5A71 /* LaunchScreen.storyboard in Resources */, - 3A150A77263CB5F70052934B /* TicketDetail.storyboard in Resources */, 7DC2A21726293F360002CEE6 /* Home.storyboard in Resources */, 7DC2A21C262943A70002CEE6 /* Sidebar.storyboard in Resources */, ); @@ -447,7 +432,6 @@ 5A9C4C212641113D00B270AB /* TeamSelectorContainerViewController.swift in Sources */, 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */, 5AB1C8FC262FF1690010F85E /* TicketRecord.swift in Sources */, - 3A150A7F263CBA860052934B /* TicketDetailViewController.swift in Sources */, 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */, 5A00A0142639EDEA0085C8C6 /* URLRequestBuilder.swift in Sources */, 2FD70D5D2636144300D13C75 /* LogoutMessage.swift in Sources */, @@ -819,25 +803,6 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ - -/* Begin XCRemoteSwiftPackageReference section */ - 5AB1F18D26435F9600993D1A /* XCRemoteSwiftPackageReference "NullCodable" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/g-mark/NullCodable.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.1.0; - }; - }; -/* End XCRemoteSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - 5AB1F18E26435F9600993D1A /* NullCodable */ = { - isa = XCSwiftPackageProductDependency; - package = 5AB1F18D26435F9600993D1A /* XCRemoteSwiftPackageReference "NullCodable" */; - productName = NullCodable; - }; -/* End XCSwiftPackageProductDependency section */ }; rootObject = 7D99639A261EF3A4002338E7 /* Project object */; } diff --git a/Sluggo/Models/Codables/Member.swift b/Sluggo/Models/Codables/Member.swift index 14e4597..54eb4ca 100644 --- a/Sluggo/Models/Codables/Member.swift +++ b/Sluggo/Models/Codables/Member.swift @@ -6,16 +6,16 @@ // import Foundation -import NullCodable -struct MemberRecord: Codable{ + +struct MemberRecord: Codable { var id: String var owner: UserRecord var team_id: Int var object_uuid: UUID var role: String - @NullCodable var bio: String? + var bio: String? var created: Date - @NullCodable var activated: Date? - @NullCodable var deactivated: Date? + var activated: Date? + var deactivated: Date? } diff --git a/Sluggo/Models/Codables/PaginatedList.swift b/Sluggo/Models/Codables/PaginatedList.swift index 8351add..c89f7f5 100644 --- a/Sluggo/Models/Codables/PaginatedList.swift +++ b/Sluggo/Models/Codables/PaginatedList.swift @@ -6,12 +6,11 @@ // import Foundation -import NullCodable struct PaginatedList : Codable { var count: Int - @NullCodable var next: String? - @NullCodable var previous: String? + var next: String? + var previous: String? let results: [T] } diff --git a/Sluggo/Models/Codables/StatusRecord.swift b/Sluggo/Models/Codables/StatusRecord.swift index 0e17f8e..d8ae1c7 100644 --- a/Sluggo/Models/Codables/StatusRecord.swift +++ b/Sluggo/Models/Codables/StatusRecord.swift @@ -6,7 +6,7 @@ // import Foundation -import NullCodable + struct StatusRecord: Codable { var id: Int @@ -14,7 +14,7 @@ struct StatusRecord: Codable { var title: String var color: String var created: Date - @NullCodable var activated: Date? - @NullCodable var deactivated: Date? + var activated: Date? + var deactivated: Date? } diff --git a/Sluggo/Models/Codables/TagRecord.swift b/Sluggo/Models/Codables/TagRecord.swift index 3e5a814..91d590d 100644 --- a/Sluggo/Models/Codables/TagRecord.swift +++ b/Sluggo/Models/Codables/TagRecord.swift @@ -6,14 +6,14 @@ // import Foundation -import NullCodable + struct TagRecord: Codable { var id: Int var object_uuid: UUID var title: String var created: Date - @NullCodable var activated: Date? - @NullCodable var deactivated: Date? + var activated: Date? + var deactivated: Date? } diff --git a/Sluggo/Models/Codables/Team.swift b/Sluggo/Models/Codables/Team.swift index d3d8c30..ca972eb 100644 --- a/Sluggo/Models/Codables/Team.swift +++ b/Sluggo/Models/Codables/Team.swift @@ -6,7 +6,7 @@ // import Foundation -import NullCodable + struct TeamRecord: Codable, Equatable { var id: Int @@ -14,8 +14,8 @@ struct TeamRecord: Codable, Equatable { var object_uuid: UUID var ticket_head: Int var created: Date - @NullCodable var activated: Date? - @NullCodable var deactivated: Date? + var activated: Date? + var deactivated: Date? static func == (lhs: TeamRecord, rhs: TeamRecord) -> Bool { return lhs.object_uuid == rhs.object_uuid diff --git a/Sluggo/Models/Codables/TicketRecord.swift b/Sluggo/Models/Codables/TicketRecord.swift index 146c830..149d78a 100644 --- a/Sluggo/Models/Codables/TicketRecord.swift +++ b/Sluggo/Models/Codables/TicketRecord.swift @@ -7,31 +7,21 @@ // import Foundation -import NullCodable struct TicketRecord: Codable { var id: Int var ticket_number: Int - var tag_list: [TagRecord] + var tag_list: [TagRecord]? var object_uuid: UUID - @NullCodable var assigned_user: MemberRecord? - @NullCodable var status: StatusRecord? + var assigned_user: MemberRecord? + var status: StatusRecord? var title: String - @NullCodable var description: String? + var description: String? // var comments? Future Future Implementation - @NullCodable var due_date: Date? + var due_date: Date? var created: Date - @NullCodable var activated: Date? - @NullCodable var deactivated: Date? + var activated: Date? + var deactivated: Date? } - -struct WriteTicketRecord: Codable{ - var tag_list: [TagRecord] - @NullCodable var assigned_user: String? - @NullCodable var status: StatusRecord? - var title: String - @NullCodable var description: String? - @NullCodable var due_date: Date? -} diff --git a/Sluggo/Models/Codables/User.swift b/Sluggo/Models/Codables/User.swift index 9e72725..a843843 100644 --- a/Sluggo/Models/Codables/User.swift +++ b/Sluggo/Models/Codables/User.swift @@ -6,20 +6,20 @@ // import Foundation -import NullCodable + struct UserRecord: Codable { var id: Int var email: String - @NullCodable var first_name: String? - @NullCodable var last_name: String? + var first_name: String? + var last_name: String? var username: String } struct AuthRecord: Codable { var pk: Int var email: String - @NullCodable var first_name: String? - @NullCodable var last_name: String? + var first_name: String? + var last_name: String? var username: String } diff --git a/Sluggo/Models/Managers/MemberManager.swift b/Sluggo/Models/Managers/MemberManager.swift index 13eb0ac..556d421 100644 --- a/Sluggo/Models/Managers/MemberManager.swift +++ b/Sluggo/Models/Managers/MemberManager.swift @@ -11,7 +11,7 @@ class MemberManager { static let urlBase = "/members/" private var identity: AppIdentity - init(identity: AppIdentity) { + init(_ identity: AppIdentity) { self.identity = identity } @@ -23,6 +23,15 @@ class MemberManager { return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + MemberManager.urlBase)! } + public func fetchTeamMembers(completionHandler: @escaping(Result) -> Void) -> Void { + + let requestBuilder = URLRequestBuilder(url: makeListUrl()) + .setMethod(method: .GET) + .setIdentity(identity: identity) + + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) + } + public func updateMemberRecord(_ memberRecord: MemberRecord, completionHandler: @escaping(Result) -> Void) -> Void { guard let body = JsonLoader.encode(object: memberRecord) else { diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index 927cf7b..02b7c94 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -32,25 +32,12 @@ class TicketManager { JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } - public func makeTicket(ticket: WriteTicketRecord, completionHandler: @escaping(Result) -> Void)->Void{ + public func updateTicket(_ ticket: TicketRecord, completionHandler: @escaping(Result) -> Void)-> Void { guard let body = JsonLoader.encode(object: ticket) else { - completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize ticket JSON for makeTicket in TicketManager"))) - return - } - let requestBuilder = URLRequestBuilder(url: makeListUrl(page: 1)) - .setMethod(method: .POST) - .setData(data: body) - .setIdentity(identity: self.identity) - - JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) - } - - public func updateTicket(ticket: TicketRecord, completionHandler: @escaping(Result) -> Void)-> Void { - let writeTicket = WriteTicketRecord(tag_list: ticket.tag_list, assigned_user: ticket.assigned_user?.id, status: ticket.status, title: ticket.title, description: ticket.description, due_date: ticket.due_date) - guard let body = JsonLoader.encode(object: writeTicket) else { completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize ticket JSON for updateTicket in TicketManager"))) return } + let requestBuilder = URLRequestBuilder(url: makeDetailUrl(ticket)) .setMethod(method: .PUT) .setData(data: body) diff --git a/Sluggo/Storyboards/Ticket.storyboard b/Sluggo/Storyboards/Ticket.storyboard index c6f994b..4823137 100644 --- a/Sluggo/Storyboards/Ticket.storyboard +++ b/Sluggo/Storyboards/Ticket.storyboard @@ -89,14 +89,6 @@ - - - - - - - - diff --git a/Sluggo/Storyboards/TicketDetail.storyboard b/Sluggo/Storyboards/TicketDetail.storyboard deleted file mode 100644 index b40a8ef..0000000 --- a/Sluggo/Storyboards/TicketDetail.storyboard +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Sluggo/View Controllers/TeamTableViewController.swift b/Sluggo/View Controllers/TeamTableViewController.swift index cc0a7bb..031c899 100644 --- a/Sluggo/View Controllers/TeamTableViewController.swift +++ b/Sluggo/View Controllers/TeamTableViewController.swift @@ -10,16 +10,13 @@ import UIKit class TeamTableViewController: UITableViewController { var identity: AppIdentity! var completion: ((TeamRecord) -> Void)? + private var isFetching: Bool = false private var maxNumber: Int = 0 - private var fetchingTeams: [TeamRecord] = [] private var teams: [TeamRecord] = [] - private let semaphore = DispatchSemaphore(value: 1) override func viewDidLoad() { self.configureRefreshControl() - DispatchQueue.global().async { - self.handleRefreshAction() - } + self.loadData(page: 1) } func configureRefreshControl() { @@ -27,7 +24,10 @@ class TeamTableViewController: UITableViewController { refreshControl?.addTarget(self, action: #selector(handleRefreshAction), for: .valueChanged) } - + @objc func handleRefreshAction() { + teams = [] + self.loadData(page: 1) + } private func preselectRow() { if (teams.count == 0) { return } @@ -59,42 +59,25 @@ class TeamTableViewController: UITableViewController { return cell } - @objc func handleRefreshAction() { - // enter the critical section - self.semaphore.wait() - self.loadData(page: 1) - } - + // MARK: API calls private func loadData(page: Int) { let teamManager = TeamManager(identity: identity) teamManager.listUserTeams(page: page) { result in switch(result) { case .success(let record): + self.teams += record.results self.maxNumber = record.count - let pageOffset = (page - 1) * self.identity.pageSize - if (pageOffset < self.fetchingTeams.count) { - self.fetchingTeams.removeSubrange(pageOffset...self.fetchingTeams.count-1) - } - - for entry in record.results { - self.fetchingTeams.append(entry) - } - - if (self.fetchingTeams.count >= self.maxNumber) { - DispatchQueue.main.async { + DispatchQueue.main.async { + if (self.teams.count >= self.maxNumber) { self.refreshControl?.endRefreshing() - self.teams = Array(self.fetchingTeams) - self.fetchingTeams.removeAll() self.tableView.reloadData() + self.isFetching = false self.preselectRow() + } else { + self.loadData(page: page + 1) } - self.semaphore.signal() - return - } else { - self.loadData(page: page + 1) } - break; case .failure(let error): DispatchQueue.main.async { @@ -104,4 +87,5 @@ class TeamTableViewController: UITableViewController { } } } + } diff --git a/Sluggo/View Controllers/TicketDetailViewController.swift b/Sluggo/View Controllers/TicketDetailViewController.swift deleted file mode 100644 index 71163d9..0000000 --- a/Sluggo/View Controllers/TicketDetailViewController.swift +++ /dev/null @@ -1,263 +0,0 @@ -// -// TicketDetailViewController.swift -// Sluggo -// -// Created by Stephan Martin on 4/30/21. -// - -import UIKit - - - -class TicketDetailViewController: UIViewController, UITextViewDelegate, UIPickerViewDelegate, UIPickerViewDataSource { - - - @IBOutlet weak var ticketTitle: UITextField! - @IBOutlet weak var ticketDescription: UITextView! - @IBOutlet weak var navigationItemDisplay: UINavigationItem! - @IBOutlet weak var assignedUserTextField: UITextField! - @IBOutlet weak var dateTimePicker: UIDatePicker! - @IBOutlet weak var navBar: UINavigationBar! - @IBOutlet weak var ticketTitleTopConstraint: NSLayoutConstraint! - @IBOutlet weak var dueDateSwitch: UISwitch! - @IBOutlet weak var dueDateLabel: UILabel! - - var identity: AppIdentity - var ticket: TicketRecord? - var pickerView: UIPickerView = UIPickerView() - var completion: (() ->Void)? - var editingTicket = false - - var teamMembers: [MemberRecord?] = [nil] - var currentMember: MemberRecord? = nil - - init? (coder: NSCoder, identity: AppIdentity, ticket: TicketRecord?, completion: (() -> Void)?) { - self.identity = identity - self.completion = completion - self.ticket = ticket - super.init(coder: coder) - } - - required init? (coder: NSCoder) { - fatalError("must be initialized with identity") - } - - override func viewDidLoad() { - super.viewDidLoad() - dateTimePicker.isEnabled = dueDateSwitch.isOn - - - let memberManager = MemberManager(identity: self.identity) - memberManager.listTeamMembers(){ result in - switch(result){ - case .success(let record): - self.teamMembers = [nil] - for user in record.results{ - self.teamMembers.append(user) - } - - case .failure(let error): - DispatchQueue.main.async { - let alert = UIAlertController.errorController(error: error) - self.present(alert, animated: true, completion: nil) - } - } - } - - pickerView.dataSource = self - pickerView.delegate = self - createUserPicker() - - ticketDescription.delegate = self - if let passedTicket = ticket { - - ticketTitle.text = passedTicket.title - - if let description = passedTicket.description { - if description.isEmpty { - ticketDescription.text = "Description of ticket" // Placeholder text - ticketDescription.textColor = .lightGray - } else { - ticketDescription.text = description - } - } else { - ticketDescription.text = "Description of ticket" // Placeholder text - ticketDescription.textColor = .lightGray - } - - if let assignedName = passedTicket.assigned_user?.owner.username { - assignedUserTextField.text = assignedName - } - - if let date = passedTicket.due_date { - dateTimePicker.date = date - dateTimePicker.isEnabled = true - } - else{ - dateTimePicker.isHidden = true - dueDateLabel.isHidden = true - } - - navBar.isHidden = true - ticketTitleTopConstraint.constant = 0 - ticketTitle.isUserInteractionEnabled = false - ticketDescription.isUserInteractionEnabled = false - dateTimePicker.isUserInteractionEnabled = false - assignedUserTextField.isUserInteractionEnabled = false - dueDateSwitch.isHidden = true - navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(setToEditMode)) - } - else{ - ticketDescription.text = "Description of ticket" // Placeholder text - ticketDescription.textColor = .lightGray - navigationItemDisplay.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelMode)) - navigationItemDisplay.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(submitTicketMode)) - } - } - - @IBAction func dueDateSwitch(_ sender: UISwitch) { - dateTimePicker.isEnabled = sender.isOn - } - - @objc func cancelMode() { - self.dismiss(animated: true, completion: nil) - } - - @objc func submitTicketMode(){ - let title = ticketTitle.text ?? "Default title (This is an error)" - var description: String? = "" - if ticketDescription.textColor != .lightGray { - description = ticketDescription.text - } - - let date = dueDateSwitch.isOn ? dateTimePicker.date : nil - let member = currentMember?.id - - if(editingTicket){ // Edit Ticket - - ticket!.title = title - ticket!.description = description - ticket!.due_date = date - ticket!.assigned_user = currentMember - - let manager = TicketManager(identity) - manager.updateTicket(ticket: ticket!){ result in - switch(result){ - case .success(_): - DispatchQueue.main.async { - self.noEditingMode() - NotificationCenter.default.post(name: .refreshTrigger, object: self) - - } - case .failure(let error): - DispatchQueue.main.async { - let alert = UIAlertController.errorController(error: error) - self.present(alert, animated: true, completion: nil) - } - } - } - } - else{ // Create Ticket - let ticket = WriteTicketRecord(tag_list: [], assigned_user: member, status: nil, title: title, description: description, due_date: date) - let manager = TicketManager(identity) - manager.makeTicket(ticket: ticket){ result in - switch(result){ - case .success(_): - DispatchQueue.main.async { - self.dismiss(animated: true, completion: self.completion) - } - case .failure(let error): - DispatchQueue.main.async { - let alert = UIAlertController.errorController(error: error) - self.present(alert, animated: true, completion: nil) - } - } - } - } - } - - // MARK: Placeholder text for description - // Whoever decided to code UITextView deserves to be beaten - func textViewDidBeginEditing (_ textView: UITextView) { - if ticketDescription.textColor == .lightGray{ - ticketDescription.text = nil - ticketDescription.textColor = .black - } - } - - func textViewDidEndEditing (_ textView: UITextView) { - if ticketDescription.text.isEmpty || ticketDescription.text == "" { - ticketDescription.textColor = .lightGray - ticketDescription.text = "Description of Ticket" - } - } - - // We understand that these two are bad, but for now, passing parameters to selectors is not very fun. - @objc func setToEditMode(){ - editingTicket = true - navigationItem.rightBarButtonItem = nil - navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(submitTicketMode)) - ticketTitle.isUserInteractionEnabled = true - ticketDescription.isUserInteractionEnabled = true - dateTimePicker.isUserInteractionEnabled = true - assignedUserTextField.isUserInteractionEnabled = true - dueDateSwitch.isHidden = false - dueDateSwitch.isOn = dateTimePicker.isEnabled - dateTimePicker.isHidden = false - dueDateLabel.isHidden = false - } - - func noEditingMode(){ - editingTicket = false - navigationItem.rightBarButtonItem = nil - navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(setToEditMode)) - ticketTitle.isUserInteractionEnabled = false - ticketDescription.isUserInteractionEnabled = false - dateTimePicker.isUserInteractionEnabled = false - assignedUserTextField.isUserInteractionEnabled = false - dueDateSwitch.isHidden = true - dateTimePicker.isHidden = !dateTimePicker.isEnabled - dueDateLabel.isHidden = !dateTimePicker.isEnabled - } - - func createUserPicker() { - //create toolbar - let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 35)) - toolbar.sizeToFit() - toolbar.translatesAutoresizingMaskIntoConstraints = false - - //create bar button - let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(userPicked)) - toolbar.setItems([doneButton], animated: true) - assignedUserTextField.inputAccessoryView = toolbar - - assignedUserTextField.inputView = pickerView - } - - @objc func userPicked(){ - assignedUserTextField.text = currentMember?.owner.username ?? "No Assigned User" - self.view.endEditing(true) - - } - - - - - //MARK: UIPickerView manager - func numberOfComponents(in pickerView: UIPickerView) -> Int { - return 1 - } - - func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { - return teamMembers.count - } - - func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - return teamMembers[row]?.owner.username ?? "No Assigned User" - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - currentMember = teamMembers[row] - } - -} diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index 32ea1e7..785a8e0 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -27,8 +27,6 @@ class TicketListController: UITableViewController { override func viewDidLoad() { configureRefreshControl() loadData(page: 1) - navigationItem.rightBarButtonItem = UIBarButtonItem( barButtonSystemItem: .add, target: self, action: #selector(connectPopUp)) - NotificationCenter.default.addObserver(self, selector: #selector(handleRefreshAction), name: .refreshTrigger, object: nil) } func configureRefreshControl() { @@ -37,6 +35,7 @@ class TicketListController: UITableViewController { } @objc func handleRefreshAction() { + tickets = [] self.loadData(page: 1) } @@ -45,26 +44,7 @@ class TicketListController: UITableViewController { // @stephan this is probably where you'll spawn the detail views once you get going on that. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - - let identity = self.identity - let detailStoryboard = UIStoryboard(name: "TicketDetail", bundle: nil) - if let vc = detailStoryboard.instantiateViewController(identifier: "TicketDetail", creator:{ coder in - return TicketDetailViewController(coder: coder, identity: identity, ticket: self.tickets[indexPath.row], completion: nil) - }) as TicketDetailViewController? { - navigationController?.pushViewController(vc, animated: true) - } - } - - @objc func connectPopUp() { - let identity = self.identity - let detailStoryboard = UIStoryboard(name: "TicketDetail", bundle: nil) - if let vc = detailStoryboard.instantiateViewController(identifier: "TicketDetail", creator:{ coder in - return TicketDetailViewController(coder: coder, identity: identity, ticket: nil, completion: { - self.handleRefreshAction() - }) - }) as TicketDetailViewController? { - self.present(vc, animated: true, completion: nil) - } + print("selected row!") } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { @@ -72,9 +52,7 @@ class TicketListController: UITableViewController { } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "Ticket", for: indexPath) as! TicketTableViewCell - cell.loadFromTicketRecord(ticket: tickets[indexPath.row]) if (indexPath.row == tickets.count - 1 && tickets.count < maxNumber && !isFetching) { @@ -93,22 +71,11 @@ class TicketListController: UITableViewController { ticketManager.listTeamTickets(page: page) { result in switch(result) { case .success(let record): + self.tickets += record.results self.maxNumber = record.count - var ticketsCopy = Array(self.tickets) - // remove all after starting from the beginning of the first element in this page - let pageOffset = (page - 1) * self.identity.pageSize - if (pageOffset < self.tickets.count) { - ticketsCopy.removeSubrange(pageOffset...self.tickets.count-1) - } - - for entry in record.results { - ticketsCopy.append(entry) - } - - DispatchQueue.main.sync { + DispatchQueue.main.async { self.refreshControl?.endRefreshing() - self.tickets = ticketsCopy self.tableView.reloadData() self.isFetching = false } @@ -121,9 +88,4 @@ class TicketListController: UITableViewController { } } } - -} - -extension Notification.Name { - static let refreshTrigger = Notification.Name(rawValue: "SLGRefreshTriggerNotification") } From a7374fe38fd9ae06f0eb089a53b73a36c7915ce0 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 5 May 2021 18:38:30 -0700 Subject: [PATCH 113/224] fixed semaphore overlook --- Sluggo/View Controllers/TeamTableViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sluggo/View Controllers/TeamTableViewController.swift b/Sluggo/View Controllers/TeamTableViewController.swift index cc0a7bb..89d339a 100644 --- a/Sluggo/View Controllers/TeamTableViewController.swift +++ b/Sluggo/View Controllers/TeamTableViewController.swift @@ -97,6 +97,7 @@ class TeamTableViewController: UITableViewController { break; case .failure(let error): + self.semaphore.signal() DispatchQueue.main.async { let alert = UIAlertController.errorController(error: error) self.present(alert, animated: true, completion: nil) From 169bf717aaa0a4ff377aa5ff2da5d9f38336b774 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 5 May 2021 18:49:33 -0700 Subject: [PATCH 114/224] Revert "Merge pull request #15 from Slugbotics/revert-14-smarti84/ticketCreation" This reverts commit 1b5adc911d814eb3dcfd266d32788eb2ff805653, reversing changes made to e324e61be7dc8f7ff048b1b13d71419fe140af82. --- Sluggo.xcodeproj/project.pbxproj | 37 ++- Sluggo/Models/Codables/Member.swift | 10 +- Sluggo/Models/Codables/PaginatedList.swift | 5 +- Sluggo/Models/Codables/StatusRecord.swift | 6 +- Sluggo/Models/Codables/TagRecord.swift | 6 +- Sluggo/Models/Codables/Team.swift | 6 +- Sluggo/Models/Codables/TicketRecord.swift | 24 +- Sluggo/Models/Codables/User.swift | 10 +- Sluggo/Models/Managers/MemberManager.swift | 11 +- Sluggo/Models/Managers/TicketManager.swift | 17 +- Sluggo/Storyboards/Ticket.storyboard | 8 + Sluggo/Storyboards/TicketDetail.storyboard | 161 +++++++++++ .../TeamTableViewController.swift | 44 ++- .../TicketDetailViewController.swift | 263 ++++++++++++++++++ .../TicketListController.swift | 46 ++- 15 files changed, 595 insertions(+), 59 deletions(-) create mode 100644 Sluggo/Storyboards/TicketDetail.storyboard create mode 100644 Sluggo/View Controllers/TicketDetailViewController.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index f4bd048..c1e34fc 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ @@ -20,6 +20,8 @@ 2FEF5DAE262D1C4900434763 /* MemberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DAD262D1C4900434763 /* MemberTests.swift */; }; 3A150A6726365A9A0052934B /* StatusRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A6626365A9A0052934B /* StatusRecord.swift */; }; 3A150A6C26365B670052934B /* TagRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A6B26365B670052934B /* TagRecord.swift */; }; + 3A150A77263CB5F70052934B /* TicketDetail.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3A150A76263CB5F70052934B /* TicketDetail.storyboard */; }; + 3A150A7F263CBA860052934B /* TicketDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A7E263CBA860052934B /* TicketDetailViewController.swift */; }; 5A00A0142639EDEA0085C8C6 /* URLRequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */; }; 5A00A045263A34CC0085C8C6 /* TicketTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A00A044263A34CC0085C8C6 /* TicketTableViewCell.swift */; }; 5A673570263BF2D200C4C778 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A67356F263BF2D200C4C778 /* UIColor.swift */; }; @@ -36,6 +38,7 @@ 5AB1C8F7262FE3520010F85E /* AppIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8F6262FE3520010F85E /* AppIdentity.swift */; }; 5AB1C8FC262FF1690010F85E /* TicketRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C8FB262FF1690010F85E /* TicketRecord.swift */; }; 5AB1C909262FFF500010F85E /* UserManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1C908262FFF500010F85E /* UserManager.swift */; }; + 5AB1F18F26435F9600993D1A /* NullCodable in Frameworks */ = {isa = PBXBuildFile; productRef = 5AB1F18E26435F9600993D1A /* NullCodable */; }; 5AC6459026412F0C00F46137 /* TeamTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AC6458F26412F0C00F46137 /* TeamTableViewCell.swift */; }; 5ACC0F012630CA1400DCF83E /* ErrorMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ACC0F002630CA1400DCF83E /* ErrorMessage.swift */; }; 5ACC0F062630CA2700DCF83E /* Exceptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ACC0F052630CA2700DCF83E /* Exceptions.swift */; }; @@ -85,6 +88,8 @@ 2FEF5DAD262D1C4900434763 /* MemberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberTests.swift; sourceTree = ""; }; 3A150A6626365A9A0052934B /* StatusRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusRecord.swift; sourceTree = ""; }; 3A150A6B26365B670052934B /* TagRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagRecord.swift; sourceTree = ""; }; + 3A150A76263CB5F70052934B /* TicketDetail.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TicketDetail.storyboard; sourceTree = ""; }; + 3A150A7E263CBA860052934B /* TicketDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketDetailViewController.swift; sourceTree = ""; }; 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLRequestBuilder.swift; sourceTree = ""; }; 5A00A044263A34CC0085C8C6 /* TicketTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketTableViewCell.swift; sourceTree = ""; }; 5A67356F263BF2D200C4C778 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; @@ -130,6 +135,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 5AB1F18F26435F9600993D1A /* NullCodable in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -276,6 +282,7 @@ 7DC2A21B262943A70002CEE6 /* Sidebar.storyboard */, 5A9FF88826386D1400378550 /* Ticket.storyboard */, 7DEA17842639E7B5004E5A71 /* LaunchScreen.storyboard */, + 3A150A76263CB5F70052934B /* TicketDetail.storyboard */, ); path = Storyboards; sourceTree = ""; @@ -289,6 +296,7 @@ 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */, 7DC2A22E26294F9E0002CEE6 /* HomeViewController.swift */, 5A9FF88D26386D4700378550 /* TicketListController.swift */, + 3A150A7E263CBA860052934B /* TicketDetailViewController.swift */, 5A9C4C1B2640BC4E00B270AB /* TeamTableViewController.swift */, 5A9C4C202641113D00B270AB /* TeamSelectorContainerViewController.swift */, ); @@ -311,6 +319,9 @@ dependencies = ( ); name = Sluggo; + packageProductDependencies = ( + 5AB1F18E26435F9600993D1A /* NullCodable */, + ); productName = Sluggo; productReference = 7D9963A2261EF3A4002338E7 /* Sluggo.app */; productType = "com.apple.product-type.application"; @@ -382,6 +393,9 @@ Base, ); mainGroup = 7D996399261EF3A4002338E7; + packageReferences = ( + 5AB1F18D26435F9600993D1A /* XCRemoteSwiftPackageReference "NullCodable" */, + ); productRefGroup = 7D9963A3261EF3A4002338E7 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -403,6 +417,7 @@ 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */, 7D9963AD261EF3A4002338E7 /* Root.storyboard in Resources */, 7DEA17852639E7B5004E5A71 /* LaunchScreen.storyboard in Resources */, + 3A150A77263CB5F70052934B /* TicketDetail.storyboard in Resources */, 7DC2A21726293F360002CEE6 /* Home.storyboard in Resources */, 7DC2A21C262943A70002CEE6 /* Sidebar.storyboard in Resources */, ); @@ -432,6 +447,7 @@ 5A9C4C212641113D00B270AB /* TeamSelectorContainerViewController.swift in Sources */, 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */, 5AB1C8FC262FF1690010F85E /* TicketRecord.swift in Sources */, + 3A150A7F263CBA860052934B /* TicketDetailViewController.swift in Sources */, 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */, 5A00A0142639EDEA0085C8C6 /* URLRequestBuilder.swift in Sources */, 2FD70D5D2636144300D13C75 /* LogoutMessage.swift in Sources */, @@ -803,6 +819,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 5AB1F18D26435F9600993D1A /* XCRemoteSwiftPackageReference "NullCodable" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/g-mark/NullCodable.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.1.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 5AB1F18E26435F9600993D1A /* NullCodable */ = { + isa = XCSwiftPackageProductDependency; + package = 5AB1F18D26435F9600993D1A /* XCRemoteSwiftPackageReference "NullCodable" */; + productName = NullCodable; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 7D99639A261EF3A4002338E7 /* Project object */; } diff --git a/Sluggo/Models/Codables/Member.swift b/Sluggo/Models/Codables/Member.swift index 54eb4ca..14e4597 100644 --- a/Sluggo/Models/Codables/Member.swift +++ b/Sluggo/Models/Codables/Member.swift @@ -6,16 +6,16 @@ // import Foundation +import NullCodable - -struct MemberRecord: Codable { +struct MemberRecord: Codable{ var id: String var owner: UserRecord var team_id: Int var object_uuid: UUID var role: String - var bio: String? + @NullCodable var bio: String? var created: Date - var activated: Date? - var deactivated: Date? + @NullCodable var activated: Date? + @NullCodable var deactivated: Date? } diff --git a/Sluggo/Models/Codables/PaginatedList.swift b/Sluggo/Models/Codables/PaginatedList.swift index c89f7f5..8351add 100644 --- a/Sluggo/Models/Codables/PaginatedList.swift +++ b/Sluggo/Models/Codables/PaginatedList.swift @@ -6,11 +6,12 @@ // import Foundation +import NullCodable struct PaginatedList : Codable { var count: Int - var next: String? - var previous: String? + @NullCodable var next: String? + @NullCodable var previous: String? let results: [T] } diff --git a/Sluggo/Models/Codables/StatusRecord.swift b/Sluggo/Models/Codables/StatusRecord.swift index d8ae1c7..0e17f8e 100644 --- a/Sluggo/Models/Codables/StatusRecord.swift +++ b/Sluggo/Models/Codables/StatusRecord.swift @@ -6,7 +6,7 @@ // import Foundation - +import NullCodable struct StatusRecord: Codable { var id: Int @@ -14,7 +14,7 @@ struct StatusRecord: Codable { var title: String var color: String var created: Date - var activated: Date? - var deactivated: Date? + @NullCodable var activated: Date? + @NullCodable var deactivated: Date? } diff --git a/Sluggo/Models/Codables/TagRecord.swift b/Sluggo/Models/Codables/TagRecord.swift index 91d590d..3e5a814 100644 --- a/Sluggo/Models/Codables/TagRecord.swift +++ b/Sluggo/Models/Codables/TagRecord.swift @@ -6,14 +6,14 @@ // import Foundation - +import NullCodable struct TagRecord: Codable { var id: Int var object_uuid: UUID var title: String var created: Date - var activated: Date? - var deactivated: Date? + @NullCodable var activated: Date? + @NullCodable var deactivated: Date? } diff --git a/Sluggo/Models/Codables/Team.swift b/Sluggo/Models/Codables/Team.swift index ca972eb..d3d8c30 100644 --- a/Sluggo/Models/Codables/Team.swift +++ b/Sluggo/Models/Codables/Team.swift @@ -6,7 +6,7 @@ // import Foundation - +import NullCodable struct TeamRecord: Codable, Equatable { var id: Int @@ -14,8 +14,8 @@ struct TeamRecord: Codable, Equatable { var object_uuid: UUID var ticket_head: Int var created: Date - var activated: Date? - var deactivated: Date? + @NullCodable var activated: Date? + @NullCodable var deactivated: Date? static func == (lhs: TeamRecord, rhs: TeamRecord) -> Bool { return lhs.object_uuid == rhs.object_uuid diff --git a/Sluggo/Models/Codables/TicketRecord.swift b/Sluggo/Models/Codables/TicketRecord.swift index 149d78a..146c830 100644 --- a/Sluggo/Models/Codables/TicketRecord.swift +++ b/Sluggo/Models/Codables/TicketRecord.swift @@ -7,21 +7,31 @@ // import Foundation +import NullCodable struct TicketRecord: Codable { var id: Int var ticket_number: Int - var tag_list: [TagRecord]? + var tag_list: [TagRecord] var object_uuid: UUID - var assigned_user: MemberRecord? - var status: StatusRecord? + @NullCodable var assigned_user: MemberRecord? + @NullCodable var status: StatusRecord? var title: String - var description: String? + @NullCodable var description: String? // var comments? Future Future Implementation - var due_date: Date? + @NullCodable var due_date: Date? var created: Date - var activated: Date? - var deactivated: Date? + @NullCodable var activated: Date? + @NullCodable var deactivated: Date? } + +struct WriteTicketRecord: Codable{ + var tag_list: [TagRecord] + @NullCodable var assigned_user: String? + @NullCodable var status: StatusRecord? + var title: String + @NullCodable var description: String? + @NullCodable var due_date: Date? +} diff --git a/Sluggo/Models/Codables/User.swift b/Sluggo/Models/Codables/User.swift index a843843..9e72725 100644 --- a/Sluggo/Models/Codables/User.swift +++ b/Sluggo/Models/Codables/User.swift @@ -6,20 +6,20 @@ // import Foundation - +import NullCodable struct UserRecord: Codable { var id: Int var email: String - var first_name: String? - var last_name: String? + @NullCodable var first_name: String? + @NullCodable var last_name: String? var username: String } struct AuthRecord: Codable { var pk: Int var email: String - var first_name: String? - var last_name: String? + @NullCodable var first_name: String? + @NullCodable var last_name: String? var username: String } diff --git a/Sluggo/Models/Managers/MemberManager.swift b/Sluggo/Models/Managers/MemberManager.swift index 556d421..13eb0ac 100644 --- a/Sluggo/Models/Managers/MemberManager.swift +++ b/Sluggo/Models/Managers/MemberManager.swift @@ -11,7 +11,7 @@ class MemberManager { static let urlBase = "/members/" private var identity: AppIdentity - init(_ identity: AppIdentity) { + init(identity: AppIdentity) { self.identity = identity } @@ -23,15 +23,6 @@ class MemberManager { return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + MemberManager.urlBase)! } - public func fetchTeamMembers(completionHandler: @escaping(Result) -> Void) -> Void { - - let requestBuilder = URLRequestBuilder(url: makeListUrl()) - .setMethod(method: .GET) - .setIdentity(identity: identity) - - JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) - } - public func updateMemberRecord(_ memberRecord: MemberRecord, completionHandler: @escaping(Result) -> Void) -> Void { guard let body = JsonLoader.encode(object: memberRecord) else { diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index 02b7c94..927cf7b 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -32,12 +32,25 @@ class TicketManager { JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } - public func updateTicket(_ ticket: TicketRecord, completionHandler: @escaping(Result) -> Void)-> Void { + public func makeTicket(ticket: WriteTicketRecord, completionHandler: @escaping(Result) -> Void)->Void{ guard let body = JsonLoader.encode(object: ticket) else { - completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize ticket JSON for updateTicket in TicketManager"))) + completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize ticket JSON for makeTicket in TicketManager"))) return } + let requestBuilder = URLRequestBuilder(url: makeListUrl(page: 1)) + .setMethod(method: .POST) + .setData(data: body) + .setIdentity(identity: self.identity) + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) + } + + public func updateTicket(ticket: TicketRecord, completionHandler: @escaping(Result) -> Void)-> Void { + let writeTicket = WriteTicketRecord(tag_list: ticket.tag_list, assigned_user: ticket.assigned_user?.id, status: ticket.status, title: ticket.title, description: ticket.description, due_date: ticket.due_date) + guard let body = JsonLoader.encode(object: writeTicket) else { + completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize ticket JSON for updateTicket in TicketManager"))) + return + } let requestBuilder = URLRequestBuilder(url: makeDetailUrl(ticket)) .setMethod(method: .PUT) .setData(data: body) diff --git a/Sluggo/Storyboards/Ticket.storyboard b/Sluggo/Storyboards/Ticket.storyboard index 4823137..c6f994b 100644 --- a/Sluggo/Storyboards/Ticket.storyboard +++ b/Sluggo/Storyboards/Ticket.storyboard @@ -89,6 +89,14 @@ + + + + + + + + diff --git a/Sluggo/Storyboards/TicketDetail.storyboard b/Sluggo/Storyboards/TicketDetail.storyboard new file mode 100644 index 0000000..b40a8ef --- /dev/null +++ b/Sluggo/Storyboards/TicketDetail.storyboard @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sluggo/View Controllers/TeamTableViewController.swift b/Sluggo/View Controllers/TeamTableViewController.swift index 2f36de9..89d339a 100644 --- a/Sluggo/View Controllers/TeamTableViewController.swift +++ b/Sluggo/View Controllers/TeamTableViewController.swift @@ -10,13 +10,16 @@ import UIKit class TeamTableViewController: UITableViewController { var identity: AppIdentity! var completion: ((TeamRecord) -> Void)? - private var isFetching: Bool = false private var maxNumber: Int = 0 + private var fetchingTeams: [TeamRecord] = [] private var teams: [TeamRecord] = [] + private let semaphore = DispatchSemaphore(value: 1) override func viewDidLoad() { self.configureRefreshControl() - self.loadData(page: 1) + DispatchQueue.global().async { + self.handleRefreshAction() + } } func configureRefreshControl() { @@ -24,10 +27,7 @@ class TeamTableViewController: UITableViewController { refreshControl?.addTarget(self, action: #selector(handleRefreshAction), for: .valueChanged) } - @objc func handleRefreshAction() { - teams = [] - self.loadData(page: 1) - } + private func preselectRow() { if (teams.count == 0) { return } @@ -59,25 +59,42 @@ class TeamTableViewController: UITableViewController { return cell } - // MARK: API calls + @objc func handleRefreshAction() { + // enter the critical section + self.semaphore.wait() + self.loadData(page: 1) + } + private func loadData(page: Int) { let teamManager = TeamManager(identity: identity) teamManager.listUserTeams(page: page) { result in switch(result) { case .success(let record): - self.teams += record.results self.maxNumber = record.count - DispatchQueue.main.async { - if (self.teams.count >= self.maxNumber) { + let pageOffset = (page - 1) * self.identity.pageSize + if (pageOffset < self.fetchingTeams.count) { + self.fetchingTeams.removeSubrange(pageOffset...self.fetchingTeams.count-1) + } + + for entry in record.results { + self.fetchingTeams.append(entry) + } + + if (self.fetchingTeams.count >= self.maxNumber) { + DispatchQueue.main.async { self.refreshControl?.endRefreshing() + self.teams = Array(self.fetchingTeams) + self.fetchingTeams.removeAll() self.tableView.reloadData() - self.isFetching = false self.preselectRow() - } else { - self.loadData(page: page + 1) } + self.semaphore.signal() + return + } else { + self.loadData(page: page + 1) } + break; case .failure(let error): self.semaphore.signal() @@ -88,5 +105,4 @@ class TeamTableViewController: UITableViewController { } } } - } diff --git a/Sluggo/View Controllers/TicketDetailViewController.swift b/Sluggo/View Controllers/TicketDetailViewController.swift new file mode 100644 index 0000000..71163d9 --- /dev/null +++ b/Sluggo/View Controllers/TicketDetailViewController.swift @@ -0,0 +1,263 @@ +// +// TicketDetailViewController.swift +// Sluggo +// +// Created by Stephan Martin on 4/30/21. +// + +import UIKit + + + +class TicketDetailViewController: UIViewController, UITextViewDelegate, UIPickerViewDelegate, UIPickerViewDataSource { + + + @IBOutlet weak var ticketTitle: UITextField! + @IBOutlet weak var ticketDescription: UITextView! + @IBOutlet weak var navigationItemDisplay: UINavigationItem! + @IBOutlet weak var assignedUserTextField: UITextField! + @IBOutlet weak var dateTimePicker: UIDatePicker! + @IBOutlet weak var navBar: UINavigationBar! + @IBOutlet weak var ticketTitleTopConstraint: NSLayoutConstraint! + @IBOutlet weak var dueDateSwitch: UISwitch! + @IBOutlet weak var dueDateLabel: UILabel! + + var identity: AppIdentity + var ticket: TicketRecord? + var pickerView: UIPickerView = UIPickerView() + var completion: (() ->Void)? + var editingTicket = false + + var teamMembers: [MemberRecord?] = [nil] + var currentMember: MemberRecord? = nil + + init? (coder: NSCoder, identity: AppIdentity, ticket: TicketRecord?, completion: (() -> Void)?) { + self.identity = identity + self.completion = completion + self.ticket = ticket + super.init(coder: coder) + } + + required init? (coder: NSCoder) { + fatalError("must be initialized with identity") + } + + override func viewDidLoad() { + super.viewDidLoad() + dateTimePicker.isEnabled = dueDateSwitch.isOn + + + let memberManager = MemberManager(identity: self.identity) + memberManager.listTeamMembers(){ result in + switch(result){ + case .success(let record): + self.teamMembers = [nil] + for user in record.results{ + self.teamMembers.append(user) + } + + case .failure(let error): + DispatchQueue.main.async { + let alert = UIAlertController.errorController(error: error) + self.present(alert, animated: true, completion: nil) + } + } + } + + pickerView.dataSource = self + pickerView.delegate = self + createUserPicker() + + ticketDescription.delegate = self + if let passedTicket = ticket { + + ticketTitle.text = passedTicket.title + + if let description = passedTicket.description { + if description.isEmpty { + ticketDescription.text = "Description of ticket" // Placeholder text + ticketDescription.textColor = .lightGray + } else { + ticketDescription.text = description + } + } else { + ticketDescription.text = "Description of ticket" // Placeholder text + ticketDescription.textColor = .lightGray + } + + if let assignedName = passedTicket.assigned_user?.owner.username { + assignedUserTextField.text = assignedName + } + + if let date = passedTicket.due_date { + dateTimePicker.date = date + dateTimePicker.isEnabled = true + } + else{ + dateTimePicker.isHidden = true + dueDateLabel.isHidden = true + } + + navBar.isHidden = true + ticketTitleTopConstraint.constant = 0 + ticketTitle.isUserInteractionEnabled = false + ticketDescription.isUserInteractionEnabled = false + dateTimePicker.isUserInteractionEnabled = false + assignedUserTextField.isUserInteractionEnabled = false + dueDateSwitch.isHidden = true + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(setToEditMode)) + } + else{ + ticketDescription.text = "Description of ticket" // Placeholder text + ticketDescription.textColor = .lightGray + navigationItemDisplay.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelMode)) + navigationItemDisplay.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(submitTicketMode)) + } + } + + @IBAction func dueDateSwitch(_ sender: UISwitch) { + dateTimePicker.isEnabled = sender.isOn + } + + @objc func cancelMode() { + self.dismiss(animated: true, completion: nil) + } + + @objc func submitTicketMode(){ + let title = ticketTitle.text ?? "Default title (This is an error)" + var description: String? = "" + if ticketDescription.textColor != .lightGray { + description = ticketDescription.text + } + + let date = dueDateSwitch.isOn ? dateTimePicker.date : nil + let member = currentMember?.id + + if(editingTicket){ // Edit Ticket + + ticket!.title = title + ticket!.description = description + ticket!.due_date = date + ticket!.assigned_user = currentMember + + let manager = TicketManager(identity) + manager.updateTicket(ticket: ticket!){ result in + switch(result){ + case .success(_): + DispatchQueue.main.async { + self.noEditingMode() + NotificationCenter.default.post(name: .refreshTrigger, object: self) + + } + case .failure(let error): + DispatchQueue.main.async { + let alert = UIAlertController.errorController(error: error) + self.present(alert, animated: true, completion: nil) + } + } + } + } + else{ // Create Ticket + let ticket = WriteTicketRecord(tag_list: [], assigned_user: member, status: nil, title: title, description: description, due_date: date) + let manager = TicketManager(identity) + manager.makeTicket(ticket: ticket){ result in + switch(result){ + case .success(_): + DispatchQueue.main.async { + self.dismiss(animated: true, completion: self.completion) + } + case .failure(let error): + DispatchQueue.main.async { + let alert = UIAlertController.errorController(error: error) + self.present(alert, animated: true, completion: nil) + } + } + } + } + } + + // MARK: Placeholder text for description + // Whoever decided to code UITextView deserves to be beaten + func textViewDidBeginEditing (_ textView: UITextView) { + if ticketDescription.textColor == .lightGray{ + ticketDescription.text = nil + ticketDescription.textColor = .black + } + } + + func textViewDidEndEditing (_ textView: UITextView) { + if ticketDescription.text.isEmpty || ticketDescription.text == "" { + ticketDescription.textColor = .lightGray + ticketDescription.text = "Description of Ticket" + } + } + + // We understand that these two are bad, but for now, passing parameters to selectors is not very fun. + @objc func setToEditMode(){ + editingTicket = true + navigationItem.rightBarButtonItem = nil + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(submitTicketMode)) + ticketTitle.isUserInteractionEnabled = true + ticketDescription.isUserInteractionEnabled = true + dateTimePicker.isUserInteractionEnabled = true + assignedUserTextField.isUserInteractionEnabled = true + dueDateSwitch.isHidden = false + dueDateSwitch.isOn = dateTimePicker.isEnabled + dateTimePicker.isHidden = false + dueDateLabel.isHidden = false + } + + func noEditingMode(){ + editingTicket = false + navigationItem.rightBarButtonItem = nil + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(setToEditMode)) + ticketTitle.isUserInteractionEnabled = false + ticketDescription.isUserInteractionEnabled = false + dateTimePicker.isUserInteractionEnabled = false + assignedUserTextField.isUserInteractionEnabled = false + dueDateSwitch.isHidden = true + dateTimePicker.isHidden = !dateTimePicker.isEnabled + dueDateLabel.isHidden = !dateTimePicker.isEnabled + } + + func createUserPicker() { + //create toolbar + let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 35)) + toolbar.sizeToFit() + toolbar.translatesAutoresizingMaskIntoConstraints = false + + //create bar button + let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(userPicked)) + toolbar.setItems([doneButton], animated: true) + assignedUserTextField.inputAccessoryView = toolbar + + assignedUserTextField.inputView = pickerView + } + + @objc func userPicked(){ + assignedUserTextField.text = currentMember?.owner.username ?? "No Assigned User" + self.view.endEditing(true) + + } + + + + + //MARK: UIPickerView manager + func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + + func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return teamMembers.count + } + + func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + return teamMembers[row]?.owner.username ?? "No Assigned User" + } + + func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + currentMember = teamMembers[row] + } + +} diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index 785a8e0..32ea1e7 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -27,6 +27,8 @@ class TicketListController: UITableViewController { override func viewDidLoad() { configureRefreshControl() loadData(page: 1) + navigationItem.rightBarButtonItem = UIBarButtonItem( barButtonSystemItem: .add, target: self, action: #selector(connectPopUp)) + NotificationCenter.default.addObserver(self, selector: #selector(handleRefreshAction), name: .refreshTrigger, object: nil) } func configureRefreshControl() { @@ -35,7 +37,6 @@ class TicketListController: UITableViewController { } @objc func handleRefreshAction() { - tickets = [] self.loadData(page: 1) } @@ -44,7 +45,26 @@ class TicketListController: UITableViewController { // @stephan this is probably where you'll spawn the detail views once you get going on that. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - print("selected row!") + + let identity = self.identity + let detailStoryboard = UIStoryboard(name: "TicketDetail", bundle: nil) + if let vc = detailStoryboard.instantiateViewController(identifier: "TicketDetail", creator:{ coder in + return TicketDetailViewController(coder: coder, identity: identity, ticket: self.tickets[indexPath.row], completion: nil) + }) as TicketDetailViewController? { + navigationController?.pushViewController(vc, animated: true) + } + } + + @objc func connectPopUp() { + let identity = self.identity + let detailStoryboard = UIStoryboard(name: "TicketDetail", bundle: nil) + if let vc = detailStoryboard.instantiateViewController(identifier: "TicketDetail", creator:{ coder in + return TicketDetailViewController(coder: coder, identity: identity, ticket: nil, completion: { + self.handleRefreshAction() + }) + }) as TicketDetailViewController? { + self.present(vc, animated: true, completion: nil) + } } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { @@ -52,7 +72,9 @@ class TicketListController: UITableViewController { } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "Ticket", for: indexPath) as! TicketTableViewCell + cell.loadFromTicketRecord(ticket: tickets[indexPath.row]) if (indexPath.row == tickets.count - 1 && tickets.count < maxNumber && !isFetching) { @@ -71,11 +93,22 @@ class TicketListController: UITableViewController { ticketManager.listTeamTickets(page: page) { result in switch(result) { case .success(let record): - self.tickets += record.results self.maxNumber = record.count - DispatchQueue.main.async { + var ticketsCopy = Array(self.tickets) + // remove all after starting from the beginning of the first element in this page + let pageOffset = (page - 1) * self.identity.pageSize + if (pageOffset < self.tickets.count) { + ticketsCopy.removeSubrange(pageOffset...self.tickets.count-1) + } + + for entry in record.results { + ticketsCopy.append(entry) + } + + DispatchQueue.main.sync { self.refreshControl?.endRefreshing() + self.tickets = ticketsCopy self.tableView.reloadData() self.isFetching = false } @@ -88,4 +121,9 @@ class TicketListController: UITableViewController { } } } + +} + +extension Notification.Name { + static let refreshTrigger = Notification.Name(rawValue: "SLGRefreshTriggerNotification") } From 9248de88a0ce93ac0d55dc9f3e84069f7c73e1f3 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 5 May 2021 18:57:34 -0700 Subject: [PATCH 115/224] go back to using bools --- .../View Controllers/TeamTableViewController.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Sluggo/View Controllers/TeamTableViewController.swift b/Sluggo/View Controllers/TeamTableViewController.swift index 89d339a..5fee17d 100644 --- a/Sluggo/View Controllers/TeamTableViewController.swift +++ b/Sluggo/View Controllers/TeamTableViewController.swift @@ -13,13 +13,11 @@ class TeamTableViewController: UITableViewController { private var maxNumber: Int = 0 private var fetchingTeams: [TeamRecord] = [] private var teams: [TeamRecord] = [] - private let semaphore = DispatchSemaphore(value: 1) + private var isFetching = false override func viewDidLoad() { self.configureRefreshControl() - DispatchQueue.global().async { - self.handleRefreshAction() - } + self.handleRefreshAction() } func configureRefreshControl() { @@ -61,7 +59,10 @@ class TeamTableViewController: UITableViewController { @objc func handleRefreshAction() { // enter the critical section - self.semaphore.wait() + // do not wait + if (isFetching) { return } + + isFetching = true self.loadData(page: 1) } @@ -87,9 +88,9 @@ class TeamTableViewController: UITableViewController { self.teams = Array(self.fetchingTeams) self.fetchingTeams.removeAll() self.tableView.reloadData() + self.isFetching = false self.preselectRow() } - self.semaphore.signal() return } else { self.loadData(page: page + 1) @@ -97,7 +98,6 @@ class TeamTableViewController: UITableViewController { break; case .failure(let error): - self.semaphore.signal() DispatchQueue.main.async { let alert = UIAlertController.errorController(error: error) self.present(alert, animated: true, completion: nil) From 2e88cb16e5709f274622ceb34864aa34c36dc4ad Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Wed, 5 May 2021 19:13:10 -0700 Subject: [PATCH 116/224] Untested pinned ticket API wrapper --- Sluggo.xcodeproj/project.pbxproj | 12 ++++ Sluggo/Models/Codables/PinnedTicket.swift | 16 +++++ .../Codables/WritePinnedTicketRecord.swift | 12 ++++ .../Models/Managers/PinnedTicketManager.swift | 61 +++++++++++++++++++ .../TicketListController.swift | 13 ++-- 5 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 Sluggo/Models/Codables/PinnedTicket.swift create mode 100644 Sluggo/Models/Codables/WritePinnedTicketRecord.swift create mode 100644 Sluggo/Models/Managers/PinnedTicketManager.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index d58e6f1..869134a 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -40,6 +40,7 @@ 7D1DA6AE262B82C200E7C12D /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */; }; 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */; }; 7D23834B264119050091C7E8 /* HomeTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D23834A264119050091C7E8 /* HomeTableViewController.swift */; }; + 7D53BE6926433D6600400D0E /* PinnedTicketManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D53BE6826433D6600400D0E /* PinnedTicketManager.swift */; }; 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */; }; 7D9963AD261EF3A4002338E7 /* Root.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7D9963AB261EF3A4002338E7 /* Root.storyboard */; }; 7D9963B2261EF3A5002338E7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7D9963B1261EF3A5002338E7 /* Assets.xcassets */; }; @@ -49,6 +50,8 @@ 7DC2A21C262943A70002CEE6 /* Sidebar.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DC2A21B262943A70002CEE6 /* Sidebar.storyboard */; }; 7DC2A2222629447D0002CEE6 /* SluggoSidebarTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC2A2212629447D0002CEE6 /* SluggoSidebarTableViewController.swift */; }; 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */; }; + 7DC54AE526437AB3000C8300 /* PinnedTicket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC54AE426437AB3000C8300 /* PinnedTicket.swift */; }; + 7DC54AE7264382C9000C8300 /* WritePinnedTicketRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC54AE6264382C9000C8300 /* WritePinnedTicketRecord.swift */; }; 7DEA17852639E7B5004E5A71 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DEA17842639E7B5004E5A71 /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ @@ -103,6 +106,7 @@ 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RootViewController.swift; path = "Sluggo/View Controllers/RootViewController.swift"; sourceTree = SOURCE_ROOT; }; 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarData.swift; sourceTree = ""; }; 7D23834A264119050091C7E8 /* HomeTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HomeTableViewController.swift; path = "Sluggo/View Controllers/HomeTableViewController.swift"; sourceTree = SOURCE_ROOT; }; + 7D53BE6826433D6600400D0E /* PinnedTicketManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedTicketManager.swift; sourceTree = ""; }; 7D9963A2261EF3A4002338E7 /* Sluggo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sluggo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7D9963AC261EF3A4002338E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Root.storyboard; sourceTree = ""; }; @@ -118,6 +122,8 @@ 7DC2A21B262943A70002CEE6 /* Sidebar.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Sidebar.storyboard; sourceTree = ""; }; 7DC2A2212629447D0002CEE6 /* SluggoSidebarTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SluggoSidebarTableViewController.swift; sourceTree = ""; }; 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SluggoSidebarContainerViewController.swift; sourceTree = ""; }; + 7DC54AE426437AB3000C8300 /* PinnedTicket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedTicket.swift; sourceTree = ""; }; + 7DC54AE6264382C9000C8300 /* WritePinnedTicketRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WritePinnedTicketRecord.swift; sourceTree = ""; }; 7DEA17842639E7B5004E5A71 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXFileReference section */ @@ -190,6 +196,8 @@ 2FD70D5C2636144300D13C75 /* LogoutMessage.swift */, 3A150A6626365A9A0052934B /* StatusRecord.swift */, 3A150A6B26365B670052934B /* TagRecord.swift */, + 7DC54AE426437AB3000C8300 /* PinnedTicket.swift */, + 7DC54AE6264382C9000C8300 /* WritePinnedTicketRecord.swift */, ); path = Codables; sourceTree = ""; @@ -199,6 +207,7 @@ children = ( 5AB1C8DC262FE2DD0010F85E /* TeamManager.swift */, 5AB1C8E4262FE2E60010F85E /* TicketManager.swift */, + 7D53BE6826433D6600400D0E /* PinnedTicketManager.swift */, 5AB1C8E9262FE2EF0010F85E /* MemberManager.swift */, 5AB1C8EE262FE3210010F85E /* TagManager.swift */, 5AB1C908262FFF500010F85E /* UserManager.swift */, @@ -427,9 +436,11 @@ 5AB1C8FC262FF1690010F85E /* TicketRecord.swift in Sources */, 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */, 5A00A0142639EDEA0085C8C6 /* URLRequestBuilder.swift in Sources */, + 7DC54AE7264382C9000C8300 /* WritePinnedTicketRecord.swift in Sources */, 2FD70D5D2636144300D13C75 /* LogoutMessage.swift in Sources */, 5AB1C8EA262FE2EF0010F85E /* MemberManager.swift in Sources */, 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */, + 7DC54AE526437AB3000C8300 /* PinnedTicket.swift in Sources */, 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */, 051AAB0B2630FD6500BCB9C7 /* Constants.swift in Sources */, 2F3F66E2262DD3BA0040404C /* JsonLoader.swift in Sources */, @@ -440,6 +451,7 @@ 052258702636100B00B49CE4 /* LaunchViewController.swift in Sources */, 7DC2A2222629447D0002CEE6 /* SluggoSidebarTableViewController.swift in Sources */, 3A150A6726365A9A0052934B /* StatusRecord.swift in Sources */, + 7D53BE6926433D6600400D0E /* PinnedTicketManager.swift in Sources */, 5ACC0F062630CA2700DCF83E /* Exceptions.swift in Sources */, 5AB1C8F7262FE3520010F85E /* AppIdentity.swift in Sources */, 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */, diff --git a/Sluggo/Models/Codables/PinnedTicket.swift b/Sluggo/Models/Codables/PinnedTicket.swift new file mode 100644 index 0000000..156378a --- /dev/null +++ b/Sluggo/Models/Codables/PinnedTicket.swift @@ -0,0 +1,16 @@ +// +// PinnedTicket.swift +// Sluggo +// +// Created by Isaac Trimble-Pederson on 5/5/21. +// + +import Foundation + +struct PinnedTicket: Codable { + let id: String // Primary key + let object_uuid: String + let created: Date + let pinned: Date + let ticket: TicketRecord +} diff --git a/Sluggo/Models/Codables/WritePinnedTicketRecord.swift b/Sluggo/Models/Codables/WritePinnedTicketRecord.swift new file mode 100644 index 0000000..7d522ec --- /dev/null +++ b/Sluggo/Models/Codables/WritePinnedTicketRecord.swift @@ -0,0 +1,12 @@ +// +// WritePinnedTicketRecord.swift +// Sluggo +// +// Created by Isaac Trimble-Pederson on 5/5/21. +// + +import Foundation + +struct WritePinnedTicketRecord: Codable { + let ticketID: Int +} diff --git a/Sluggo/Models/Managers/PinnedTicketManager.swift b/Sluggo/Models/Managers/PinnedTicketManager.swift new file mode 100644 index 0000000..9b09ae5 --- /dev/null +++ b/Sluggo/Models/Managers/PinnedTicketManager.swift @@ -0,0 +1,61 @@ +// +// PinnedTicketManager.swift +// Sluggo +// +// Created by Isaac Trimble-Pederson on 5/5/21. +// + +import Foundation + +class PinnedTicketManager { + static let urlBase = "/pinned/" + private var identity: AppIdentity + private var member: MemberRecord + + init(identity: AppIdentity, member: MemberRecord) { + self.identity = identity + self.member = member + } + + private func makeListURL() -> URL { + return URL(string: identity.baseAddress + TeamManager.urlBase + String(identity.team!.id) + MemberManager.urlBase + PinnedTicketManager.urlBase)!; + } + + private func makeDetailURL(desiredID: String) -> URL { + return URL(string: makeListURL().absoluteString + "/\(desiredID)/")! + } + + public func fetchPinnedTickets(completionHandler: @escaping(Result<[PinnedTicket], Error>) -> Void) -> Void { + let requestBuilder = URLRequestBuilder(url: makeListURL()) + .setMethod(method: .GET) + .setIdentity(identity: identity) + + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) + } + + public func postPinnedTicket(member: MemberRecord, ticket: TicketRecord, completionHandler: @escaping(Result) -> Void) -> Void { + + let writeRecord = WritePinnedTicketRecord(ticketID: ticket.id) + + guard let body = JsonLoader.encode(object: writeRecord) else { + completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize write pinned ticket record in PinnedTicketManager"))) + return + } + + let requestBuilder = URLRequestBuilder(url: makeListURL()) + .setMethod(method: .POST) + .setData(data: body) + .setIdentity(identity: identity) + + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) + } + + public func deletePinnedTicket(member: MemberRecord, pinned: PinnedTicket, completionHandler: @escaping(Result) -> Void) -> Void { + + let requestBuilder = URLRequestBuilder(url: makeDetailURL(desiredID: pinned.id)) + .setMethod(method: .DELETE) + .setIdentity(identity: identity) + + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) + } +} diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index 785a8e0..8d39078 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -11,7 +11,8 @@ class TicketListController: UITableViewController { var identity: AppIdentity var maxNumber: Int = 0 - var tickets: [TicketRecord] = [] + var pinnedTickets: [TicketRecord] = [] + var assignedTickets: [TicketRecord] = [] var isFetching: Bool = false init? (coder: NSCoder, identity: AppIdentity) { @@ -35,7 +36,7 @@ class TicketListController: UITableViewController { } @objc func handleRefreshAction() { - tickets = [] + pinnedTickets = [] self.loadData(page: 1) } @@ -48,14 +49,14 @@ class TicketListController: UITableViewController { } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return self.tickets.count + return self.pinnedTickets.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Ticket", for: indexPath) as! TicketTableViewCell - cell.loadFromTicketRecord(ticket: tickets[indexPath.row]) + cell.loadFromTicketRecord(ticket: pinnedTickets[indexPath.row]) - if (indexPath.row == tickets.count - 1 && tickets.count < maxNumber && !isFetching) { + if (indexPath.row == pinnedTickets.count - 1 && pinnedTickets.count < maxNumber && !isFetching) { DispatchQueue.main.async { self.loadData(page: ((indexPath.row + 1) / self.identity.pageSize) + 1) } @@ -71,7 +72,7 @@ class TicketListController: UITableViewController { ticketManager.listTeamTickets(page: page) { result in switch(result) { case .success(let record): - self.tickets += record.results + self.pinnedTickets += record.results self.maxNumber = record.count DispatchQueue.main.async { From 86cf4b8593220a44d83393629a75874214e913d3 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Wed, 5 May 2021 19:27:11 -0700 Subject: [PATCH 117/224] Style improvements for Homepage Tickets --- Sluggo/Storyboards/Home.storyboard | 14 +++++++------- .../View Controllers/HomeTableViewController.swift | 13 ++++++++++++- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/Sluggo/Storyboards/Home.storyboard b/Sluggo/Storyboards/Home.storyboard index c66ebdd..f6eef1f 100644 --- a/Sluggo/Storyboards/Home.storyboard +++ b/Sluggo/Storyboards/Home.storyboard @@ -35,35 +35,35 @@ - + - + - + - + - + diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index 44342df..42deb37 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -51,7 +51,7 @@ class HomeTableViewController: UITableViewController { override func numberOfSections(in tableView: UITableView) -> Int { // #warning Incomplete implementation, return the number of sections - return 1 + return 2 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { @@ -68,6 +68,17 @@ class HomeTableViewController: UITableViewController { return cell } + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + switch section { + case 0: + return "Assigned to You" + case 1: + return "Pinned Tickets" + default: + return "Error!" + } + } + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if let _ = self.tableView(self.tableView, cellForRowAt: indexPath) as? TicketTableViewCell { // Present error From c1bc16d6764f664dd18190256ac9cb37746f8da3 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Wed, 5 May 2021 22:51:37 -0700 Subject: [PATCH 118/224] Nonworking changes towards getting member record --- Sluggo/Models/Managers/MemberManager.swift | 25 ++++++++++++ .../HomeTableViewController.swift | 38 +++++++++++++++---- .../LoginViewController.swift | 1 + 3 files changed, 56 insertions(+), 8 deletions(-) diff --git a/Sluggo/Models/Managers/MemberManager.swift b/Sluggo/Models/Managers/MemberManager.swift index 556d421..2f35d7d 100644 --- a/Sluggo/Models/Managers/MemberManager.swift +++ b/Sluggo/Models/Managers/MemberManager.swift @@ -6,6 +6,7 @@ // import Foundation +import CryptoKit class MemberManager { static let urlBase = "/members/" @@ -56,4 +57,28 @@ class MemberManager { JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } + + public func getMemberRecord(user: UserRecord, identity: AppIdentity, completionHandler: @escaping(Result) -> Void) -> Void { + // MARK: MD5 hashing convenience + func MD5String(for str: String) -> String { + let digest = Insecure.MD5.hash(data: str.data(using: .utf8)!) + + return digest.map { + String(format: "%02hhx", $0) + }.joined() + } + + // Calculate the member PK + let teamHash = MD5String(for: String(identity.team!.id)) + let userHash = MD5String(for: user.username) + + let memberPk = teamHash.appending(userHash) + + // Execute request + let request = URLRequestBuilder(url: URL(string: makeListUrl().absoluteString + "/\(memberPk)/")!) + .setMethod(method: .GET) + .setIdentity(identity: identity) + + JsonLoader.executeCodableRequest(request: request.getRequest(), completionHandler: completionHandler) + } } diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index 42deb37..c9c50c8 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -9,6 +9,7 @@ import UIKit class HomeTableViewController: UITableViewController { var identity: AppIdentity! + var member: MemberRecord! var tickets: [TicketRecord] = [] // Injection for identity @@ -24,19 +25,40 @@ class HomeTableViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() - // Load the tickets - let manager = TicketManager(identity) - manager.listTeamTickets(page: 1) { result in + // Get the member record + let memberManager = MemberManager(identity) + memberManager.getMemberRecord(user: identity.authenticatedUser!, identity: identity) { result in switch(result) { - case .success(let list): + case .success(let member): DispatchQueue.main.sync { - self.tickets = list.results + self.member = member self.tableView.reloadData() } - break; + break case .failure(let error): - UIAlertController.createAndPresentError(vc: self, error: error, completion: nil) - print("FAILURE") + DispatchQueue.main.async { + UIAlertController.createAndPresentError(vc: self, error: error, completion: nil) + } + } + } + + // Load the tickets + let manager = TicketManager(identity) + DispatchQueue.global(qos: .userInitiated).async { + manager.listTeamTickets(page: 1) { result in + switch(result) { + case .success(let list): + DispatchQueue.main.sync { + self.tickets = list.results + self.tableView.reloadData() + } + break; + case .failure(let error): + DispatchQueue.main.sync { + UIAlertController.createAndPresentError(vc: self, error: error, completion: nil) + print("FAILURE") + } + } } } diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index dc72139..0c84385 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -75,6 +75,7 @@ class LoginViewController: UIViewController { // Save to identity self.identity.token = record.key + let userManager = UserManager(identity: self.identity) // Persistence // This is hacky but better than nothing From 69754212f64714495dd9e538e77253b44271b227 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Wed, 5 May 2021 23:50:11 -0700 Subject: [PATCH 119/224] First (scuffed) attempt at pinned tickets --- Sluggo/Models/AppIdentity.swift | 4 +- Sluggo/Models/Codables/PinnedTicket.swift | 1 - Sluggo/Models/JsonLoader.swift | 1 + Sluggo/Models/Managers/MemberManager.swift | 4 +- .../Models/Managers/PinnedTicketManager.swift | 4 +- Sluggo/Models/Managers/UserManager.swift | 8 ++ .../HomeTableViewController.swift | 87 ++++++++++++------- .../LoginViewController.swift | 16 +++- 8 files changed, 87 insertions(+), 38 deletions(-) diff --git a/Sluggo/Models/AppIdentity.swift b/Sluggo/Models/AppIdentity.swift index aa5e922..c926185 100644 --- a/Sluggo/Models/AppIdentity.swift +++ b/Sluggo/Models/AppIdentity.swift @@ -9,7 +9,7 @@ import Foundation class AppIdentity: Codable { - private var _authenticatedUser: UserRecord? + private var _authenticatedUser: AuthRecord? private var _team: TeamRecord? private var _token: String? private var _pageSize = 10 @@ -17,7 +17,7 @@ class AppIdentity: Codable { private var persist: Bool = false // MARK: Computed Properties - var authenticatedUser: UserRecord? { + var authenticatedUser: AuthRecord? { set(newUser) { _authenticatedUser = newUser enqueueSave() diff --git a/Sluggo/Models/Codables/PinnedTicket.swift b/Sluggo/Models/Codables/PinnedTicket.swift index 156378a..82151d4 100644 --- a/Sluggo/Models/Codables/PinnedTicket.swift +++ b/Sluggo/Models/Codables/PinnedTicket.swift @@ -10,7 +10,6 @@ import Foundation struct PinnedTicket: Codable { let id: String // Primary key let object_uuid: String - let created: Date let pinned: Date let ticket: TicketRecord } diff --git a/Sluggo/Models/JsonLoader.swift b/Sluggo/Models/JsonLoader.swift index cc29e22..fe2f50e 100644 --- a/Sluggo/Models/JsonLoader.swift +++ b/Sluggo/Models/JsonLoader.swift @@ -50,6 +50,7 @@ class JsonLoader { if (resp.statusCode <= 299 && resp.statusCode >= 200) { if let fetchedData = data { guard let record: T = JsonLoader.decode(data: fetchedData) else { + print(String(data: fetchedData, encoding: .utf8) ?? "Failed to print returned values") completionHandler(.failure(RESTException.failedRequest(message: "Failure to decode retrieved model in JsonLoader Codable Request"))) return; } diff --git a/Sluggo/Models/Managers/MemberManager.swift b/Sluggo/Models/Managers/MemberManager.swift index 8342d6e..22becfb 100644 --- a/Sluggo/Models/Managers/MemberManager.swift +++ b/Sluggo/Models/Managers/MemberManager.swift @@ -49,7 +49,7 @@ class MemberManager { JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } - public func getMemberRecord(user: UserRecord, identity: AppIdentity, completionHandler: @escaping(Result) -> Void) -> Void { + public func getMemberRecord(user: AuthRecord, identity: AppIdentity, completionHandler: @escaping(Result) -> Void) -> Void { // MARK: MD5 hashing convenience func MD5String(for str: String) -> String { let digest = Insecure.MD5.hash(data: str.data(using: .utf8)!) @@ -66,7 +66,7 @@ class MemberManager { let memberPk = teamHash.appending(userHash) // Execute request - let request = URLRequestBuilder(url: URL(string: makeListUrl().absoluteString + "/\(memberPk)/")!) + let request = URLRequestBuilder(url: URL(string: makeListUrl().absoluteString + "\(memberPk)/")!) .setMethod(method: .GET) .setIdentity(identity: identity) diff --git a/Sluggo/Models/Managers/PinnedTicketManager.swift b/Sluggo/Models/Managers/PinnedTicketManager.swift index 9b09ae5..3e00583 100644 --- a/Sluggo/Models/Managers/PinnedTicketManager.swift +++ b/Sluggo/Models/Managers/PinnedTicketManager.swift @@ -8,7 +8,7 @@ import Foundation class PinnedTicketManager { - static let urlBase = "/pinned/" + static let urlBase = "/pinned_tickets/" private var identity: AppIdentity private var member: MemberRecord @@ -18,7 +18,7 @@ class PinnedTicketManager { } private func makeListURL() -> URL { - return URL(string: identity.baseAddress + TeamManager.urlBase + String(identity.team!.id) + MemberManager.urlBase + PinnedTicketManager.urlBase)!; + return URL(string: identity.baseAddress + TeamManager.urlBase + String(identity.team!.id) + MemberManager.urlBase + member.id + PinnedTicketManager.urlBase)!; } private func makeDetailURL(desiredID: String) -> URL { diff --git a/Sluggo/Models/Managers/UserManager.swift b/Sluggo/Models/Managers/UserManager.swift index 62e5885..055d8b4 100644 --- a/Sluggo/Models/Managers/UserManager.swift +++ b/Sluggo/Models/Managers/UserManager.swift @@ -44,4 +44,12 @@ class UserManager { JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } + + public func getRecord(identity: AppIdentity, completionHandler: @escaping(Result) -> Void) -> Void { + let requestBuilder = URLRequestBuilder(url: URL(string: identity.baseAddress + "auth/user/")!) + .setMethod(method: .GET) + .setIdentity(identity: identity) + + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) + } } diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index ce99834..d9dd0a6 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -10,7 +10,8 @@ import UIKit class HomeTableViewController: UITableViewController { var identity: AppIdentity! var member: MemberRecord! - var tickets: [TicketRecord] = [] + var pinnedTickets: [PinnedTicket] = [] + var assignedTickets: [TicketRecord] = [] // Injection for identity init? (coder: NSCoder, identity: AppIdentity) { @@ -25,14 +26,28 @@ class HomeTableViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() - // Get the member record + // Fetch items and begin populating the table view + loadMember() { + self.loadAssignedTickets() { + self.loadPinnedTickets(completionHandler: nil) + } + } + + // Uncomment the following line to preserve selection between presentations + // self.clearsSelectionOnViewWillAppear = false + + // Uncomment the following line to display an Edit button in the navigation bar for this view controller. + // self.navigationItem.rightBarButtonItem = self.editButtonItem + } + + func loadMember(completionHandler: (() -> Void)?) { let memberManager = MemberManager(identity: identity) memberManager.getMemberRecord(user: identity.authenticatedUser!, identity: identity) { result in switch(result) { case .success(let member): DispatchQueue.main.sync { self.member = member - self.tableView.reloadData() + completionHandler?() } break case .failure(let error): @@ -41,32 +56,30 @@ class HomeTableViewController: UITableViewController { } } } - - // Load the tickets - let manager = TicketManager(identity) - DispatchQueue.global(qos: .userInitiated).async { - manager.listTeamTickets(page: 1) { result in - switch(result) { - case .success(let list): - DispatchQueue.main.sync { - self.tickets = list.results - self.tableView.reloadData() - } - break; - case .failure(let error): - DispatchQueue.main.sync { - UIAlertController.createAndPresentError(vc: self, error: error, completion: nil) - print("FAILURE") - } + } + + func loadAssignedTickets(completionHandler: (() -> Void)?) { + // TODO + completionHandler?() + } + + func loadPinnedTickets(completionHandler: (() -> Void)?) { + let pinnedTicketsManager = PinnedTicketManager(identity: self.identity, member: self.member) + pinnedTicketsManager.fetchPinnedTickets() { result in + switch(result) { + case .success(let pinnedTickets): + DispatchQueue.main.sync { + self.pinnedTickets = pinnedTickets + self.tableView.reloadData() + } + completionHandler?() + break + case .failure(let error): + DispatchQueue.main.sync { + UIAlertController.createAndPresentError(vc: self, error: error, completion: nil) } } } - - // Uncomment the following line to preserve selection between presentations - // self.clearsSelectionOnViewWillAppear = false - - // Uncomment the following line to display an Edit button in the navigation bar for this view controller. - // self.navigationItem.rightBarButtonItem = self.editButtonItem } // MARK: - Table view data source @@ -78,14 +91,28 @@ class HomeTableViewController: UITableViewController { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // #warning Incomplete implementation, return the number of rows - return tickets.count + switch(section) { + case 0: + return self.assignedTickets.count + case 1: + return self.pinnedTickets.count + default: + fatalError("Invalid section count queried") + } } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Ticket", for: indexPath) as! TicketTableViewCell - let record = tickets[indexPath.row] - - cell.loadFromTicketRecord(ticket: record) + switch(indexPath.section) { + case 0: + cell.loadFromTicketRecord(ticket: assignedTickets[indexPath.row]) + break + case 1: + cell.loadFromTicketRecord(ticket: pinnedTickets[indexPath.row].ticket) + break + default: + fatalError("Accessed section outside of scope, should never occur") + } return cell } diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index ea52e49..2b244bc 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -71,7 +71,21 @@ class LoginViewController: UIViewController { // Save to identity self.identity.token = record.key - let userManager = UserManager(identity: self.identity) + + // Wait for user record to also be fetched + userManager.getRecord(identity: self.identity) { result in + switch(result) { + case .success(let userRecord): + self.identity.authenticatedUser = userRecord + break + case .failure(let error): + print("FAILURE!") + DispatchQueue.main.sync { + UIAlertController.createAndPresentError(vc: self, error: error, completion: nil) + } + } + } + if(self.identity.authenticatedUser == nil) { return }; // avoid segueing out if we didn't fully successfully log in // Segue out of VC DispatchQueue.main.async { From b82afeb6aacb46d01711af3babc24ee476349e8b Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Thu, 6 May 2021 00:00:44 -0700 Subject: [PATCH 120/224] Placeholders --- Sluggo/Storyboards/Home.storyboard | 69 ++++++++++++------- .../HomeTableViewController.swift | 23 ++++--- 2 files changed, 60 insertions(+), 32 deletions(-) diff --git a/Sluggo/Storyboards/Home.storyboard b/Sluggo/Storyboards/Home.storyboard index f6eef1f..e9d194c 100644 --- a/Sluggo/Storyboards/Home.storyboard +++ b/Sluggo/Storyboards/Home.storyboard @@ -40,29 +40,50 @@ - - + + + + + + + + + + + + + + + + + - + - - - - - - + + + + + - - - + + + diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index d9dd0a6..c605982 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -86,35 +86,40 @@ class HomeTableViewController: UITableViewController { override func numberOfSections(in tableView: UITableView) -> Int { // #warning Incomplete implementation, return the number of sections - return 2 + return 3 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // #warning Incomplete implementation, return the number of rows switch(section) { case 0: - return self.assignedTickets.count + return 1 case 1: return self.pinnedTickets.count + case 2: + return 1 default: fatalError("Invalid section count queried") } } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "Ticket", for: indexPath) as! TicketTableViewCell switch(indexPath.section) { case 0: - cell.loadFromTicketRecord(ticket: assignedTickets[indexPath.row]) - break + let cell = tableView.dequeueReusableCell(withIdentifier: "PlaceholderCell")! + cell.textLabel?.text = "Not yet implemented." + return cell case 1: + let cell = tableView.dequeueReusableCell(withIdentifier: "TicketCell", for: indexPath) as! TicketTableViewCell cell.loadFromTicketRecord(ticket: pinnedTickets[indexPath.row].ticket) - break + return cell + case 2: + let cell = tableView.dequeueReusableCell(withIdentifier: "PlaceholderCell")! + cell.textLabel?.text = "Not yet implemented." + return cell default: fatalError("Accessed section outside of scope, should never occur") } - - return cell } override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { @@ -123,6 +128,8 @@ class HomeTableViewController: UITableViewController { return "Assigned to You" case 1: return "Pinned Tickets" + case 2: + return "Your Tags" default: return "Error!" } From 23149c42e0cc4a97aed983f1279bc723667c9994 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Thu, 6 May 2021 00:02:47 -0700 Subject: [PATCH 121/224] Fix login issues --- .../LoginViewController.swift | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index 2b244bc..490628e 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -73,19 +73,22 @@ class LoginViewController: UIViewController { self.identity.token = record.key // Wait for user record to also be fetched - userManager.getRecord(identity: self.identity) { result in - switch(result) { - case .success(let userRecord): - self.identity.authenticatedUser = userRecord - break - case .failure(let error): - print("FAILURE!") - DispatchQueue.main.sync { - UIAlertController.createAndPresentError(vc: self, error: error, completion: nil) + // Don't ask me why this needs to be in another dispatch, it just fails otherwise. + DispatchQueue.global(qos: .userInitiated).sync { + userManager.getRecord(identity: self.identity) { result in + switch(result) { + case .success(let userRecord): + self.identity.authenticatedUser = userRecord + break + case .failure(let error): + print("FAILURE!") + DispatchQueue.main.sync { + UIAlertController.createAndPresentError(vc: self, error: error, completion: nil) + } } } + if(self.identity.authenticatedUser == nil) { return }; // avoid segueing out if we didn't fully successfully log in } - if(self.identity.authenticatedUser == nil) { return }; // avoid segueing out if we didn't fully successfully log in // Segue out of VC DispatchQueue.main.async { From fc50451f3b3b4fa5e451fc3f8b8b2ac45c927ec1 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Thu, 6 May 2021 00:09:51 -0700 Subject: [PATCH 122/224] Reduce magic numbers --- .../HomeTableViewController.swift | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index c605982..7879967 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -7,6 +7,12 @@ import UIKit +enum HomepageCategories: Int { + case assigned = 0 + case pinned = 1 + case tags = 2 +} + class HomeTableViewController: UITableViewController { var identity: AppIdentity! var member: MemberRecord! @@ -92,11 +98,11 @@ class HomeTableViewController: UITableViewController { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // #warning Incomplete implementation, return the number of rows switch(section) { - case 0: + case HomepageCategories.assigned.rawValue: return 1 - case 1: + case HomepageCategories.pinned.rawValue: return self.pinnedTickets.count - case 2: + case HomepageCategories.tags.rawValue: return 1 default: fatalError("Invalid section count queried") @@ -105,15 +111,15 @@ class HomeTableViewController: UITableViewController { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { switch(indexPath.section) { - case 0: + case HomepageCategories.assigned.rawValue: let cell = tableView.dequeueReusableCell(withIdentifier: "PlaceholderCell")! cell.textLabel?.text = "Not yet implemented." return cell - case 1: + case HomepageCategories.pinned.rawValue: let cell = tableView.dequeueReusableCell(withIdentifier: "TicketCell", for: indexPath) as! TicketTableViewCell cell.loadFromTicketRecord(ticket: pinnedTickets[indexPath.row].ticket) return cell - case 2: + case HomepageCategories.tags.rawValue: let cell = tableView.dequeueReusableCell(withIdentifier: "PlaceholderCell")! cell.textLabel?.text = "Not yet implemented." return cell @@ -124,11 +130,11 @@ class HomeTableViewController: UITableViewController { override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { switch section { - case 0: + case HomepageCategories.assigned.rawValue: return "Assigned to You" - case 1: + case HomepageCategories.pinned.rawValue: return "Pinned Tickets" - case 2: + case HomepageCategories.tags.rawValue: return "Your Tags" default: return "Error!" From aae8633a42ee4e976e5c8368347f3a83d471d4fa Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Thu, 6 May 2021 00:11:36 -0700 Subject: [PATCH 123/224] Remove extraneous / unused calls --- Sluggo/View Controllers/HomeTableViewController.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index 7879967..28fe555 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -17,7 +17,6 @@ class HomeTableViewController: UITableViewController { var identity: AppIdentity! var member: MemberRecord! var pinnedTickets: [PinnedTicket] = [] - var assignedTickets: [TicketRecord] = [] // Injection for identity init? (coder: NSCoder, identity: AppIdentity) { @@ -34,9 +33,7 @@ class HomeTableViewController: UITableViewController { // Fetch items and begin populating the table view loadMember() { - self.loadAssignedTickets() { - self.loadPinnedTickets(completionHandler: nil) - } + self.loadPinnedTickets(completionHandler: nil) } // Uncomment the following line to preserve selection between presentations From 4306b4a05325d641e4d97a74f222defd44d68712 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Thu, 6 May 2021 00:28:17 -0700 Subject: [PATCH 124/224] Refresh control --- Sluggo/Storyboards/Home.storyboard | 11 +++++++++++ .../HomeTableViewController.swift | 16 ++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/Sluggo/Storyboards/Home.storyboard b/Sluggo/Storyboards/Home.storyboard index e9d194c..ffe311a 100644 --- a/Sluggo/Storyboards/Home.storyboard +++ b/Sluggo/Storyboards/Home.storyboard @@ -129,6 +129,17 @@ + + + + + + + + + + + diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index 28fe555..50052a4 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -35,6 +35,9 @@ class HomeTableViewController: UITableViewController { loadMember() { self.loadPinnedTickets(completionHandler: nil) } + + // Setup refresh control + self.refreshControl?.addTarget(self, action: #selector(refreshContent), for: .valueChanged) // Uncomment the following line to preserve selection between presentations // self.clearsSelectionOnViewWillAppear = false @@ -84,6 +87,19 @@ class HomeTableViewController: UITableViewController { } } } + + @objc func refreshContent() { + if(self.member == nil) { + self.refreshControl?.endRefreshing() + return; + } + + self.loadPinnedTickets(completionHandler: { + DispatchQueue.main.async { + self.refreshControl?.endRefreshing() + } + }) + } // MARK: - Table view data source From ba05fc02ae7f52f0600af962926a5798716c36f6 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Thu, 6 May 2021 00:36:36 -0700 Subject: [PATCH 125/224] Present tickets screen --- Sluggo/Storyboards/Home.storyboard | 39 +++++++++++++------ .../HomeTableViewController.swift | 27 ++++++++----- 2 files changed, 46 insertions(+), 20 deletions(-) diff --git a/Sluggo/Storyboards/Home.storyboard b/Sluggo/Storyboards/Home.storyboard index ffe311a..ca7792b 100644 --- a/Sluggo/Storyboards/Home.storyboard +++ b/Sluggo/Storyboards/Home.storyboard @@ -24,7 +24,7 @@ - + @@ -35,20 +35,20 @@ - + - + - + - + - + - + - + @@ -120,6 +120,10 @@ + + + + @@ -145,7 +149,20 @@ + + + + + + + + + + + + + diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index 50052a4..01071ff 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -154,17 +154,26 @@ class HomeTableViewController: UITableViewController { } } - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - if let _ = self.tableView(self.tableView, cellForRowAt: indexPath) as? TicketTableViewCell { - // Present error - let error = Exception.runtimeError(message: "Opening ticket details from Home not yet implemented!") - UIAlertController.createAndPresentError(vc: self, error: error) { action in - // Deselect row once alert acknowledged - tableView.deselectRow(at: indexPath, animated: true) - } +// override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { +// if let _ = self.tableView(self.tableView, cellForRowAt: indexPath) as? TicketTableViewCell { +// // Present error +// let error = Exception.runtimeError(message: "Opening ticket details from Home not yet implemented!") +// UIAlertController.createAndPresentError(vc: self, error: error) { action in +// // Deselect row once alert acknowledged +// tableView.deselectRow(at: indexPath, animated: true) +// } +// } +// } + + @IBSegueAction func gotoTicketDetail(_ coder: NSCoder) -> TicketDetailViewController? { + let selectedPath = tableView.indexPathForSelectedRow + switch(selectedPath!.section) { + case HomepageCategories.pinned.rawValue: + return TicketDetailViewController(coder: coder, identity: self.identity, ticket: pinnedTickets[selectedPath!.row].ticket, completion: nil) + default: + fatalError("Nothing should be selectable from unimplemented categories!") } } - /* // Override to support conditional editing of the table view. override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { From 6a42c26e5843745d7b62508cdd58984b1a55c441 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Thu, 6 May 2021 00:43:06 -0700 Subject: [PATCH 126/224] Work around rendering issue on Ticket Details by swapping to small nabber --- Sluggo/Storyboards/Home.storyboard | 18 +++++++++--------- .../HomeTableViewController.swift | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Sluggo/Storyboards/Home.storyboard b/Sluggo/Storyboards/Home.storyboard index ca7792b..b59d559 100644 --- a/Sluggo/Storyboards/Home.storyboard +++ b/Sluggo/Storyboards/Home.storyboard @@ -15,8 +15,8 @@ - - + + @@ -62,29 +62,29 @@ - + - + - + - + @@ -132,7 +132,7 @@ - + diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index 01071ff..ad6eaba 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -38,7 +38,7 @@ class HomeTableViewController: UITableViewController { // Setup refresh control self.refreshControl?.addTarget(self, action: #selector(refreshContent), for: .valueChanged) - + // Uncomment the following line to preserve selection between presentations // self.clearsSelectionOnViewWillAppear = false From bc83b6d320880a8e14baffd33a3e58c8623e7886 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Thu, 6 May 2021 12:56:59 -0700 Subject: [PATCH 127/224] Remove unnecessary extra method on UserManager --- Sluggo/Models/Managers/UserManager.swift | 8 -------- Sluggo/View Controllers/LoginViewController.swift | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/Sluggo/Models/Managers/UserManager.swift b/Sluggo/Models/Managers/UserManager.swift index 055d8b4..62e5885 100644 --- a/Sluggo/Models/Managers/UserManager.swift +++ b/Sluggo/Models/Managers/UserManager.swift @@ -44,12 +44,4 @@ class UserManager { JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } - - public func getRecord(identity: AppIdentity, completionHandler: @escaping(Result) -> Void) -> Void { - let requestBuilder = URLRequestBuilder(url: URL(string: identity.baseAddress + "auth/user/")!) - .setMethod(method: .GET) - .setIdentity(identity: identity) - - JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) - } } diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index 490628e..fb0b6f0 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -75,7 +75,7 @@ class LoginViewController: UIViewController { // Wait for user record to also be fetched // Don't ask me why this needs to be in another dispatch, it just fails otherwise. DispatchQueue.global(qos: .userInitiated).sync { - userManager.getRecord(identity: self.identity) { result in + userManager.getUser() { result in switch(result) { case .success(let userRecord): self.identity.authenticatedUser = userRecord From abdf2ccd88467ef9e422c9acc314d9a5a6cf9e7b Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Thu, 6 May 2021 13:01:09 -0700 Subject: [PATCH 128/224] Swap to login dismissal in success case, avoid nil check --- Sluggo/View Controllers/LoginViewController.swift | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index fb0b6f0..cae12fa 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -79,6 +79,12 @@ class LoginViewController: UIViewController { switch(result) { case .success(let userRecord): self.identity.authenticatedUser = userRecord + + // Segue out of VC + DispatchQueue.main.async { + self.dismiss(animated: true, completion: self.completion) + } + break case .failure(let error): print("FAILURE!") @@ -87,13 +93,9 @@ class LoginViewController: UIViewController { } } } - if(self.identity.authenticatedUser == nil) { return }; // avoid segueing out if we didn't fully successfully log in } - // Segue out of VC - DispatchQueue.main.async { - self.dismiss(animated: true, completion: self.completion) - } + break; case .failure(let error): From 27985831b4e672e12122740a00142c9071f75369 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Thu, 6 May 2021 13:03:26 -0700 Subject: [PATCH 129/224] Refactor PinnedTicket to PinnedTicketRecord and merge files --- Sluggo.xcodeproj/project.pbxproj | 12 ++++-------- .../{PinnedTicket.swift => PinnedTicketRecord.swift} | 6 +++++- Sluggo/Models/Codables/WritePinnedTicketRecord.swift | 12 ------------ Sluggo/Models/Managers/PinnedTicketManager.swift | 6 +++--- .../View Controllers/HomeTableViewController.swift | 2 +- 5 files changed, 13 insertions(+), 25 deletions(-) rename Sluggo/Models/Codables/{PinnedTicket.swift => PinnedTicketRecord.swift} (68%) delete mode 100644 Sluggo/Models/Codables/WritePinnedTicketRecord.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index a11c3dd..481cae9 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -55,8 +55,7 @@ 7DC2A21726293F360002CEE6 /* Home.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DC2A21626293F360002CEE6 /* Home.storyboard */; }; 7DC2A21C262943A70002CEE6 /* Sidebar.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DC2A21B262943A70002CEE6 /* Sidebar.storyboard */; }; 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */; }; - 7DC54AE526437AB3000C8300 /* PinnedTicket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC54AE426437AB3000C8300 /* PinnedTicket.swift */; }; - 7DC54AE7264382C9000C8300 /* WritePinnedTicketRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC54AE6264382C9000C8300 /* WritePinnedTicketRecord.swift */; }; + 7DC54AE526437AB3000C8300 /* PinnedTicketRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC54AE426437AB3000C8300 /* PinnedTicketRecord.swift */; }; 7DEA17852639E7B5004E5A71 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DEA17842639E7B5004E5A71 /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ @@ -131,8 +130,7 @@ 7DC2A21626293F360002CEE6 /* Home.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Home.storyboard; sourceTree = ""; }; 7DC2A21B262943A70002CEE6 /* Sidebar.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Sidebar.storyboard; sourceTree = ""; }; 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SluggoSidebarContainerViewController.swift; sourceTree = ""; }; - 7DC54AE426437AB3000C8300 /* PinnedTicket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedTicket.swift; sourceTree = ""; }; - 7DC54AE6264382C9000C8300 /* WritePinnedTicketRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WritePinnedTicketRecord.swift; sourceTree = ""; }; + 7DC54AE426437AB3000C8300 /* PinnedTicketRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedTicketRecord.swift; sourceTree = ""; }; 7DEA17842639E7B5004E5A71 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXFileReference section */ @@ -207,8 +205,7 @@ 2FD70D5C2636144300D13C75 /* LogoutMessage.swift */, 3A150A6626365A9A0052934B /* StatusRecord.swift */, 3A150A6B26365B670052934B /* TagRecord.swift */, - 7DC54AE426437AB3000C8300 /* PinnedTicket.swift */, - 7DC54AE6264382C9000C8300 /* WritePinnedTicketRecord.swift */, + 7DC54AE426437AB3000C8300 /* PinnedTicketRecord.swift */, ); path = Codables; sourceTree = ""; @@ -459,12 +456,11 @@ 3A150A7F263CBA860052934B /* TicketDetailViewController.swift in Sources */, 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */, 5A00A0142639EDEA0085C8C6 /* URLRequestBuilder.swift in Sources */, - 7DC54AE7264382C9000C8300 /* WritePinnedTicketRecord.swift in Sources */, 2FD70D5D2636144300D13C75 /* LogoutMessage.swift in Sources */, 5AB1C8EA262FE2EF0010F85E /* MemberManager.swift in Sources */, 5A9C4C1C2640BC4E00B270AB /* TeamTableViewController.swift in Sources */, 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */, - 7DC54AE526437AB3000C8300 /* PinnedTicket.swift in Sources */, + 7DC54AE526437AB3000C8300 /* PinnedTicketRecord.swift in Sources */, 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */, 5AC6459026412F0C00F46137 /* TeamTableViewCell.swift in Sources */, 051AAB0B2630FD6500BCB9C7 /* Constants.swift in Sources */, diff --git a/Sluggo/Models/Codables/PinnedTicket.swift b/Sluggo/Models/Codables/PinnedTicketRecord.swift similarity index 68% rename from Sluggo/Models/Codables/PinnedTicket.swift rename to Sluggo/Models/Codables/PinnedTicketRecord.swift index 82151d4..27a383b 100644 --- a/Sluggo/Models/Codables/PinnedTicket.swift +++ b/Sluggo/Models/Codables/PinnedTicketRecord.swift @@ -7,9 +7,13 @@ import Foundation -struct PinnedTicket: Codable { +struct PinnedTicketRecord: Codable { let id: String // Primary key let object_uuid: String let pinned: Date let ticket: TicketRecord } + +struct WritePinnedTicketRecord: Codable { + let ticketID: Int +} diff --git a/Sluggo/Models/Codables/WritePinnedTicketRecord.swift b/Sluggo/Models/Codables/WritePinnedTicketRecord.swift deleted file mode 100644 index 7d522ec..0000000 --- a/Sluggo/Models/Codables/WritePinnedTicketRecord.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// WritePinnedTicketRecord.swift -// Sluggo -// -// Created by Isaac Trimble-Pederson on 5/5/21. -// - -import Foundation - -struct WritePinnedTicketRecord: Codable { - let ticketID: Int -} diff --git a/Sluggo/Models/Managers/PinnedTicketManager.swift b/Sluggo/Models/Managers/PinnedTicketManager.swift index 3e00583..2d2cc1b 100644 --- a/Sluggo/Models/Managers/PinnedTicketManager.swift +++ b/Sluggo/Models/Managers/PinnedTicketManager.swift @@ -25,7 +25,7 @@ class PinnedTicketManager { return URL(string: makeListURL().absoluteString + "/\(desiredID)/")! } - public func fetchPinnedTickets(completionHandler: @escaping(Result<[PinnedTicket], Error>) -> Void) -> Void { + public func fetchPinnedTickets(completionHandler: @escaping(Result<[PinnedTicketRecord], Error>) -> Void) -> Void { let requestBuilder = URLRequestBuilder(url: makeListURL()) .setMethod(method: .GET) .setIdentity(identity: identity) @@ -33,7 +33,7 @@ class PinnedTicketManager { JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } - public func postPinnedTicket(member: MemberRecord, ticket: TicketRecord, completionHandler: @escaping(Result) -> Void) -> Void { + public func postPinnedTicket(member: MemberRecord, ticket: TicketRecord, completionHandler: @escaping(Result) -> Void) -> Void { let writeRecord = WritePinnedTicketRecord(ticketID: ticket.id) @@ -50,7 +50,7 @@ class PinnedTicketManager { JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } - public func deletePinnedTicket(member: MemberRecord, pinned: PinnedTicket, completionHandler: @escaping(Result) -> Void) -> Void { + public func deletePinnedTicket(member: MemberRecord, pinned: PinnedTicketRecord, completionHandler: @escaping(Result) -> Void) -> Void { let requestBuilder = URLRequestBuilder(url: makeDetailURL(desiredID: pinned.id)) .setMethod(method: .DELETE) diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index ad6eaba..ddbe12e 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -16,7 +16,7 @@ enum HomepageCategories: Int { class HomeTableViewController: UITableViewController { var identity: AppIdentity! var member: MemberRecord! - var pinnedTickets: [PinnedTicket] = [] + var pinnedTickets: [PinnedTicketRecord] = [] // Injection for identity init? (coder: NSCoder, identity: AppIdentity) { From 9e631d1dfbbb50c84fe1568933a3bbfb79d3dbf8 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Thu, 6 May 2021 13:05:43 -0700 Subject: [PATCH 130/224] Rename models to Record --- Sluggo.xcodeproj/project.pbxproj | 32 +++++++++---------- .../{Member.swift => MemberRecord.swift} | 0 .../Codables/{Team.swift => TeamRecord.swift} | 0 .../{Token.swift => TokenRecord.swift} | 0 .../Codables/{User.swift => UserRecord.swift} | 0 5 files changed, 16 insertions(+), 16 deletions(-) rename Sluggo/Models/Codables/{Member.swift => MemberRecord.swift} (100%) rename Sluggo/Models/Codables/{Team.swift => TeamRecord.swift} (100%) rename Sluggo/Models/Codables/{Token.swift => TokenRecord.swift} (100%) rename Sluggo/Models/Codables/{User.swift => UserRecord.swift} (100%) diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 481cae9..3745500 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -9,10 +9,10 @@ /* Begin PBXBuildFile section */ 051AAB0B2630FD6500BCB9C7 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 051AAB0A2630FD6500BCB9C7 /* Constants.swift */; }; 052258702636100B00B49CE4 /* LaunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0522586F2636100B00B49CE4 /* LaunchViewController.swift */; }; - 2F2247AC262920AC006C6EE6 /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2247AB262920AC006C6EE6 /* Token.swift */; }; - 2F3AD7A32627C86A00B61A97 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A22627C86A00B61A97 /* User.swift */; }; - 2F3AD7A82627C89B00B61A97 /* Member.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A72627C89B00B61A97 /* Member.swift */; }; - 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7AC2627C8EF00B61A97 /* Team.swift */; }; + 2F2247AC262920AC006C6EE6 /* TokenRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2247AB262920AC006C6EE6 /* TokenRecord.swift */; }; + 2F3AD7A32627C86A00B61A97 /* UserRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A22627C86A00B61A97 /* UserRecord.swift */; }; + 2F3AD7A82627C89B00B61A97 /* MemberRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A72627C89B00B61A97 /* MemberRecord.swift */; }; + 2F3AD7AD2627C8EF00B61A97 /* TeamRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7AC2627C8EF00B61A97 /* TeamRecord.swift */; }; 2F3F66E2262DD3BA0040404C /* JsonLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3F66E1262DD3BA0040404C /* JsonLoader.swift */; }; 2F44E21D2639FA6B006CA85D /* TicketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F44E21C2639FA6B006CA85D /* TicketTests.swift */; }; 2FD70D5D2636144300D13C75 /* LogoutMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FD70D5C2636144300D13C75 /* LogoutMessage.swift */; }; @@ -79,10 +79,10 @@ /* Begin PBXFileReference section */ 051AAB0A2630FD6500BCB9C7 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 0522586F2636100B00B49CE4 /* LaunchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchViewController.swift; sourceTree = ""; }; - 2F2247AB262920AC006C6EE6 /* Token.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = ""; }; - 2F3AD7A22627C86A00B61A97 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; - 2F3AD7A72627C89B00B61A97 /* Member.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Member.swift; sourceTree = ""; }; - 2F3AD7AC2627C8EF00B61A97 /* Team.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Team.swift; sourceTree = ""; }; + 2F2247AB262920AC006C6EE6 /* TokenRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenRecord.swift; sourceTree = ""; }; + 2F3AD7A22627C86A00B61A97 /* UserRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRecord.swift; sourceTree = ""; }; + 2F3AD7A72627C89B00B61A97 /* MemberRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberRecord.swift; sourceTree = ""; }; + 2F3AD7AC2627C8EF00B61A97 /* TeamRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamRecord.swift; sourceTree = ""; }; 2F3F66E1262DD3BA0040404C /* JsonLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JsonLoader.swift; sourceTree = ""; }; 2F44E21C2639FA6B006CA85D /* TicketTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketTests.swift; sourceTree = ""; }; 2FD70D5C2636144300D13C75 /* LogoutMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutMessage.swift; sourceTree = ""; }; @@ -195,10 +195,10 @@ 5A79FFED262E5FEF00D04320 /* Codables */ = { isa = PBXGroup; children = ( - 2F3AD7A22627C86A00B61A97 /* User.swift */, - 2F3AD7A72627C89B00B61A97 /* Member.swift */, - 2F3AD7AC2627C8EF00B61A97 /* Team.swift */, - 2F2247AB262920AC006C6EE6 /* Token.swift */, + 2F3AD7A22627C86A00B61A97 /* UserRecord.swift */, + 2F3AD7A72627C89B00B61A97 /* MemberRecord.swift */, + 2F3AD7AC2627C8EF00B61A97 /* TeamRecord.swift */, + 2F2247AB262920AC006C6EE6 /* TokenRecord.swift */, 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */, 5AB1C8FB262FF1690010F85E /* TicketRecord.swift */, 5ACC0F002630CA1400DCF83E /* ErrorMessage.swift */, @@ -459,16 +459,16 @@ 2FD70D5D2636144300D13C75 /* LogoutMessage.swift in Sources */, 5AB1C8EA262FE2EF0010F85E /* MemberManager.swift in Sources */, 5A9C4C1C2640BC4E00B270AB /* TeamTableViewController.swift in Sources */, - 2F3AD7AD2627C8EF00B61A97 /* Team.swift in Sources */, + 2F3AD7AD2627C8EF00B61A97 /* TeamRecord.swift in Sources */, 7DC54AE526437AB3000C8300 /* PinnedTicketRecord.swift in Sources */, 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */, 5AC6459026412F0C00F46137 /* TeamTableViewCell.swift in Sources */, 051AAB0B2630FD6500BCB9C7 /* Constants.swift in Sources */, 2F3F66E2262DD3BA0040404C /* JsonLoader.swift in Sources */, - 2F2247AC262920AC006C6EE6 /* Token.swift in Sources */, + 2F2247AC262920AC006C6EE6 /* TokenRecord.swift in Sources */, 7D1DA6AE262B82C200E7C12D /* RootViewController.swift in Sources */, 5ACC0F012630CA1400DCF83E /* ErrorMessage.swift in Sources */, - 2F3AD7A32627C86A00B61A97 /* User.swift in Sources */, + 2F3AD7A32627C86A00B61A97 /* UserRecord.swift in Sources */, 052258702636100B00B49CE4 /* LaunchViewController.swift in Sources */, 3A150A6726365A9A0052934B /* StatusRecord.swift in Sources */, 7D53BE6926433D6600400D0E /* PinnedTicketManager.swift in Sources */, @@ -481,7 +481,7 @@ 5AB1C8EF262FE3210010F85E /* TagManager.swift in Sources */, 5A9FF88E26386D4700378550 /* TicketListController.swift in Sources */, 5AB1C8E5262FE2E60010F85E /* TicketManager.swift in Sources */, - 2F3AD7A82627C89B00B61A97 /* Member.swift in Sources */, + 2F3AD7A82627C89B00B61A97 /* MemberRecord.swift in Sources */, 5AB1C8DD262FE2DD0010F85E /* TeamManager.swift in Sources */, 5A00A045263A34CC0085C8C6 /* TicketTableViewCell.swift in Sources */, 5AB1C909262FFF500010F85E /* UserManager.swift in Sources */, diff --git a/Sluggo/Models/Codables/Member.swift b/Sluggo/Models/Codables/MemberRecord.swift similarity index 100% rename from Sluggo/Models/Codables/Member.swift rename to Sluggo/Models/Codables/MemberRecord.swift diff --git a/Sluggo/Models/Codables/Team.swift b/Sluggo/Models/Codables/TeamRecord.swift similarity index 100% rename from Sluggo/Models/Codables/Team.swift rename to Sluggo/Models/Codables/TeamRecord.swift diff --git a/Sluggo/Models/Codables/Token.swift b/Sluggo/Models/Codables/TokenRecord.swift similarity index 100% rename from Sluggo/Models/Codables/Token.swift rename to Sluggo/Models/Codables/TokenRecord.swift diff --git a/Sluggo/Models/Codables/User.swift b/Sluggo/Models/Codables/UserRecord.swift similarity index 100% rename from Sluggo/Models/Codables/User.swift rename to Sluggo/Models/Codables/UserRecord.swift From 36e43e721b8b10fc9faea90a17b99c70650e072a Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Thu, 6 May 2021 13:18:58 -0700 Subject: [PATCH 131/224] First pass at sidebar gesture We should consider restricting this to root view controllers in the future. --- Sluggo/View Controllers/RootViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sluggo/View Controllers/RootViewController.swift b/Sluggo/View Controllers/RootViewController.swift index 77f1be9..1a94ef3 100644 --- a/Sluggo/View Controllers/RootViewController.swift +++ b/Sluggo/View Controllers/RootViewController.swift @@ -60,10 +60,10 @@ class RootViewController: UIViewController { } @IBAction func receivedGesture() { - print("swiped right") + NotificationCenter.default.post(name: .onSidebarTrigger, object: self, userInfo: [Sidebar.USER_INFO_KEY: SidebarStatus.open]) } @IBAction func receieveLeft() { - print("swiped left") + NotificationCenter.default.post(name: .onSidebarTrigger, object: self, userInfo: [Sidebar.USER_INFO_KEY: SidebarStatus.closed]) } @IBSegueAction func showSidebar(_ coder: NSCoder) -> UIViewController? { return SluggoSidebarContainerViewController(coder: coder, identity: self.identity) From 10f2c2040279b362346060b15846b1ca912420db Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Thu, 6 May 2021 13:23:43 -0700 Subject: [PATCH 132/224] Remove secondary DispatchQueue --- .../LoginViewController.swift | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index cae12fa..d77c99f 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -73,24 +73,21 @@ class LoginViewController: UIViewController { self.identity.token = record.key // Wait for user record to also be fetched - // Don't ask me why this needs to be in another dispatch, it just fails otherwise. - DispatchQueue.global(qos: .userInitiated).sync { - userManager.getUser() { result in - switch(result) { - case .success(let userRecord): - self.identity.authenticatedUser = userRecord - - // Segue out of VC - DispatchQueue.main.async { - self.dismiss(animated: true, completion: self.completion) - } - - break - case .failure(let error): - print("FAILURE!") - DispatchQueue.main.sync { - UIAlertController.createAndPresentError(vc: self, error: error, completion: nil) - } + userManager.getUser() { result in + switch(result) { + case .success(let userRecord): + self.identity.authenticatedUser = userRecord + + // Segue out of VC + DispatchQueue.main.async { + self.dismiss(animated: true, completion: self.completion) + } + + break + case .failure(let error): + print("FAILURE!") + DispatchQueue.main.sync { + UIAlertController.createAndPresentError(vc: self, error: error, completion: nil) } } } From 64623cee0b902b50c72ee0002968bad14d2dbf48 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Mon, 10 May 2021 19:15:35 -0700 Subject: [PATCH 133/224] Probably working but untested assigned user setting --- Sluggo/Models/Managers/TicketManager.swift | 17 ++++++++++++----- .../HomeTableViewController.swift | 8 +++++++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index 927cf7b..3692abc 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -19,25 +19,32 @@ class TicketManager { return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + TicketManager.urlBase + "\(ticketRecord.id)/")! } - private func makeListUrl(page: Int) -> URL { - let urlString = identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + TicketManager.urlBase + "?page=\(page)" + private func makeListUrl(page: Int, assignedMember: MemberRecord?) -> URL { + var urlString = identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + TicketManager.urlBase + "?page=\(page)" + if let assignedMember = assignedMember { + urlString += "?assigned_user__owner_username=\(assignedMember.owner.username)" + } return URL(string: urlString)! } - public func listTeamTickets(page: Int, completionHandler: @escaping (Result, Error>) -> Void) -> Void { - let requestBuilder = URLRequestBuilder(url: makeListUrl(page: page)) + public func listTeamTickets(page: Int, assigned: MemberRecord?, completionHandler: @escaping (Result, Error>) -> Void) -> Void { + let requestBuilder = URLRequestBuilder(url: makeListUrl(page: page, assignedMember: nil)) .setMethod(method: .GET) .setIdentity(identity: self.identity) JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } + public func listTeamTickets(page: Int, completionHandler: @escaping (Result, Error>) -> Void) -> Void { + listTeamTickets(page: page, assigned: nil, completionHandler: completionHandler) + } + public func makeTicket(ticket: WriteTicketRecord, completionHandler: @escaping(Result) -> Void)->Void{ guard let body = JsonLoader.encode(object: ticket) else { completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize ticket JSON for makeTicket in TicketManager"))) return } - let requestBuilder = URLRequestBuilder(url: makeListUrl(page: 1)) + let requestBuilder = URLRequestBuilder(url: makeListUrl(page: 1, assignedMember: nil)) .setMethod(method: .POST) .setData(data: body) .setIdentity(identity: self.identity) diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index ddbe12e..8fdf58b 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -65,7 +65,13 @@ class HomeTableViewController: UITableViewController { } func loadAssignedTickets(completionHandler: (() -> Void)?) { - // TODO + // Note: This will only grab the first page. + // This is an OK assumption, since we assume the total page size is > 3. + // Ideally, this could be extended with some other endpoints to get the total counts + // but this will suffice for now + let ticketsManager = TicketManager(identity) + // TODO Call to filtered method + completionHandler?() } From 4e3e528b4ee928c6517b9e8d02fc869c830a122a Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Mon, 10 May 2021 19:33:34 -0700 Subject: [PATCH 134/224] First working pass at assigned tickets TODO: - Verify error flows on homepage screen - Make tests so the code is cleaner - Reduce duplication on homepage table view controller --- Sluggo/Models/Managers/TicketManager.swift | 6 +-- .../HomeTableViewController.swift | 40 ++++++++++++++----- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index 3692abc..776e634 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -19,16 +19,16 @@ class TicketManager { return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + TicketManager.urlBase + "\(ticketRecord.id)/")! } - private func makeListUrl(page: Int, assignedMember: MemberRecord?) -> URL { + public func makeListUrl(page: Int, assignedMember: MemberRecord?) -> URL { var urlString = identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + TicketManager.urlBase + "?page=\(page)" if let assignedMember = assignedMember { - urlString += "?assigned_user__owner_username=\(assignedMember.owner.username)" + urlString += "&assigned_user__owner__username=\(assignedMember.owner.username)" } return URL(string: urlString)! } public func listTeamTickets(page: Int, assigned: MemberRecord?, completionHandler: @escaping (Result, Error>) -> Void) -> Void { - let requestBuilder = URLRequestBuilder(url: makeListUrl(page: page, assignedMember: nil)) + let requestBuilder = URLRequestBuilder(url: makeListUrl(page: page, assignedMember: assigned)) .setMethod(method: .GET) .setIdentity(identity: self.identity) diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index 8fdf58b..335e476 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -16,6 +16,7 @@ enum HomepageCategories: Int { class HomeTableViewController: UITableViewController { var identity: AppIdentity! var member: MemberRecord! + var assignedTickets: [TicketRecord] = [] var pinnedTickets: [PinnedTicketRecord] = [] // Injection for identity @@ -33,7 +34,9 @@ class HomeTableViewController: UITableViewController { // Fetch items and begin populating the table view loadMember() { - self.loadPinnedTickets(completionHandler: nil) + self.loadAssignedTickets() { + self.loadPinnedTickets(completionHandler: nil) + } } // Setup refresh control @@ -70,9 +73,20 @@ class HomeTableViewController: UITableViewController { // Ideally, this could be extended with some other endpoints to get the total counts // but this will suffice for now let ticketsManager = TicketManager(identity) - // TODO Call to filtered method - - completionHandler?() + ticketsManager.listTeamTickets(page: 1, assigned: member) { result in + switch(result) { + case .success(let assignedTicketsList): + DispatchQueue.main.sync { + self.assignedTickets = assignedTicketsList.results + completionHandler?() + } + break + case .failure(let error): + DispatchQueue.main.async { + UIAlertController.createAndPresentError(vc: self, error: error, completion: nil) + } + } + } } func loadPinnedTickets(completionHandler: (() -> Void)?) { @@ -100,10 +114,12 @@ class HomeTableViewController: UITableViewController { return; } - self.loadPinnedTickets(completionHandler: { - DispatchQueue.main.async { - self.refreshControl?.endRefreshing() - } + self.loadAssignedTickets(completionHandler: { + self.loadPinnedTickets(completionHandler: { + DispatchQueue.main.async { + self.refreshControl?.endRefreshing() + } + }) }) } @@ -118,7 +134,7 @@ class HomeTableViewController: UITableViewController { // #warning Incomplete implementation, return the number of rows switch(section) { case HomepageCategories.assigned.rawValue: - return 1 + return self.assignedTickets.count case HomepageCategories.pinned.rawValue: return self.pinnedTickets.count case HomepageCategories.tags.rawValue: @@ -131,8 +147,8 @@ class HomeTableViewController: UITableViewController { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { switch(indexPath.section) { case HomepageCategories.assigned.rawValue: - let cell = tableView.dequeueReusableCell(withIdentifier: "PlaceholderCell")! - cell.textLabel?.text = "Not yet implemented." + let cell = tableView.dequeueReusableCell(withIdentifier: "TicketCell", for: indexPath) as! TicketTableViewCell + cell.loadFromTicketRecord(ticket: assignedTickets[indexPath.row]) return cell case HomepageCategories.pinned.rawValue: let cell = tableView.dequeueReusableCell(withIdentifier: "TicketCell", for: indexPath) as! TicketTableViewCell @@ -174,6 +190,8 @@ class HomeTableViewController: UITableViewController { @IBSegueAction func gotoTicketDetail(_ coder: NSCoder) -> TicketDetailViewController? { let selectedPath = tableView.indexPathForSelectedRow switch(selectedPath!.section) { + case HomepageCategories.assigned.rawValue: + return TicketDetailViewController(coder: coder, identity: self.identity, ticket: assignedTickets[selectedPath!.row], completion: nil) case HomepageCategories.pinned.rawValue: return TicketDetailViewController(coder: coder, identity: self.identity, ticket: pinnedTickets[selectedPath!.row].ticket, completion: nil) default: From 6eed2a7187eb60715b3a2cb4a46231c2b42ebb53 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Tue, 11 May 2021 00:27:56 -0700 Subject: [PATCH 135/224] Reduce repetition with API calls from Home view controller Keep an eye on any strange issues that arise, as semaphore logic could be a bit hairy. --- Sluggo.xcodeproj/project.pbxproj | 12 +++ Sluggo/Models/Exceptions.swift | 4 + .../Extensions/ViewControllerExtensions.swift | 45 ++++++++ .../HomeTableViewController.swift | 100 +++++++++--------- 4 files changed, 109 insertions(+), 52 deletions(-) create mode 100644 Sluggo/View Controllers/Extensions/ViewControllerExtensions.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 3745500..1982c39 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */; }; 7D23834B264119050091C7E8 /* HomeTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D23834A264119050091C7E8 /* HomeTableViewController.swift */; }; 7D53BE6926433D6600400D0E /* PinnedTicketManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D53BE6826433D6600400D0E /* PinnedTicketManager.swift */; }; + 7D778C06264A374C00D4061A /* ViewControllerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D778C05264A374C00D4061A /* ViewControllerExtensions.swift */; }; 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */; }; 7D9963AD261EF3A4002338E7 /* Root.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7D9963AB261EF3A4002338E7 /* Root.storyboard */; }; 7D9963B2261EF3A5002338E7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7D9963B1261EF3A5002338E7 /* Assets.xcassets */; }; @@ -116,6 +117,7 @@ 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarData.swift; sourceTree = ""; }; 7D23834A264119050091C7E8 /* HomeTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HomeTableViewController.swift; path = "Sluggo/View Controllers/HomeTableViewController.swift"; sourceTree = SOURCE_ROOT; }; 7D53BE6826433D6600400D0E /* PinnedTicketManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedTicketManager.swift; sourceTree = ""; }; + 7D778C05264A374C00D4061A /* ViewControllerExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerExtensions.swift; sourceTree = ""; }; 7D9963A2261EF3A4002338E7 /* Sluggo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sluggo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7D9963AC261EF3A4002338E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Root.storyboard; sourceTree = ""; }; @@ -223,6 +225,14 @@ path = Managers; sourceTree = ""; }; + 7D778C04264A374200D4061A /* Extensions */ = { + isa = PBXGroup; + children = ( + 7D778C05264A374C00D4061A /* ViewControllerExtensions.swift */, + ); + path = Extensions; + sourceTree = ""; + }; 7D996399261EF3A4002338E7 = { isa = PBXGroup; children = ( @@ -296,6 +306,7 @@ 7DC2A2202629442B0002CEE6 /* View Controllers */ = { isa = PBXGroup; children = ( + 7D778C04264A374200D4061A /* Extensions */, 0522586F2636100B00B49CE4 /* LaunchViewController.swift */, 5A79FFE5262E563F00D04320 /* LoginViewController.swift */, 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */, @@ -477,6 +488,7 @@ 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */, 5A673570263BF2D200C4C778 /* UIColor.swift in Sources */, 3A150A6C26365B670052934B /* TagRecord.swift in Sources */, + 7D778C06264A374C00D4061A /* ViewControllerExtensions.swift in Sources */, 7D23834B264119050091C7E8 /* HomeTableViewController.swift in Sources */, 5AB1C8EF262FE3210010F85E /* TagManager.swift in Sources */, 5A9FF88E26386D4700378550 /* TicketListController.swift in Sources */, diff --git a/Sluggo/Models/Exceptions.swift b/Sluggo/Models/Exceptions.swift index cd936dc..168fdc9 100644 --- a/Sluggo/Models/Exceptions.swift +++ b/Sluggo/Models/Exceptions.swift @@ -50,4 +50,8 @@ extension UIAlertController { vc.present(alertController, animated: true, completion: nil) } + + static func createAndPresentError(vc: UIViewController, error: Error) { + UIAlertController.createAndPresentError(vc: vc, error: error, completion: nil) + } } diff --git a/Sluggo/View Controllers/Extensions/ViewControllerExtensions.swift b/Sluggo/View Controllers/Extensions/ViewControllerExtensions.swift new file mode 100644 index 0000000..9c639eb --- /dev/null +++ b/Sluggo/View Controllers/Extensions/ViewControllerExtensions.swift @@ -0,0 +1,45 @@ +// +// ViewControllerExtensions.swift +// Sluggo +// +// Created by Isaac Trimble-Pederson on 5/10/21. +// + +import UIKit + +extension UIViewController { + func presentError(error: Error, completion: ((UIAlertAction) -> Void)?) { + UIAlertController.createAndPresentError(vc: self, error: error, completion: completion) + } + + func presentError(error: Error) { + UIAlertController.createAndPresentError(vc: self, error: error) + } + + func processResult(result: Result, onSuccess: @escaping ((T) -> Void), onError: ((Error) -> Void)?, after: (() -> Void)?) { + DispatchQueue.main.sync { + switch(result) { + case .success(let successObj): + onSuccess(successObj) + break + case .failure(let error): + if let errorHandler = onError { + errorHandler(error) + } else { + presentError(error: error) + } + } + + // Continue chains if necessary + after?() + } + } + + func processResult(result: Result, onSuccess: @escaping ((T) -> Void), after: (() -> Void)?) { + self.processResult(result: result, onSuccess: onSuccess, onError: nil, after: after) + } + + func processResult(result: Result, onSuccess: @escaping ((T) -> Void)) { + self.processResult(result: result, onSuccess: onSuccess, onError: nil, after: nil) + } +} diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index 335e476..311e1e5 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -32,12 +32,9 @@ class HomeTableViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() + self.refreshControl?.beginRefreshing() // Fetch items and begin populating the table view - loadMember() { - self.loadAssignedTickets() { - self.loadPinnedTickets(completionHandler: nil) - } - } + loadMember(completionHandler: refreshContent) // Setup refresh control self.refreshControl?.addTarget(self, action: #selector(refreshContent), for: .valueChanged) @@ -52,18 +49,9 @@ class HomeTableViewController: UITableViewController { func loadMember(completionHandler: (() -> Void)?) { let memberManager = MemberManager(identity: identity) memberManager.getMemberRecord(user: identity.authenticatedUser!, identity: identity) { result in - switch(result) { - case .success(let member): - DispatchQueue.main.sync { - self.member = member - completionHandler?() - } - break - case .failure(let error): - DispatchQueue.main.async { - UIAlertController.createAndPresentError(vc: self, error: error, completion: nil) - } - } + self.processResult(result: result, onSuccess: { retrievedMember in + self.member = retrievedMember + }, after: completionHandler) } } @@ -74,53 +62,61 @@ class HomeTableViewController: UITableViewController { // but this will suffice for now let ticketsManager = TicketManager(identity) ticketsManager.listTeamTickets(page: 1, assigned: member) { result in - switch(result) { - case .success(let assignedTicketsList): - DispatchQueue.main.sync { - self.assignedTickets = assignedTicketsList.results - completionHandler?() - } - break - case .failure(let error): - DispatchQueue.main.async { - UIAlertController.createAndPresentError(vc: self, error: error, completion: nil) - } - } + self.processResult(result: result, onSuccess: { retrievedAssignedTickets in + self.assignedTickets = retrievedAssignedTickets.results + self.tableView.reloadSections([HomepageCategories.assigned.rawValue], with: .automatic) + }, after: completionHandler) } } func loadPinnedTickets(completionHandler: (() -> Void)?) { let pinnedTicketsManager = PinnedTicketManager(identity: self.identity, member: self.member) pinnedTicketsManager.fetchPinnedTickets() { result in - switch(result) { - case .success(let pinnedTickets): - DispatchQueue.main.sync { - self.pinnedTickets = pinnedTickets - self.tableView.reloadData() - } - completionHandler?() - break - case .failure(let error): - DispatchQueue.main.sync { - UIAlertController.createAndPresentError(vc: self, error: error, completion: nil) - } - } + self.processResult(result: result, onSuccess: { retrievedPinned in + self.pinnedTickets = retrievedPinned + self.tableView.reloadSections([HomepageCategories.pinned.rawValue], with: .automatic) + }, after: completionHandler) } } @objc func refreshContent() { - if(self.member == nil) { - self.refreshControl?.endRefreshing() - return; + // Dispatch to background thread so we don't permanently sleep the main thread + // This is done since processResult handles closures back on the main thread + DispatchQueue.global(qos: .userInitiated).async { + if(self.member == nil) { + // Not safe to make the call + // Might make sense to migrate to optionals in data layer + // for future iterations of the app + self.refreshControl?.endRefreshing() + return; + } + + // Setup gating semaphores and variables + let sem = DispatchSemaphore(value: 0) + var loadedAssigned = false + var loadedPinned = false + + // Make calls to reload data, with completions to signal semaphore + self.loadAssignedTickets() { + loadedAssigned = true + sem.signal() + } + self.loadPinnedTickets() { + loadedPinned = true + sem.signal() + } + + // Wait for everything to complete and avoid blocking + while(!(loadedPinned && loadedAssigned)) { + sem.wait() + } + + // Stop the refresh control (being careful to dispatch to main thread) + DispatchQueue.main.async { + self.refreshControl?.endRefreshing() + } } - self.loadAssignedTickets(completionHandler: { - self.loadPinnedTickets(completionHandler: { - DispatchQueue.main.async { - self.refreshControl?.endRefreshing() - } - }) - }) } // MARK: - Table view data source From e5faf91560de3a6419b0c7e20e95c827b638aae8 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Tue, 11 May 2021 00:45:44 -0700 Subject: [PATCH 136/224] Added tests for URL generation This is kinda the easy part to test... I don't think we're going to be able to test the higher level concepts without refactoring, which we *do not* have time for in this class. Something to revisit if we seriously revamp this during summer --- Sluggo.xcodeproj/project.pbxproj | 4 +++ SluggoTests/TicketManagerTests.swift | 44 ++++++++++++++++++++++++++++ SluggoTests/TicketTests.swift | 30 +++++++++---------- 3 files changed, 63 insertions(+), 15 deletions(-) create mode 100644 SluggoTests/TicketManagerTests.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 1982c39..8400af2 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -48,6 +48,7 @@ 7D23834B264119050091C7E8 /* HomeTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D23834A264119050091C7E8 /* HomeTableViewController.swift */; }; 7D53BE6926433D6600400D0E /* PinnedTicketManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D53BE6826433D6600400D0E /* PinnedTicketManager.swift */; }; 7D778C06264A374C00D4061A /* ViewControllerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D778C05264A374C00D4061A /* ViewControllerExtensions.swift */; }; + 7D778C0A264A6A0F00D4061A /* TicketManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D778C09264A6A0F00D4061A /* TicketManagerTests.swift */; }; 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */; }; 7D9963AD261EF3A4002338E7 /* Root.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7D9963AB261EF3A4002338E7 /* Root.storyboard */; }; 7D9963B2261EF3A5002338E7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7D9963B1261EF3A5002338E7 /* Assets.xcassets */; }; @@ -118,6 +119,7 @@ 7D23834A264119050091C7E8 /* HomeTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HomeTableViewController.swift; path = "Sluggo/View Controllers/HomeTableViewController.swift"; sourceTree = SOURCE_ROOT; }; 7D53BE6826433D6600400D0E /* PinnedTicketManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedTicketManager.swift; sourceTree = ""; }; 7D778C05264A374C00D4061A /* ViewControllerExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerExtensions.swift; sourceTree = ""; }; + 7D778C09264A6A0F00D4061A /* TicketManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketManagerTests.swift; sourceTree = ""; }; 7D9963A2261EF3A4002338E7 /* Sluggo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sluggo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7D9963AC261EF3A4002338E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Root.storyboard; sourceTree = ""; }; @@ -276,6 +278,7 @@ 7D1354FB262D026E00D38EE0 /* UserTests.swift */, 2FEF5DAD262D1C4900434763 /* MemberTests.swift */, 2F44E21C2639FA6B006CA85D /* TicketTests.swift */, + 7D778C09264A6A0F00D4061A /* TicketManagerTests.swift */, ); path = SluggoTests; sourceTree = ""; @@ -508,6 +511,7 @@ 7D1354FC262D026E00D38EE0 /* UserTests.swift in Sources */, 2FEF5DAE262D1C4900434763 /* MemberTests.swift in Sources */, 7D9963C0261EF3A5002338E7 /* SluggoTests.swift in Sources */, + 7D778C0A264A6A0F00D4061A /* TicketManagerTests.swift in Sources */, 2F44E21D2639FA6B006CA85D /* TicketTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/SluggoTests/TicketManagerTests.swift b/SluggoTests/TicketManagerTests.swift new file mode 100644 index 0000000..490cd3f --- /dev/null +++ b/SluggoTests/TicketManagerTests.swift @@ -0,0 +1,44 @@ +// +// TicketManagerTests.swift +// SluggoTests +// +// Created by Isaac Trimble-Pederson on 5/11/21. +// + +import XCTest +@testable import Sluggo + +class TicketManagerTests: XCTestCase { + var identity: AppIdentity! + var exampleUser: UserRecord! + var exampleMember: MemberRecord! + + override func setUp() { + let team = TeamRecord(id: 1, name: "bugslotics", object_uuid: UUID(), ticket_head: 1, created: Date(timeIntervalSince1970: 0), activated: nil, deactivated: nil) + + identity = AppIdentity() + identity.team = team + identity.baseAddress = Constants.Config.URL_BASE + + exampleUser = UserRecord(id: 1, email: "sammy@ucsc.edu", first_name: "Sammy", last_name: "Slug", username: "sammytheslug") + exampleMember = MemberRecord(id: UUID().uuidString, owner: exampleUser, team_id: 1, object_uuid: UUID(), role: "AD", bio: nil, created: Date(timeIntervalSince1970: 0), activated: nil, deactivated: nil) + } + + func testListURLGeneration() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + + let ticketManager = TicketManager(identity) + let obtainedURL = ticketManager.makeListUrl(page: 1, assignedMember: nil) + + XCTAssertEqual(obtainedURL, URL(string: "http://127.0.0.1:8000/api/teams/1/tickets/?page=1")!) + } + + func testListURLGenerationWithAssignedFilter() throws { + let ticketManager = TicketManager(identity) + let obtainedURL = ticketManager.makeListUrl(page: 1, assignedMember: exampleMember) + + XCTAssertEqual(obtainedURL, URL(string: "http://127.0.0.1:8000/api/teams/1/tickets/?page=1&assigned_user__owner__username=sammytheslug")) + } + +} diff --git a/SluggoTests/TicketTests.swift b/SluggoTests/TicketTests.swift index 7abc334..2a3294b 100644 --- a/SluggoTests/TicketTests.swift +++ b/SluggoTests/TicketTests.swift @@ -55,20 +55,20 @@ class TicketTests: XCTestCase { """ func testTicketDoesDeserialize() { - - let ticket: TicketRecord? = JsonLoader.decode(data: testWorkingJson.data(using: .utf8)!) - - XCTAssertNotNil(ticket) - - XCTAssertEqual(ticket!.ticket_number, 2) - XCTAssertEqual(ticket!.title, "Hi") - XCTAssertEqual(ticket!.id, 2) - - - XCTAssertNil(ticket!.status) - XCTAssertNil(ticket!.assigned_user) - - + // CRASHES, PLEASE FIX! +// let ticket: TicketRecord? = JsonLoader.decode(data: testWorkingJson.data(using: .utf8)!) +// +// XCTAssertNotNil(ticket) +// +// XCTAssertEqual(ticket!.ticket_number, 2) +// XCTAssertEqual(ticket!.title, "Hi") +// XCTAssertEqual(ticket!.id, 2) +// +// +// XCTAssertNil(ticket!.status) +// XCTAssertNil(ticket!.assigned_user) +// +// } func testTicketDeserializesWithExtraProps() { @@ -110,7 +110,7 @@ class TicketTests: XCTestCase { func testTicketDoesSerialize() { - let ticket = TicketRecord(id: 1, ticket_number: 1, tag_list: nil, object_uuid: UUID(uuidString: "aa80fe7c-d346-4944-9d9e-3d7286fc4ca7")!, assigned_user: nil, status: nil, title: "Howdy Partner", description: "This is a ticket", due_date: Date(), created: Date(), activated: Date(), deactivated: Date()) + let ticket = TicketRecord(id: 1, ticket_number: 1, tag_list: [], object_uuid: UUID(uuidString: "aa80fe7c-d346-4944-9d9e-3d7286fc4ca7")!, assigned_user: nil, status: nil, title: "Howdy Partner", description: "This is a ticket", due_date: Date(), created: Date(), activated: Date(), deactivated: Date()) let json = JsonLoader.encode(object: ticket) XCTAssertNotNil(json) From 46a5c43d2b93ac8d45c0d1c9bcaecf528683b8e0 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Tue, 11 May 2021 01:17:21 -0700 Subject: [PATCH 137/224] fix ticket tests --- SluggoTests/TicketTests.swift | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/SluggoTests/TicketTests.swift b/SluggoTests/TicketTests.swift index 2a3294b..37bc13a 100644 --- a/SluggoTests/TicketTests.swift +++ b/SluggoTests/TicketTests.swift @@ -14,6 +14,7 @@ class TicketTests: XCTestCase { "id": 2, "ticket_number": 2, "object_uuid": "cefdf703-40ff-40d0-b07a-1583cc33d7d4", + "tag_list": [], "title": "Hi", "description": "this is a description", "created": "2021-04-28T20:31:43+0000", @@ -44,31 +45,29 @@ class TicketTests: XCTestCase { "activated": "2021-04-28T20:24:15+0000", "deactivated": null }, - "status": null, "title": "Hi", "description": "this is a description", - "due_date": null, "created": "2021-04-28T20:31:43+0000", "activated": "2021-04-28T20:31:43+0000", - "deactivated": null } """ func testTicketDoesDeserialize() { // CRASHES, PLEASE FIX! -// let ticket: TicketRecord? = JsonLoader.decode(data: testWorkingJson.data(using: .utf8)!) -// -// XCTAssertNotNil(ticket) -// -// XCTAssertEqual(ticket!.ticket_number, 2) -// XCTAssertEqual(ticket!.title, "Hi") -// XCTAssertEqual(ticket!.id, 2) -// -// -// XCTAssertNil(ticket!.status) -// XCTAssertNil(ticket!.assigned_user) -// -// + // you break it, you buy it + let ticket: TicketRecord? = JsonLoader.decode(data: testWorkingJson.data(using: .utf8)!) + + XCTAssertNotNil(ticket) + + XCTAssertEqual(ticket!.ticket_number, 2) + XCTAssertEqual(ticket!.title, "Hi") + XCTAssertEqual(ticket!.id, 2) + + + XCTAssertNil(ticket!.status) + XCTAssertNil(ticket!.assigned_user) + + } func testTicketDeserializesWithExtraProps() { From f6c710df31494d4a74b9e88cc1391bc8831cc5b7 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Tue, 11 May 2021 01:30:35 -0700 Subject: [PATCH 138/224] return null members to ticket json --- SluggoTests/TicketTests.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SluggoTests/TicketTests.swift b/SluggoTests/TicketTests.swift index 37bc13a..0ef0fc5 100644 --- a/SluggoTests/TicketTests.swift +++ b/SluggoTests/TicketTests.swift @@ -45,16 +45,18 @@ class TicketTests: XCTestCase { "activated": "2021-04-28T20:24:15+0000", "deactivated": null }, + "status": null, "title": "Hi", "description": "this is a description", + "due_date": null, "created": "2021-04-28T20:31:43+0000", "activated": "2021-04-28T20:31:43+0000", + "deactivated": null } """ func testTicketDoesDeserialize() { // CRASHES, PLEASE FIX! - // you break it, you buy it let ticket: TicketRecord? = JsonLoader.decode(data: testWorkingJson.data(using: .utf8)!) XCTAssertNotNil(ticket) From 5df853018d5c4c1515d9604045d959b9f696332a Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Tue, 11 May 2021 21:59:54 -0700 Subject: [PATCH 139/224] basic skelton of filterview --- Sluggo.xcodeproj/project.pbxproj | 12 ++ Sluggo/Models/Codables/TagRecord.swift | 2 +- Sluggo/Models/Managers/MemberManager.swift | 2 + Sluggo/Models/Managers/StatusManager.swift | 36 +++++ Sluggo/Models/Managers/TagManager.swift | 23 ++++ Sluggo/Models/Managers/TicketManager.swift | 16 ++- Sluggo/Storyboards/Home.storyboard | 21 +-- Sluggo/Storyboards/Ticket.storyboard | 20 ++- .../TeamTableViewController.swift | 2 - .../TicketFilterTableViewController.swift | 70 ++++++++++ SluggoTests/TagTests.swift | 124 ++++++++++++++++++ 11 files changed, 312 insertions(+), 16 deletions(-) create mode 100644 Sluggo/Models/Managers/StatusManager.swift create mode 100644 Sluggo/View Controllers/TicketFilterTableViewController.swift create mode 100644 SluggoTests/TagTests.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 8400af2..ac212ce 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -24,6 +24,9 @@ 3A150A7F263CBA860052934B /* TicketDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A7E263CBA860052934B /* TicketDetailViewController.swift */; }; 5A00A0142639EDEA0085C8C6 /* URLRequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */; }; 5A00A045263A34CC0085C8C6 /* TicketTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A00A044263A34CC0085C8C6 /* TicketTableViewCell.swift */; }; + 5A25CFA2264B8C8100852035 /* TagTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFA1264B8C8100852035 /* TagTests.swift */; }; + 5A25CFA7264B934400852035 /* StatusManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFA6264B934400852035 /* StatusManager.swift */; }; + 5A25CFAC264B966300852035 /* TicketFilterTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFAB264B966300852035 /* TicketFilterTableViewController.swift */; }; 5A673570263BF2D200C4C778 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A67356F263BF2D200C4C778 /* UIColor.swift */; }; 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A79FFE0262E562A00D04320 /* Main.storyboard */; }; 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A79FFE5262E563F00D04320 /* LoginViewController.swift */; }; @@ -96,6 +99,9 @@ 3A150A7E263CBA860052934B /* TicketDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketDetailViewController.swift; sourceTree = ""; }; 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLRequestBuilder.swift; sourceTree = ""; }; 5A00A044263A34CC0085C8C6 /* TicketTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketTableViewCell.swift; sourceTree = ""; }; + 5A25CFA1264B8C8100852035 /* TagTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagTests.swift; sourceTree = ""; }; + 5A25CFA6264B934400852035 /* StatusManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusManager.swift; sourceTree = ""; }; + 5A25CFAB264B966300852035 /* TicketFilterTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketFilterTableViewController.swift; sourceTree = ""; }; 5A67356F263BF2D200C4C778 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; 5A79FFE0262E562A00D04320 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 5A79FFE5262E563F00D04320 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; @@ -223,6 +229,7 @@ 5AB1C8E9262FE2EF0010F85E /* MemberManager.swift */, 5AB1C8EE262FE3210010F85E /* TagManager.swift */, 5AB1C908262FFF500010F85E /* UserManager.swift */, + 5A25CFA6264B934400852035 /* StatusManager.swift */, ); path = Managers; sourceTree = ""; @@ -279,6 +286,7 @@ 2FEF5DAD262D1C4900434763 /* MemberTests.swift */, 2F44E21C2639FA6B006CA85D /* TicketTests.swift */, 7D778C09264A6A0F00D4061A /* TicketManagerTests.swift */, + 5A25CFA1264B8C8100852035 /* TagTests.swift */, ); path = SluggoTests; sourceTree = ""; @@ -319,6 +327,7 @@ 3A150A7E263CBA860052934B /* TicketDetailViewController.swift */, 5A9C4C1B2640BC4E00B270AB /* TeamTableViewController.swift */, 5A9C4C202641113D00B270AB /* TeamSelectorContainerViewController.swift */, + 5A25CFAB264B966300852035 /* TicketFilterTableViewController.swift */, ); path = "View Controllers"; sourceTree = ""; @@ -487,6 +496,7 @@ 3A150A6726365A9A0052934B /* StatusRecord.swift in Sources */, 7D53BE6926433D6600400D0E /* PinnedTicketManager.swift in Sources */, 5ACC0F062630CA2700DCF83E /* Exceptions.swift in Sources */, + 5A25CFA7264B934400852035 /* StatusManager.swift in Sources */, 5AB1C8F7262FE3520010F85E /* AppIdentity.swift in Sources */, 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */, 5A673570263BF2D200C4C778 /* UIColor.swift in Sources */, @@ -501,6 +511,7 @@ 5A00A045263A34CC0085C8C6 /* TicketTableViewCell.swift in Sources */, 5AB1C909262FFF500010F85E /* UserManager.swift in Sources */, 2FEF5DA9262D0E7C00434763 /* PaginatedList.swift in Sources */, + 5A25CFAC264B966300852035 /* TicketFilterTableViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -512,6 +523,7 @@ 2FEF5DAE262D1C4900434763 /* MemberTests.swift in Sources */, 7D9963C0261EF3A5002338E7 /* SluggoTests.swift in Sources */, 7D778C0A264A6A0F00D4061A /* TicketManagerTests.swift in Sources */, + 5A25CFA2264B8C8100852035 /* TagTests.swift in Sources */, 2F44E21D2639FA6B006CA85D /* TicketTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sluggo/Models/Codables/TagRecord.swift b/Sluggo/Models/Codables/TagRecord.swift index 3e5a814..f0b2938 100644 --- a/Sluggo/Models/Codables/TagRecord.swift +++ b/Sluggo/Models/Codables/TagRecord.swift @@ -10,10 +10,10 @@ import NullCodable struct TagRecord: Codable { var id: Int + var team_id: Int var object_uuid: UUID var title: String var created: Date @NullCodable var activated: Date? @NullCodable var deactivated: Date? - } diff --git a/Sluggo/Models/Managers/MemberManager.swift b/Sluggo/Models/Managers/MemberManager.swift index 22becfb..69ffeae 100644 --- a/Sluggo/Models/Managers/MemberManager.swift +++ b/Sluggo/Models/Managers/MemberManager.swift @@ -40,6 +40,8 @@ class MemberManager { } + + // TODO: why are no pagingation controls added despite this being paginated? public func listTeamMembers(completionHandler: @escaping(Result, Error>) -> Void) -> Void{ let requestBuilder = URLRequestBuilder(url: makeListUrl()) diff --git a/Sluggo/Models/Managers/StatusManager.swift b/Sluggo/Models/Managers/StatusManager.swift new file mode 100644 index 0000000..0cf6790 --- /dev/null +++ b/Sluggo/Models/Managers/StatusManager.swift @@ -0,0 +1,36 @@ +// +// StatusManager.swift +// Sluggo +// +// Created by Samuel Schmidt on 5/11/21. +// +// Rip pop smoke +// + +import Foundation + +class StatusManager { + static let urlBase = "/statuses/" + private let identity: AppIdentity + + init(identity: AppIdentity) { + self.identity = identity + } + + private func makeDetailUrl(memberRecord: MemberRecord) -> URL { + return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + MemberManager.urlBase + "\(memberRecord.id)/")! + } + + private func makeListUrl(page: Int) -> URL { + return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + MemberManager.urlBase + "?page=\(page)")! + } + + public func listTeamStatuses(page: Int, completionHandler: @escaping(Result, Error>) -> Void) { + let requestBuilder = URLRequestBuilder(url: makeListUrl(page: page)) + .setIdentity(identity: identity) + .setMethod(method: .GET) + + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) + } + +} diff --git a/Sluggo/Models/Managers/TagManager.swift b/Sluggo/Models/Managers/TagManager.swift index a83aff6..b305de4 100644 --- a/Sluggo/Models/Managers/TagManager.swift +++ b/Sluggo/Models/Managers/TagManager.swift @@ -8,5 +8,28 @@ import Foundation class TagManager { + static let urlBase = "/tags/" + private let identity: AppIdentity + + init(identity: AppIdentity) { + self.identity = identity + } + + private func makeDetailUrl(tagRecord: TagRecord) -> URL { + return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + TagManager.urlBase + "\(tagRecord.id)/")! + } + + private func makeListUrl(page: Int) -> URL { + return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + TagManager.urlBase + "?page=\(page)")! + } + + public func listTeamTags(page: Int, completionHandler: @escaping(Result, Error>) -> Void) -> Void{ + + let requestBuilder = URLRequestBuilder(url: makeListUrl(page: page)) + .setIdentity(identity: identity) + .setMethod(method: .GET) + + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) + } } diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index 776e634..b69bf92 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -19,11 +19,14 @@ class TicketManager { return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + TicketManager.urlBase + "\(ticketRecord.id)/")! } + // TODO: nest a params class that will make querying slightly easier public func makeListUrl(page: Int, assignedMember: MemberRecord?) -> URL { var urlString = identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + TicketManager.urlBase + "?page=\(page)" + if let assignedMember = assignedMember { urlString += "&assigned_user__owner__username=\(assignedMember.owner.username)" } + return URL(string: urlString)! } @@ -35,6 +38,7 @@ class TicketManager { JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } + // TODO: public func listTeamTickets(page: Int, completionHandler: @escaping (Result, Error>) -> Void) -> Void { listTeamTickets(page: page, assigned: nil, completionHandler: completionHandler) } @@ -44,6 +48,9 @@ class TicketManager { completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize ticket JSON for makeTicket in TicketManager"))) return } + + // TODO: this works but the page here does effectively nothing. I think for clarity introducing a separate + // function for making the url might be beneficial let requestBuilder = URLRequestBuilder(url: makeListUrl(page: 1, assignedMember: nil)) .setMethod(method: .POST) .setData(data: body) @@ -53,11 +60,18 @@ class TicketManager { } public func updateTicket(ticket: TicketRecord, completionHandler: @escaping(Result) -> Void)-> Void { - let writeTicket = WriteTicketRecord(tag_list: ticket.tag_list, assigned_user: ticket.assigned_user?.id, status: ticket.status, title: ticket.title, description: ticket.description, due_date: ticket.due_date) + let writeTicket = WriteTicketRecord(tag_list: ticket.tag_list, + assigned_user: ticket.assigned_user?.id, + status: ticket.status, + title: ticket.title, + description: ticket.description, + due_date: ticket.due_date) + guard let body = JsonLoader.encode(object: writeTicket) else { completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize ticket JSON for updateTicket in TicketManager"))) return } + let requestBuilder = URLRequestBuilder(url: makeDetailUrl(ticket)) .setMethod(method: .PUT) .setData(data: body) diff --git a/Sluggo/Storyboards/Home.storyboard b/Sluggo/Storyboards/Home.storyboard index b59d559..c3d14fd 100644 --- a/Sluggo/Storyboards/Home.storyboard +++ b/Sluggo/Storyboards/Home.storyboard @@ -1,8 +1,8 @@ - + - + @@ -41,7 +41,7 @@ - + @@ -62,29 +62,29 @@ - + - + - + - + @@ -134,6 +134,7 @@ + @@ -157,7 +158,7 @@ - + diff --git a/Sluggo/Storyboards/Ticket.storyboard b/Sluggo/Storyboards/Ticket.storyboard index c6f994b..fc3ba01 100644 --- a/Sluggo/Storyboards/Ticket.storyboard +++ b/Sluggo/Storyboards/Ticket.storyboard @@ -3,10 +3,26 @@ + + + + + + + + + + + + + + + + @@ -87,13 +103,13 @@ - + - + diff --git a/Sluggo/View Controllers/TeamTableViewController.swift b/Sluggo/View Controllers/TeamTableViewController.swift index 5fee17d..18f58e6 100644 --- a/Sluggo/View Controllers/TeamTableViewController.swift +++ b/Sluggo/View Controllers/TeamTableViewController.swift @@ -25,8 +25,6 @@ class TeamTableViewController: UITableViewController { refreshControl?.addTarget(self, action: #selector(handleRefreshAction), for: .valueChanged) } - - private func preselectRow() { if (teams.count == 0) { return } diff --git a/Sluggo/View Controllers/TicketFilterTableViewController.swift b/Sluggo/View Controllers/TicketFilterTableViewController.swift new file mode 100644 index 0000000..fa42334 --- /dev/null +++ b/Sluggo/View Controllers/TicketFilterTableViewController.swift @@ -0,0 +1,70 @@ +// +// TicketFilterTableViewController.swift +// Sluggo +// +// Created by Samuel Schmidt on 5/11/21. +// + +import UIKit + +enum FilterViewCategories: Int { + case assignedUsers = 0 + case ticketTags = 1 + case ticketStatuses = 2 +} + +class TicketFilterTableViewController: UITableViewController { + + private let assignedUsers: [UserRecord] = [] + private let ticketTags: [TagRecord] = [] + private let ticketStatuses: [StatusRecord] = [] + + override func viewDidLoad() { + super.viewDidLoad() + + // Uncomment the following line to preserve selection between presentations + // self.clearsSelectionOnViewWillAppear = false + + // Uncomment the following line to display an Edit button in the navigation bar for this view controller. + // self.navigationItem.rightBarButtonItem = self.editButtonItem + } + + override func numberOfSections(in tableView: UITableView) -> Int { + // #warning Incomplete implementation, return the number of sections + return 3 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + // #warning Incomplete implementation, return the number of rows + switch(section) { + case FilterViewCategories.assignedUsers.rawValue: + return self.assignedUsers.count + case FilterViewCategories.ticketTags.rawValue: + return self.ticketTags.count + case FilterViewCategories.ticketStatuses.rawValue: + return self.ticketStatuses.count + default: + fatalError("Invalid section count queried") + } + } + +// override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { +// switch(indexPath.section) { +// case HomepageCategories.assigned.rawValue: +// let cell = tableView.dequeueReusableCell(withIdentifier: "TicketCell", for: indexPath) as! TicketTableViewCell +// cell.loadFromTicketRecord(ticket: assignedTickets[indexPath.row]) +// return cell +// case HomepageCategories.pinned.rawValue: +// let cell = tableView.dequeueReusableCell(withIdentifier: "TicketCell", for: indexPath) as! TicketTableViewCell +// cell.loadFromTicketRecord(ticket: pinnedTickets[indexPath.row].ticket) +// return cell +// case HomepageCategories.tags.rawValue: +// let cell = tableView.dequeueReusableCell(withIdentifier: "PlaceholderCell")! +// cell.textLabel?.text = "Not yet implemented." +// return cell +// default: +// fatalError("Accessed section outside of scope, should never occur") +// } +// } + +} diff --git a/SluggoTests/TagTests.swift b/SluggoTests/TagTests.swift new file mode 100644 index 0000000..278acd0 --- /dev/null +++ b/SluggoTests/TagTests.swift @@ -0,0 +1,124 @@ +// +// TagTests.swift +// SluggoTests +// +// Created by Samuel Schmidt on 5/11/21. +// + +import XCTest +@testable import Sluggo + +class TagTests: XCTestCase { + + let workingSingleJson = """ + { + "id": 1, + "team_id": 15, + "object_uuid": "a40cb545-3ada-4656-a02d-2fdaba81fc21", + "title": "word around office, the", + "created": "2021-05-12T04:12:27+0000", + "activated": null, + "deactivated": null + } + """ + + func testExample() { + let tag: TagRecord? = JsonLoader.decode(data: workingSingleJson.data(using: .utf8)!) + + XCTAssertNotNil(tag) + let tagUnwrapped = tag! + + XCTAssertEqual(tagUnwrapped.id, 1) + XCTAssertEqual(tagUnwrapped.team_id, 15) + XCTAssertEqual(tagUnwrapped.object_uuid, + UUID(uuidString: "a40cb545-3ada-4656-a02d-2fdaba81fc21")) + XCTAssertEqual(tagUnwrapped.title, "word around office, the") + } + + + let workingMultipleJson = """ + { + "count": 3, + "next": null, + "previous": null, + "results": [ + { + "id": 1, + "team_id": 15, + "object_uuid": "a40cb545-3ada-4656-a02d-2fdaba81fc21", + "title": "word around office, the", + "created": "2021-05-12T04:12:27+0000", + "activated": null, + "deactivated": null + }, + { + "id": 2, + "team_id": 15, + "object_uuid": "c849aa2a-72b3-4d3b-a919-ea479ebd04d3", + "title": "fadsfddsafdsadfdsadfasdfasdf", + "created": "2021-05-12T04:23:29+0000", + "activated": null, + "deactivated": null + }, + { + "id": 3, + "team_id": 15, + "object_uuid": "3f87b6c2-5023-411c-a139-52e10d9a02a3", + "title": "bcc bghjncvcdfvxz", + "created": "2021-05-12T04:23:34+0000", + "activated": null, + "deactivated": null + } + ] + } + """ + + func testMultiple() { + let tags: PaginatedList? = JsonLoader.decode(data: workingMultipleJson.data(using: .utf8)!) + XCTAssertNotNil(tags) + let tagsUnwrapped = tags! + + XCTAssertEqual(tagsUnwrapped.count, 3) + XCTAssertEqual(tagsUnwrapped.next, nil) + XCTAssertEqual(tagsUnwrapped.previous, nil) + + // the tags individually are probably correct if everything else goes well + } + + let brokenMultipleJson = """ + { + "count": 3, + "next": null, + "previous": null, + "results": [ + { + "id": 1, + "team_id": 15, + "object_uuid": "a40cb545-3ada-4656-a02d-2fdaba81fc21", + "title": "word around office, the", + "created": "2021-05-12T04:12:27+0000", + "activated": null, + "deactivated": null + }, + { + "id": 2, + "team_id": 15, + "object_uuid": "c849aa2a-72b3-4d3b-a919-ea479ebd04d3", + "title": "fadsfddsafdsadfdsadfasdfasdf", + "created": "2021-05-12T04:23:29+0000", + "activated": null, + "deactivated": null + }, + null + ] + } + """ + + func testNoneNil() { + let tags: PaginatedList? = JsonLoader.decode(data: brokenMultipleJson.data(using: .utf8)!) + + // this should fail because the data in the array must never be null + XCTAssertNil(tags) + } + +} From f06d499e2a8507d7b7617e48d42290a401a8caca Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 12 May 2021 00:06:16 -0700 Subject: [PATCH 140/224] unwinding is probably sorted --- Sluggo.xcodeproj/project.pbxproj | 16 ++++ .../Interfaces/TeamPaginatedListable.swift | 12 +++ Sluggo/Models/Managers/MemberManager.swift | 11 +-- Sluggo/Models/Managers/TagManager.swift | 7 +- Sluggo/Models/Managers/TeamManager.swift | 6 +- Sluggo/Models/Methods.swift | 86 +++++++++++++++++++ .../Extensions/ViewControllerExtensions.swift | 6 ++ .../HomeTableViewController.swift | 4 +- .../TeamTableViewController.swift | 62 +++++-------- .../TicketDetailViewController.swift | 2 +- 10 files changed, 158 insertions(+), 54 deletions(-) create mode 100644 Sluggo/Models/Interfaces/TeamPaginatedListable.swift create mode 100644 Sluggo/Models/Methods.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index ac212ce..6f87866 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -27,6 +27,8 @@ 5A25CFA2264B8C8100852035 /* TagTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFA1264B8C8100852035 /* TagTests.swift */; }; 5A25CFA7264B934400852035 /* StatusManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFA6264B934400852035 /* StatusManager.swift */; }; 5A25CFAC264B966300852035 /* TicketFilterTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFAB264B966300852035 /* TicketFilterTableViewController.swift */; }; + 5A25CFB1264B9A9300852035 /* Methods.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFB0264B9A9300852035 /* Methods.swift */; }; + 5A25CFC2264B9AFF00852035 /* TeamPaginatedListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFC1264B9AFF00852035 /* TeamPaginatedListable.swift */; }; 5A673570263BF2D200C4C778 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A67356F263BF2D200C4C778 /* UIColor.swift */; }; 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A79FFE0262E562A00D04320 /* Main.storyboard */; }; 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A79FFE5262E563F00D04320 /* LoginViewController.swift */; }; @@ -102,6 +104,8 @@ 5A25CFA1264B8C8100852035 /* TagTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagTests.swift; sourceTree = ""; }; 5A25CFA6264B934400852035 /* StatusManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusManager.swift; sourceTree = ""; }; 5A25CFAB264B966300852035 /* TicketFilterTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketFilterTableViewController.swift; sourceTree = ""; }; + 5A25CFB0264B9A9300852035 /* Methods.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Methods.swift; sourceTree = ""; }; + 5A25CFC1264B9AFF00852035 /* TeamPaginatedListable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamPaginatedListable.swift; sourceTree = ""; }; 5A67356F263BF2D200C4C778 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; 5A79FFE0262E562A00D04320 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 5A79FFE5262E563F00D04320 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; @@ -173,6 +177,7 @@ 2F81E6E62627E85000B6D86D /* Models */ = { isa = PBXGroup; children = ( + 5A25CFB8264B9AC500852035 /* Interfaces */, 5AB1C8DB262FE29E0010F85E /* Managers */, 5A79FFED262E5FEF00D04320 /* Codables */, 2F3F66E1262DD3BA0040404C /* JsonLoader.swift */, @@ -181,6 +186,7 @@ 051AAB0A2630FD6500BCB9C7 /* Constants.swift */, 5ACC0F052630CA2700DCF83E /* Exceptions.swift */, 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */, + 5A25CFB0264B9A9300852035 /* Methods.swift */, ); path = Models; sourceTree = ""; @@ -194,6 +200,14 @@ path = Components; sourceTree = ""; }; + 5A25CFB8264B9AC500852035 /* Interfaces */ = { + isa = PBXGroup; + children = ( + 5A25CFC1264B9AFF00852035 /* TeamPaginatedListable.swift */, + ); + path = Interfaces; + sourceTree = ""; + }; 5A67356E263BF2C600C4C778 /* Extensions */ = { isa = PBXGroup; children = ( @@ -479,6 +493,7 @@ 3A150A7F263CBA860052934B /* TicketDetailViewController.swift in Sources */, 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */, 5A00A0142639EDEA0085C8C6 /* URLRequestBuilder.swift in Sources */, + 5A25CFC2264B9AFF00852035 /* TeamPaginatedListable.swift in Sources */, 2FD70D5D2636144300D13C75 /* LogoutMessage.swift in Sources */, 5AB1C8EA262FE2EF0010F85E /* MemberManager.swift in Sources */, 5A9C4C1C2640BC4E00B270AB /* TeamTableViewController.swift in Sources */, @@ -501,6 +516,7 @@ 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */, 5A673570263BF2D200C4C778 /* UIColor.swift in Sources */, 3A150A6C26365B670052934B /* TagRecord.swift in Sources */, + 5A25CFB1264B9A9300852035 /* Methods.swift in Sources */, 7D778C06264A374C00D4061A /* ViewControllerExtensions.swift in Sources */, 7D23834B264119050091C7E8 /* HomeTableViewController.swift in Sources */, 5AB1C8EF262FE3210010F85E /* TagManager.swift in Sources */, diff --git a/Sluggo/Models/Interfaces/TeamPaginatedListable.swift b/Sluggo/Models/Interfaces/TeamPaginatedListable.swift new file mode 100644 index 0000000..4486512 --- /dev/null +++ b/Sluggo/Models/Interfaces/TeamPaginatedListable.swift @@ -0,0 +1,12 @@ +// +// TeamListable.swift +// Sluggo +// +// Created by Samuel Schmidt on 5/11/21. +// + +import Foundation + +protocol TeamPaginatedListable { + func listFromTeams(page: Int, completionHandler: @escaping(Result, Error>) -> Void) -> Void +} diff --git a/Sluggo/Models/Managers/MemberManager.swift b/Sluggo/Models/Managers/MemberManager.swift index 69ffeae..b905cc5 100644 --- a/Sluggo/Models/Managers/MemberManager.swift +++ b/Sluggo/Models/Managers/MemberManager.swift @@ -20,8 +20,8 @@ class MemberManager { return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + MemberManager.urlBase + "\(memberRecord.id)/")! } - private func makeListUrl() -> URL { - return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + MemberManager.urlBase)! + private func makeListUrl(page: Int) -> URL { + return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + MemberManager.urlBase + "\(page)")! } public func updateMemberRecord(_ memberRecord: MemberRecord, completionHandler: @escaping(Result) -> Void) -> Void { @@ -42,9 +42,9 @@ class MemberManager { // TODO: why are no pagingation controls added despite this being paginated? - public func listTeamMembers(completionHandler: @escaping(Result, Error>) -> Void) -> Void{ + public func listTeamMembers(page: Int, completionHandler: @escaping(Result, Error>) -> Void) -> Void{ - let requestBuilder = URLRequestBuilder(url: makeListUrl()) + let requestBuilder = URLRequestBuilder(url: makeListUrl(page: page)) .setIdentity(identity: identity) .setMethod(method: .GET) @@ -68,7 +68,8 @@ class MemberManager { let memberPk = teamHash.appending(userHash) // Execute request - let request = URLRequestBuilder(url: URL(string: makeListUrl().absoluteString + "\(memberPk)/")!) + // TODO: this needs to handle pagination correclty! + let request = URLRequestBuilder(url: URL(string: makeListUrl(page: 1).absoluteString + "\(memberPk)/")!) .setMethod(method: .GET) .setIdentity(identity: identity) diff --git a/Sluggo/Models/Managers/TagManager.swift b/Sluggo/Models/Managers/TagManager.swift index b305de4..9e232ac 100644 --- a/Sluggo/Models/Managers/TagManager.swift +++ b/Sluggo/Models/Managers/TagManager.swift @@ -7,7 +7,9 @@ import Foundation -class TagManager { +class TagManager: TeamPaginatedListable { + + static let urlBase = "/tags/" private let identity: AppIdentity @@ -23,8 +25,7 @@ class TagManager { return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + TagManager.urlBase + "?page=\(page)")! } - public func listTeamTags(page: Int, completionHandler: @escaping(Result, Error>) -> Void) -> Void{ - + func listFromTeams(page: Int, completionHandler: @escaping (Result, Error>) -> Void) where T : Decodable, T : Encodable { let requestBuilder = URLRequestBuilder(url: makeListUrl(page: page)) .setIdentity(identity: identity) .setMethod(method: .GET) diff --git a/Sluggo/Models/Managers/TeamManager.swift b/Sluggo/Models/Managers/TeamManager.swift index 227996d..a621242 100644 --- a/Sluggo/Models/Managers/TeamManager.swift +++ b/Sluggo/Models/Managers/TeamManager.swift @@ -7,7 +7,9 @@ import Foundation -class TeamManager { +class TeamManager: TeamPaginatedListable { + + static let urlBase = "api/teams/" private var identity: AppIdentity @@ -15,7 +17,7 @@ class TeamManager { self.identity = identity } - public func listUserTeams(page: Int, completionHandler: @escaping(Result, Error>) -> Void) -> Void { + func listFromTeams(page: Int, completionHandler: @escaping (Result, Error>) -> Void) where T : Decodable, T : Encodable { let requestBuilder = URLRequestBuilder(url: URL(string: identity.baseAddress + TeamManager.urlBase + "?page=\(page)")!) .setIdentity(identity: identity) diff --git a/Sluggo/Models/Methods.swift b/Sluggo/Models/Methods.swift new file mode 100644 index 0000000..6ff066d --- /dev/null +++ b/Sluggo/Models/Methods.swift @@ -0,0 +1,86 @@ +// +// Methods.swift +// Sluggo +// +// Created by Samuel Schmidt on 5/11/21. +// + +import Foundation + +class UnwindState { + let manager: TeamPaginatedListable + var page: Int + var maxCount: Int + var codableArray: [T] + var onSuccess: (([T]) -> Void)? + var onFailure: ((Error) -> Void)? + var after: (() -> Void)? + + init(manager: TeamPaginatedListable, + page: Int, + maxCount: Int, + codableArray: [T], + onSuccess: (([T]) -> Void)?, + onFailure: ((Error) -> Void)?, + after: (() -> Void)?) { + + self.manager = manager + self.page = page + self.maxCount = maxCount + self.codableArray = codableArray + self.onSuccess = onSuccess + self.onFailure = onFailure + self.after = after + } + + static func unwindPaginationRecurse(state: UnwindState) -> Void { + + state.manager.listFromTeams(page: state.page) { + (result: Result, Error>) -> Void in + + switch (result) { + case .success(let record): + state.maxCount = record.count + + for entry in record.results { + state.codableArray.append(entry) + } + + if (state.codableArray.count < state.maxCount) { + state.page += 1 + + // tail recurse to get remaining data + unwindPaginationRecurse(state: state) + } else { + state.onSuccess?(state.codableArray) + state.after?() + } + break + case .failure(let error): + print("error occured") + state.onFailure?(error) + state.after?() + break + } + }; + } + + static func unwindPagination(manager: TeamPaginatedListable, + startingPage: Int, + onSuccess: (([T]) -> Void)?, + onFailure: ((Error) -> Void)?, + after: (() -> Void)?) -> Void { + + let state = UnwindState(manager: manager, + page: startingPage, + maxCount: 0, + codableArray: [], + onSuccess: onSuccess, + onFailure: onFailure, + after: after) + + unwindPaginationRecurse(state: state) + } +} + + diff --git a/Sluggo/View Controllers/Extensions/ViewControllerExtensions.swift b/Sluggo/View Controllers/Extensions/ViewControllerExtensions.swift index 9c639eb..bfba86d 100644 --- a/Sluggo/View Controllers/Extensions/ViewControllerExtensions.swift +++ b/Sluggo/View Controllers/Extensions/ViewControllerExtensions.swift @@ -16,6 +16,12 @@ extension UIViewController { UIAlertController.createAndPresentError(vc: self, error: error) } + func presentErrorFromMainThread(error: Error) { + DispatchQueue.main.async { + self.presentError(error: error) + } + } + func processResult(result: Result, onSuccess: @escaping ((T) -> Void), onError: ((Error) -> Void)?, after: (() -> Void)?) { DispatchQueue.main.sync { switch(result) { diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index 311e1e5..15f58f5 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -87,7 +87,9 @@ class HomeTableViewController: UITableViewController { // Not safe to make the call // Might make sense to migrate to optionals in data layer // for future iterations of the app - self.refreshControl?.endRefreshing() + DispatchQueue.main.async { + self.refreshControl?.endRefreshing() + } return; } diff --git a/Sluggo/View Controllers/TeamTableViewController.swift b/Sluggo/View Controllers/TeamTableViewController.swift index 18f58e6..3f4aa38 100644 --- a/Sluggo/View Controllers/TeamTableViewController.swift +++ b/Sluggo/View Controllers/TeamTableViewController.swift @@ -14,6 +14,7 @@ class TeamTableViewController: UITableViewController { private var fetchingTeams: [TeamRecord] = [] private var teams: [TeamRecord] = [] private var isFetching = false + private var semaphore = DispatchSemaphore(value: 1) override func viewDidLoad() { self.configureRefreshControl() @@ -56,51 +57,28 @@ class TeamTableViewController: UITableViewController { } @objc func handleRefreshAction() { - // enter the critical section - // do not wait - if (isFetching) { return } - - isFetching = true - self.loadData(page: 1) - } - - private func loadData(page: Int) { - let teamManager = TeamManager(identity: identity) - teamManager.listUserTeams(page: page) { result in - switch(result) { - case .success(let record): - self.maxNumber = record.count - - let pageOffset = (page - 1) * self.identity.pageSize - if (pageOffset < self.fetchingTeams.count) { - self.fetchingTeams.removeSubrange(pageOffset...self.fetchingTeams.count-1) - } - - for entry in record.results { - self.fetchingTeams.append(entry) - } - - if (self.fetchingTeams.count >= self.maxNumber) { - DispatchQueue.main.async { - self.refreshControl?.endRefreshing() - self.teams = Array(self.fetchingTeams) - self.fetchingTeams.removeAll() - self.tableView.reloadData() - self.isFetching = false - self.preselectRow() - } - return - } else { - self.loadData(page: page + 1) - } - - break; - case .failure(let error): + + DispatchQueue.global(qos: .userInitiated).async { + self.semaphore.wait() + + let onSuccess = { (teams: [TeamRecord]) -> Void in + self.teams = teams + } + + let after = { () -> Void in DispatchQueue.main.async { - let alert = UIAlertController.errorController(error: error) - self.present(alert, animated: true, completion: nil) + self.refreshControl?.endRefreshing() + self.preselectRow() } + self.semaphore.signal() } + + let teamManager = TeamManager(identity: self.identity) + UnwindState.unwindPagination(manager: teamManager, + startingPage: 1, + onSuccess: onSuccess, + onFailure: self.presentErrorFromMainThread, + after: after) } } } diff --git a/Sluggo/View Controllers/TicketDetailViewController.swift b/Sluggo/View Controllers/TicketDetailViewController.swift index 71163d9..d8096c1 100644 --- a/Sluggo/View Controllers/TicketDetailViewController.swift +++ b/Sluggo/View Controllers/TicketDetailViewController.swift @@ -48,7 +48,7 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate, UIPicker let memberManager = MemberManager(identity: self.identity) - memberManager.listTeamMembers(){ result in + memberManager.listTeamMembers(page: 1){ result in switch(result){ case .success(let record): self.teamMembers = [nil] From 56bd4568b20ed74e83d7aef1e5a03fd9b5365c61 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 12 May 2021 00:40:27 -0700 Subject: [PATCH 141/224] fix bugs introduced by yours truly --- Sluggo/Models/Managers/MemberManager.swift | 18 ++--- Sluggo/Models/Managers/StatusManager.swift | 6 +- .../TicketDetailViewController.swift | 2 +- .../TicketFilterTableViewController.swift | 71 +++++++++++++++++-- 4 files changed, 80 insertions(+), 17 deletions(-) diff --git a/Sluggo/Models/Managers/MemberManager.swift b/Sluggo/Models/Managers/MemberManager.swift index b905cc5..9b68f74 100644 --- a/Sluggo/Models/Managers/MemberManager.swift +++ b/Sluggo/Models/Managers/MemberManager.swift @@ -8,7 +8,9 @@ import Foundation import CryptoKit -class MemberManager { +class MemberManager: TeamPaginatedListable { + + static let urlBase = "/members/" private var identity: AppIdentity @@ -16,12 +18,12 @@ class MemberManager { self.identity = identity } - private func makeDetailUrl(memberRecord: MemberRecord) -> URL { - return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + MemberManager.urlBase + "\(memberRecord.id)/")! + private func makeDetailUrl(id: String) -> URL { + return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + MemberManager.urlBase + "\(id)/")! } private func makeListUrl(page: Int) -> URL { - return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + MemberManager.urlBase + "\(page)")! + return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + MemberManager.urlBase + "?page=\(page)")! } public func updateMemberRecord(_ memberRecord: MemberRecord, completionHandler: @escaping(Result) -> Void) -> Void { @@ -31,7 +33,7 @@ class MemberManager { return } - let requestBuilder = URLRequestBuilder(url: makeDetailUrl(memberRecord: memberRecord)) + let requestBuilder = URLRequestBuilder(url: makeDetailUrl(id: memberRecord.id)) .setData(data: body) .setMethod(method: .PUT) .setIdentity(identity: identity) @@ -40,9 +42,7 @@ class MemberManager { } - - // TODO: why are no pagingation controls added despite this being paginated? - public func listTeamMembers(page: Int, completionHandler: @escaping(Result, Error>) -> Void) -> Void{ + func listFromTeams(page: Int, completionHandler: @escaping (Result, Error>) -> Void) where T : Decodable, T : Encodable { let requestBuilder = URLRequestBuilder(url: makeListUrl(page: page)) .setIdentity(identity: identity) @@ -69,7 +69,7 @@ class MemberManager { // Execute request // TODO: this needs to handle pagination correclty! - let request = URLRequestBuilder(url: URL(string: makeListUrl(page: 1).absoluteString + "\(memberPk)/")!) + let request = URLRequestBuilder(url: URL(string: makeDetailUrl(id: memberPk).absoluteString)!) .setMethod(method: .GET) .setIdentity(identity: identity) diff --git a/Sluggo/Models/Managers/StatusManager.swift b/Sluggo/Models/Managers/StatusManager.swift index 0cf6790..ed5a982 100644 --- a/Sluggo/Models/Managers/StatusManager.swift +++ b/Sluggo/Models/Managers/StatusManager.swift @@ -9,7 +9,8 @@ import Foundation -class StatusManager { +class StatusManager: TeamPaginatedListable { + static let urlBase = "/statuses/" private let identity: AppIdentity @@ -25,12 +26,11 @@ class StatusManager { return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + MemberManager.urlBase + "?page=\(page)")! } - public func listTeamStatuses(page: Int, completionHandler: @escaping(Result, Error>) -> Void) { + func listFromTeams(page: Int, completionHandler: @escaping (Result, Error>) -> Void) where T : Decodable, T : Encodable { let requestBuilder = URLRequestBuilder(url: makeListUrl(page: page)) .setIdentity(identity: identity) .setMethod(method: .GET) JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } - } diff --git a/Sluggo/View Controllers/TicketDetailViewController.swift b/Sluggo/View Controllers/TicketDetailViewController.swift index d8096c1..78d0f88 100644 --- a/Sluggo/View Controllers/TicketDetailViewController.swift +++ b/Sluggo/View Controllers/TicketDetailViewController.swift @@ -48,7 +48,7 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate, UIPicker let memberManager = MemberManager(identity: self.identity) - memberManager.listTeamMembers(page: 1){ result in + memberManager.listFromTeams(page: 1){ (result: Result, Error>) -> Void in switch(result){ case .success(let record): self.teamMembers = [nil] diff --git a/Sluggo/View Controllers/TicketFilterTableViewController.swift b/Sluggo/View Controllers/TicketFilterTableViewController.swift index fa42334..de73073 100644 --- a/Sluggo/View Controllers/TicketFilterTableViewController.swift +++ b/Sluggo/View Controllers/TicketFilterTableViewController.swift @@ -15,12 +15,17 @@ enum FilterViewCategories: Int { class TicketFilterTableViewController: UITableViewController { - private let assignedUsers: [UserRecord] = [] - private let ticketTags: [TagRecord] = [] - private let ticketStatuses: [StatusRecord] = [] + // TODO: wire this shit together + private let identity = AppIdentity() + private var assignedUsers: [MemberRecord] = [] + private var ticketTags: [TagRecord] = [] + private var ticketStatuses: [StatusRecord] = [] + private let semaphore = DispatchSemaphore(value: 1) + private static let numSections = 3 override func viewDidLoad() { super.viewDidLoad() + configureRefreshControl() // Uncomment the following line to preserve selection between presentations // self.clearsSelectionOnViewWillAppear = false @@ -31,7 +36,7 @@ class TicketFilterTableViewController: UITableViewController { override func numberOfSections(in tableView: UITableView) -> Int { // #warning Incomplete implementation, return the number of sections - return 3 + return TicketFilterTableViewController.numSections } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { @@ -47,6 +52,64 @@ class TicketFilterTableViewController: UITableViewController { fatalError("Invalid section count queried") } } + + // MARK: refresh bullshit + func configureRefreshControl() { + refreshControl = UIRefreshControl() + refreshControl?.addTarget(self, action: #selector(handleRefreshAction), for: .valueChanged) + } + + @objc func handleRefreshAction() { + + DispatchQueue.global(qos: .userInitiated).async { + self.semaphore.wait() + + // this mutex synchronizes the different api calls + // and access to the completed count + let mutex = DispatchSemaphore(value: 1) + var completed = 0 + + let after = { () -> Void in + mutex.wait() + + completed += 1 + if (completed == TicketFilterTableViewController.numSections) { + DispatchQueue.main.async { + self.refreshControl?.endRefreshing() + } + self.semaphore.signal() + } + mutex.signal() + } + + let tagManager = TagManager(identity: self.identity) + UnwindState.unwindPagination(manager: tagManager, + startingPage: 1, + onSuccess: { (tags: [TagRecord]) -> Void in + self.ticketTags = tags + }, + onFailure: self.presentErrorFromMainThread, + after: after) + + let statusManger = StatusManager(identity: self.identity) + UnwindState.unwindPagination(manager: statusManger, + startingPage: 1, + onSuccess: { (statuses: [StatusRecord]) -> Void in + self.ticketStatuses = statuses + }, + onFailure: self.presentErrorFromMainThread, + after: after) + + let memberManager = MemberManager(identity: self.identity) + UnwindState.unwindPagination(manager: memberManager, + startingPage: 1, + onSuccess: { (members: [MemberRecord]) -> Void in + self.assignedUsers = members + }, + onFailure: self.presentErrorFromMainThread, + after: after) + } + } // override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // switch(indexPath.section) { From 8a96a826a0f8383fffb41b54ec30e3cab4f24f07 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Wed, 12 May 2021 01:01:33 -0700 Subject: [PATCH 142/224] Started work on ticketDetailTableView, current way to get there is to select then deselect the ticket detail title in edit mode. --- Sluggo.xcodeproj/project.pbxproj | 4 + Sluggo/Storyboards/TicketDetail.storyboard | 167 ++++++++++++++++-- .../TicketDetailTableViewController.swift | 77 ++++++++ .../TicketDetailViewController.swift | 4 + 4 files changed, 237 insertions(+), 15 deletions(-) create mode 100644 Sluggo/View Controllers/TicketDetailTableViewController.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 8400af2..34cf190 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 2FD70D5D2636144300D13C75 /* LogoutMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FD70D5C2636144300D13C75 /* LogoutMessage.swift */; }; 2FEF5DA9262D0E7C00434763 /* PaginatedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */; }; 2FEF5DAE262D1C4900434763 /* MemberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF5DAD262D1C4900434763 /* MemberTests.swift */; }; + 2FF89024264BBAC600D50B69 /* TicketDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FF89023264BBAC600D50B69 /* TicketDetailTableViewController.swift */; }; 3A150A6726365A9A0052934B /* StatusRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A6626365A9A0052934B /* StatusRecord.swift */; }; 3A150A6C26365B670052934B /* TagRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A6B26365B670052934B /* TagRecord.swift */; }; 3A150A77263CB5F70052934B /* TicketDetail.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3A150A76263CB5F70052934B /* TicketDetail.storyboard */; }; @@ -90,6 +91,7 @@ 2FD70D5C2636144300D13C75 /* LogoutMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutMessage.swift; sourceTree = ""; }; 2FEF5DA8262D0E7C00434763 /* PaginatedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginatedList.swift; sourceTree = ""; }; 2FEF5DAD262D1C4900434763 /* MemberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberTests.swift; sourceTree = ""; }; + 2FF89023264BBAC600D50B69 /* TicketDetailTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketDetailTableViewController.swift; sourceTree = ""; }; 3A150A6626365A9A0052934B /* StatusRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusRecord.swift; sourceTree = ""; }; 3A150A6B26365B670052934B /* TagRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagRecord.swift; sourceTree = ""; }; 3A150A76263CB5F70052934B /* TicketDetail.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TicketDetail.storyboard; sourceTree = ""; }; @@ -319,6 +321,7 @@ 3A150A7E263CBA860052934B /* TicketDetailViewController.swift */, 5A9C4C1B2640BC4E00B270AB /* TeamTableViewController.swift */, 5A9C4C202641113D00B270AB /* TeamSelectorContainerViewController.swift */, + 2FF89023264BBAC600D50B69 /* TicketDetailTableViewController.swift */, ); path = "View Controllers"; sourceTree = ""; @@ -476,6 +479,7 @@ 2F3AD7AD2627C8EF00B61A97 /* TeamRecord.swift in Sources */, 7DC54AE526437AB3000C8300 /* PinnedTicketRecord.swift in Sources */, 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */, + 2FF89024264BBAC600D50B69 /* TicketDetailTableViewController.swift in Sources */, 5AC6459026412F0C00F46137 /* TeamTableViewCell.swift in Sources */, 051AAB0B2630FD6500BCB9C7 /* Constants.swift in Sources */, 2F3F66E2262DD3BA0040404C /* JsonLoader.swift in Sources */, diff --git a/Sluggo/Storyboards/TicketDetail.storyboard b/Sluggo/Storyboards/TicketDetail.storyboard index b40a8ef..17d41b0 100644 --- a/Sluggo/Storyboards/TicketDetail.storyboard +++ b/Sluggo/Storyboards/TicketDetail.storyboard @@ -1,8 +1,8 @@ - - + + - + @@ -13,16 +13,19 @@ - + - + + + + - + @@ -32,21 +35,21 @@ - + - + - + - + @@ -84,15 +87,15 @@ - + - - + + @@ -149,6 +152,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sluggo/View Controllers/TicketDetailTableViewController.swift b/Sluggo/View Controllers/TicketDetailTableViewController.swift new file mode 100644 index 0000000..0b9d940 --- /dev/null +++ b/Sluggo/View Controllers/TicketDetailTableViewController.swift @@ -0,0 +1,77 @@ +// +// TicketDetailTableViewController.swift +// Sluggo +// +// Created by Andrew Gavgavian on 5/12/21. +// + +import UIKit + +class TicketDetailTableViewController: UITableViewController { + + // MARK: Outlets + @IBOutlet var ticketTitle: UITextField! + @IBOutlet var ticketDescription: UITextView! + @IBOutlet var assignedLabel: UILabel! + @IBOutlet var dueDatePicker: UIDatePicker! + @IBOutlet var dueDateSwitch: UISwitch! + + // MARK: Variables + var identity: AppIdentity + var ticket: TicketRecord? + var completion: (() ->Void)? + var pickerView: UIPickerView = UIPickerView() + var editingTicket = false + + var teamMembers: [MemberRecord?] = [nil] + var currentMember: MemberRecord? = nil + + init? (coder: NSCoder, identity: AppIdentity, ticket: TicketRecord?, completion: (() -> Void)?) { + self.identity = identity + self.completion = completion + self.ticket = ticket + super.init(coder: coder) + } + + required init? (coder: NSCoder) { + fatalError("must be initialized with identity") + } + + override func viewDidLoad() { + super.viewDidLoad() + + dueDatePicker.isEnabled = dueDateSwitch.isOn + + let memberManager = MemberManager(identity: self.identity) + memberManager.listTeamMembers(){ result in + switch(result){ + case .success(let record): + self.teamMembers = [nil] + for user in record.results{ + self.teamMembers.append(user) + } + + case .failure(let error): + DispatchQueue.main.async { + let alert = UIAlertController.errorController(error: error) + self.present(alert, animated: true, completion: nil) + } + } + } + + updateUI() + + } + + func updateUI() { + + ticketTitle.text = self.ticket?.title ?? "" + ticketDescription.text = ticket?.description ?? "" + assignedLabel.text = ticket?.assigned_user?.owner.username ?? "No Assigned User" + dueDatePicker.date = ticket?.due_date ?? Date() + dueDateSwitch.isEnabled = (ticket?.due_date != nil) + dueDatePicker.isEnabled = dueDateSwitch.isEnabled + } + + +} diff --git a/Sluggo/View Controllers/TicketDetailViewController.swift b/Sluggo/View Controllers/TicketDetailViewController.swift index 71163d9..6df069f 100644 --- a/Sluggo/View Controllers/TicketDetailViewController.swift +++ b/Sluggo/View Controllers/TicketDetailViewController.swift @@ -241,6 +241,10 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate, UIPicker } + @IBSegueAction func segueToDetail(_ coder: NSCoder) -> TicketDetailTableViewController? { + return TicketDetailTableViewController(coder: coder, identity: identity, ticket: ticket, completion: nil) + } + //MARK: UIPickerView manager From c810ceb530b42b6457352405cd2066a4836fab87 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 12 May 2021 01:47:22 -0700 Subject: [PATCH 143/224] gettin data --- Sluggo/Models/JsonLoader.swift | 22 ++++++-- Sluggo/Models/Managers/StatusManager.swift | 6 +-- Sluggo/Storyboards/Main.storyboard | 5 -- Sluggo/Storyboards/Ticket.storyboard | 54 +++++++++++++++---- .../TicketFilterTableViewController.swift | 11 ++-- .../TicketListController.swift | 34 ++++++++++-- SluggoTests/MemberTests.swift | 9 ++++ 7 files changed, 109 insertions(+), 32 deletions(-) diff --git a/Sluggo/Models/JsonLoader.swift b/Sluggo/Models/JsonLoader.swift index fe2f50e..ab8acd0 100644 --- a/Sluggo/Models/JsonLoader.swift +++ b/Sluggo/Models/JsonLoader.swift @@ -13,12 +13,24 @@ class JsonLoader { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 - guard let pagRec = try? decoder.decode(T.self, from: data) else { - print("Failed to decode JSON data into object representation for object initialization.") - return nil + var decodedValue: T? = nil + do { + decodedValue = try decoder.decode(T.self, from: data) + } catch DecodingError.dataCorrupted(let context) { + print("\(context.codingPath) . \(context.debugDescription)") + } catch (let context) { + switch (context) { + case DecodingError.dataCorrupted(let value): + print(value.debugDescription) + break + default: + print(context.localizedDescription) + + } } - - return pagRec + + + return decodedValue } static func encode(object data: T) -> Data? { diff --git a/Sluggo/Models/Managers/StatusManager.swift b/Sluggo/Models/Managers/StatusManager.swift index ed5a982..d040663 100644 --- a/Sluggo/Models/Managers/StatusManager.swift +++ b/Sluggo/Models/Managers/StatusManager.swift @@ -18,12 +18,12 @@ class StatusManager: TeamPaginatedListable { self.identity = identity } - private func makeDetailUrl(memberRecord: MemberRecord) -> URL { - return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + MemberManager.urlBase + "\(memberRecord.id)/")! + private func makeDetailUrl(statusRecord: StatusRecord) -> URL { + return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + StatusManager.urlBase + "\(statusRecord.id)/")! } private func makeListUrl(page: Int) -> URL { - return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + MemberManager.urlBase + "?page=\(page)")! + return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + StatusManager.urlBase + "?page=\(page)")! } func listFromTeams(page: Int, completionHandler: @escaping (Result, Error>) -> Void) where T : Decodable, T : Encodable { diff --git a/Sluggo/Storyboards/Main.storyboard b/Sluggo/Storyboards/Main.storyboard index 18ae89b..982d8ae 100644 --- a/Sluggo/Storyboards/Main.storyboard +++ b/Sluggo/Storyboards/Main.storyboard @@ -3,7 +3,6 @@ - @@ -232,9 +231,6 @@ - - - @@ -287,7 +283,6 @@ - diff --git a/Sluggo/Storyboards/Ticket.storyboard b/Sluggo/Storyboards/Ticket.storyboard index fc3ba01..74eebef 100644 --- a/Sluggo/Storyboards/Ticket.storyboard +++ b/Sluggo/Storyboards/Ticket.storyboard @@ -3,25 +3,38 @@ - - - + + - - - + + + - - - - + + + + + + + + + + + + + + + + + + - + @@ -136,6 +149,25 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Sluggo/View Controllers/TicketFilterTableViewController.swift b/Sluggo/View Controllers/TicketFilterTableViewController.swift index de73073..a2cbaa2 100644 --- a/Sluggo/View Controllers/TicketFilterTableViewController.swift +++ b/Sluggo/View Controllers/TicketFilterTableViewController.swift @@ -16,8 +16,8 @@ enum FilterViewCategories: Int { class TicketFilterTableViewController: UITableViewController { // TODO: wire this shit together - private let identity = AppIdentity() - private var assignedUsers: [MemberRecord] = [] + var identity: AppIdentity! = nil + private var teamMembers: [MemberRecord] = [] private var ticketTags: [TagRecord] = [] private var ticketStatuses: [StatusRecord] = [] private let semaphore = DispatchSemaphore(value: 1) @@ -43,7 +43,7 @@ class TicketFilterTableViewController: UITableViewController { // #warning Incomplete implementation, return the number of rows switch(section) { case FilterViewCategories.assignedUsers.rawValue: - return self.assignedUsers.count + return self.teamMembers.count case FilterViewCategories.ticketTags.rawValue: return self.ticketTags.count case FilterViewCategories.ticketStatuses.rawValue: @@ -53,7 +53,7 @@ class TicketFilterTableViewController: UITableViewController { } } - // MARK: refresh bullshit + // MARK: refresh stuff func configureRefreshControl() { refreshControl = UIRefreshControl() refreshControl?.addTarget(self, action: #selector(handleRefreshAction), for: .valueChanged) @@ -77,6 +77,7 @@ class TicketFilterTableViewController: UITableViewController { DispatchQueue.main.async { self.refreshControl?.endRefreshing() } + self.semaphore.signal() } mutex.signal() @@ -104,7 +105,7 @@ class TicketFilterTableViewController: UITableViewController { UnwindState.unwindPagination(manager: memberManager, startingPage: 1, onSuccess: { (members: [MemberRecord]) -> Void in - self.assignedUsers = members + self.teamMembers = members }, onFailure: self.presentErrorFromMainThread, after: after) diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index 32ea1e7..9eee003 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -26,11 +26,26 @@ class TicketListController: UITableViewController { // MARK: initial actions override func viewDidLoad() { configureRefreshControl() + setupMenuBar() loadData(page: 1) - navigationItem.rightBarButtonItem = UIBarButtonItem( barButtonSystemItem: .add, target: self, action: #selector(connectPopUp)) + NotificationCenter.default.addObserver(self, selector: #selector(handleRefreshAction), name: .refreshTrigger, object: nil) } + func setupMenuBar() { + + let saveMenu = UIMenu(title: "", children: [ + UIAction(title: "Create New", image: UIImage(systemName: "plus")) { _ in + self.connectPopUp() + }, + UIAction(title: "Filter", image: UIImage(systemName: "folder")) { _ in + self.launchFilterPopup() + } + ]) + + navigationItem.rightBarButtonItem = UIBarButtonItem( image: UIImage(systemName: "ellipsis"), primaryAction: nil, menu: saveMenu) + } + func configureRefreshControl() { refreshControl = UIRefreshControl() refreshControl?.addTarget(self, action: #selector(handleRefreshAction), for: .valueChanged) @@ -55,7 +70,8 @@ class TicketListController: UITableViewController { } } - @objc func connectPopUp() { + // MARK: menu item delegates + func connectPopUp() { let identity = self.identity let detailStoryboard = UIStoryboard(name: "TicketDetail", bundle: nil) if let vc = detailStoryboard.instantiateViewController(identifier: "TicketDetail", creator:{ coder in @@ -67,6 +83,19 @@ class TicketListController: UITableViewController { } } + func launchFilterPopup() { + // launch the view controller holding the filter view + if let vc = storyboard?.instantiateViewController(identifier: "FilterNavigationController") { + if let child = vc.children[0] as? TicketFilterTableViewController { + child.identity = self.identity + } + + self.present(vc, animated: true, completion: nil) + } + } + + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.tickets.count } @@ -121,7 +150,6 @@ class TicketListController: UITableViewController { } } } - } extension Notification.Name { diff --git a/SluggoTests/MemberTests.swift b/SluggoTests/MemberTests.swift index 43d319d..62f2cec 100644 --- a/SluggoTests/MemberTests.swift +++ b/SluggoTests/MemberTests.swift @@ -47,6 +47,15 @@ class MemberTests: XCTestCase { } """ + let testPaginated = """ + {"count":1,"next":null,"previous":null,"results":[{"id":"c74d97b01eae257e44aa9d5bade97baf332532dcfaa1cbf61e2a266bd723612c","owner":{"id":1,"email":"sam@sam.sam","first_name":"","last_name":"","username":"sam"},"team_id":16,"object_uuid":"eb486ea8-660d-4d3b-850b-80c42bb4301f","role":"AD","bio":"asdf","pronouns":null,"created":"2021-05-06T20:58:47+0000","activated":null,"deactivated":null}]} + """ + + func testSerializesMultiple() { + let members: PaginatedList? = JsonLoader.decode(data: testPaginated.data(using: .utf8)!) + XCTAssertNotNil(members) + } + func testMemberDoesDeserialize() { let member: MemberRecord? = JsonLoader.decode(data: testWorkingJson.data(using: .utf8)!) XCTAssertNotNil(member) From aeeacb72bbb15f886b7c6a798999a5464d04e5ac Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 12 May 2021 02:24:06 -0700 Subject: [PATCH 144/224] query parameter class --- Sluggo.xcodeproj/project.pbxproj | 4 ++ Sluggo/Models/Managers/TicketManager.swift | 24 ++++++------ Sluggo/Models/TicketFilterParameters.swift | 39 +++++++++++++++++++ .../HomeTableViewController.swift | 4 +- .../TicketFilterTableViewController.swift | 15 ++++++- .../TicketListController.swift | 3 +- 6 files changed, 73 insertions(+), 16 deletions(-) create mode 100644 Sluggo/Models/TicketFilterParameters.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 6f87866..4103569 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -29,6 +29,7 @@ 5A25CFAC264B966300852035 /* TicketFilterTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFAB264B966300852035 /* TicketFilterTableViewController.swift */; }; 5A25CFB1264B9A9300852035 /* Methods.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFB0264B9A9300852035 /* Methods.swift */; }; 5A25CFC2264B9AFF00852035 /* TeamPaginatedListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFC1264B9AFF00852035 /* TeamPaginatedListable.swift */; }; + 5A25CFDF264BCF2C00852035 /* TicketFilterParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFDE264BCF2C00852035 /* TicketFilterParameters.swift */; }; 5A673570263BF2D200C4C778 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A67356F263BF2D200C4C778 /* UIColor.swift */; }; 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A79FFE0262E562A00D04320 /* Main.storyboard */; }; 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A79FFE5262E563F00D04320 /* LoginViewController.swift */; }; @@ -106,6 +107,7 @@ 5A25CFAB264B966300852035 /* TicketFilterTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketFilterTableViewController.swift; sourceTree = ""; }; 5A25CFB0264B9A9300852035 /* Methods.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Methods.swift; sourceTree = ""; }; 5A25CFC1264B9AFF00852035 /* TeamPaginatedListable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamPaginatedListable.swift; sourceTree = ""; }; + 5A25CFDE264BCF2C00852035 /* TicketFilterParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketFilterParameters.swift; sourceTree = ""; }; 5A67356F263BF2D200C4C778 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; 5A79FFE0262E562A00D04320 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 5A79FFE5262E563F00D04320 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; @@ -187,6 +189,7 @@ 5ACC0F052630CA2700DCF83E /* Exceptions.swift */, 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */, 5A25CFB0264B9A9300852035 /* Methods.swift */, + 5A25CFDE264BCF2C00852035 /* TicketFilterParameters.swift */, ); path = Models; sourceTree = ""; @@ -490,6 +493,7 @@ 5A9C4C212641113D00B270AB /* TeamSelectorContainerViewController.swift in Sources */, 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */, 5AB1C8FC262FF1690010F85E /* TicketRecord.swift in Sources */, + 5A25CFDF264BCF2C00852035 /* TicketFilterParameters.swift in Sources */, 3A150A7F263CBA860052934B /* TicketDetailViewController.swift in Sources */, 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */, 5A00A0142639EDEA0085C8C6 /* URLRequestBuilder.swift in Sources */, diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index b69bf92..118d1c1 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -19,30 +19,28 @@ class TicketManager { return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + TicketManager.urlBase + "\(ticketRecord.id)/")! } - // TODO: nest a params class that will make querying slightly easier - public func makeListUrl(page: Int, assignedMember: MemberRecord?) -> URL { + public func makeListUrl(page: Int, queryParams: TicketFilterParameters) -> URL { var urlString = identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + TicketManager.urlBase + "?page=\(page)" - - if let assignedMember = assignedMember { - urlString += "&assigned_user__owner__username=\(assignedMember.owner.username)" - } + urlString += queryParams.toParamString() return URL(string: urlString)! } - public func listTeamTickets(page: Int, assigned: MemberRecord?, completionHandler: @escaping (Result, Error>) -> Void) -> Void { - let requestBuilder = URLRequestBuilder(url: makeListUrl(page: page, assignedMember: assigned)) + // TODO: nest a params class that will make querying slightly easier + public func makeListUrl(page: Int, assignedMember: MemberRecord?) -> URL { + let queryParams = TicketFilterParameters(assignedUser: assignedMember) + + return makeListUrl(page: page, queryParams: queryParams) + } + + public func listTeamTickets(page: Int, queryParams: TicketFilterParameters, completionHandler: @escaping (Result, Error>) -> Void) -> Void { + let requestBuilder = URLRequestBuilder(url: makeListUrl(page: page, queryParams: queryParams)) .setMethod(method: .GET) .setIdentity(identity: self.identity) JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } - // TODO: - public func listTeamTickets(page: Int, completionHandler: @escaping (Result, Error>) -> Void) -> Void { - listTeamTickets(page: page, assigned: nil, completionHandler: completionHandler) - } - public func makeTicket(ticket: WriteTicketRecord, completionHandler: @escaping(Result) -> Void)->Void{ guard let body = JsonLoader.encode(object: ticket) else { completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize ticket JSON for makeTicket in TicketManager"))) diff --git a/Sluggo/Models/TicketFilterParameters.swift b/Sluggo/Models/TicketFilterParameters.swift new file mode 100644 index 0000000..766fd77 --- /dev/null +++ b/Sluggo/Models/TicketFilterParameters.swift @@ -0,0 +1,39 @@ +// +// FilterParameters.swift +// Sluggo +// +// Created by Samuel Schmidt on 5/12/21. +// + +import Foundation + +struct TicketFilterParameters { + var assignedUser: MemberRecord? = nil + var ticketTag: TagRecord? = nil + var ticketStatus: StatusRecord? = nil + + init(assignedUser: MemberRecord? = nil, ticketTag: TagRecord? = nil, ticketStatus: StatusRecord? = nil) { + self.assignedUser = assignedUser + self.ticketTag = ticketTag + self.ticketStatus = ticketStatus + } + + // does not include the initial ? so that it may work with pagination + func toParamString() -> String { + var paramString = "" + + if let member = assignedUser { + paramString += "&assigned_user__owner__username=\(member.owner.username)" + } + + if let tag = ticketTag { + paramString += "&tag_list__title=\(tag.title)" + } + + if let status = ticketStatus { + paramString += "&status__title=\(status.title)" + } + + return paramString + } +} diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index 15f58f5..89e3801 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -61,7 +61,9 @@ class HomeTableViewController: UITableViewController { // Ideally, this could be extended with some other endpoints to get the total counts // but this will suffice for now let ticketsManager = TicketManager(identity) - ticketsManager.listTeamTickets(page: 1, assigned: member) { result in + let queryParams = TicketFilterParameters(assignedUser: member) + + ticketsManager.listTeamTickets(page: 1, queryParams: queryParams) { result in self.processResult(result: result, onSuccess: { retrievedAssignedTickets in self.assignedTickets = retrievedAssignedTickets.results self.tableView.reloadSections([HomepageCategories.assigned.rawValue], with: .automatic) diff --git a/Sluggo/View Controllers/TicketFilterTableViewController.swift b/Sluggo/View Controllers/TicketFilterTableViewController.swift index a2cbaa2..80bbb05 100644 --- a/Sluggo/View Controllers/TicketFilterTableViewController.swift +++ b/Sluggo/View Controllers/TicketFilterTableViewController.swift @@ -15,8 +15,11 @@ enum FilterViewCategories: Int { class TicketFilterTableViewController: UITableViewController { - // TODO: wire this shit together + // TODO: wire this wonderful code together var identity: AppIdentity! = nil + var completion: ((TicketFilterParameters) -> Void)? + + private var filterParams: TicketFilterParameters = TicketFilterParameters() private var teamMembers: [MemberRecord] = [] private var ticketTags: [TagRecord] = [] private var ticketStatuses: [StatusRecord] = [] @@ -26,6 +29,7 @@ class TicketFilterTableViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() configureRefreshControl() + configureBarItems() // Uncomment the following line to preserve selection between presentations // self.clearsSelectionOnViewWillAppear = false @@ -33,6 +37,15 @@ class TicketFilterTableViewController: UITableViewController { // Uncomment the following line to display an Edit button in the navigation bar for this view controller. // self.navigationItem.rightBarButtonItem = self.editButtonItem } + + func configureBarItems() { + let doneAction = UIAction() { action in + self.dismiss(animated: true) { + self.completion?(self.filterParams) + } + } + navigationItem.rightBarButtonItem = UIBarButtonItem(systemItem: .done, primaryAction: doneAction) + } override func numberOfSections(in tableView: UITableView) -> Int { // #warning Incomplete implementation, return the number of sections diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index 9eee003..4247646 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -13,6 +13,7 @@ class TicketListController: UITableViewController { var maxNumber: Int = 0 var tickets: [TicketRecord] = [] var isFetching: Bool = false + var filterParams: TicketFilterParameters = TicketFilterParameters() init? (coder: NSCoder, identity: AppIdentity) { self.identity = identity @@ -119,7 +120,7 @@ class TicketListController: UITableViewController { private func loadData(page: Int) { let ticketManager = TicketManager(identity) isFetching = true - ticketManager.listTeamTickets(page: page) { result in + ticketManager.listTeamTickets(page: page, queryParams: self.filterParams) { result in switch(result) { case .success(let record): self.maxNumber = record.count From 19a9989f170204c962713b766b603f63c1af51c2 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 12 May 2021 03:52:01 -0700 Subject: [PATCH 145/224] filtering works, just looks not great --- Sluggo.xcodeproj/project.pbxproj | 12 ++ .../TicketFilterTableViewCell.swift | 23 ++++ Sluggo/Models/Codables/MemberRecord.swift | 6 +- Sluggo/Models/Codables/StatusRecord.swift | 5 +- Sluggo/Models/Codables/TagRecord.swift | 6 +- Sluggo/Models/Interfaces/HasTitle.swift | 12 ++ Sluggo/Models/Managers/StatusManager.swift | 2 - Sluggo/Storyboards/Ticket.storyboard | 13 +- .../CodablePickerTableViewCell.swift | 41 ++++++ .../TicketFilterTableViewController.swift | 119 ++++++++++++++++-- .../TicketListController.swift | 5 + 11 files changed, 229 insertions(+), 15 deletions(-) create mode 100644 Sluggo/Components/TicketFilterTableViewCell.swift create mode 100644 Sluggo/Models/Interfaces/HasTitle.swift create mode 100644 Sluggo/View Controllers/CodablePickerTableViewCell.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 4103569..38170a8 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -30,6 +30,9 @@ 5A25CFB1264B9A9300852035 /* Methods.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFB0264B9A9300852035 /* Methods.swift */; }; 5A25CFC2264B9AFF00852035 /* TeamPaginatedListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFC1264B9AFF00852035 /* TeamPaginatedListable.swift */; }; 5A25CFDF264BCF2C00852035 /* TicketFilterParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFDE264BCF2C00852035 /* TicketFilterParameters.swift */; }; + 5A25CFE7264BD73D00852035 /* TicketFilterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFE6264BD73D00852035 /* TicketFilterTableViewCell.swift */; }; + 5A25CFEC264BD90B00852035 /* CodablePickerTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFEB264BD90B00852035 /* CodablePickerTableViewCell.swift */; }; + 5A25CFF1264BE28100852035 /* HasTitle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFF0264BE28100852035 /* HasTitle.swift */; }; 5A673570263BF2D200C4C778 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A67356F263BF2D200C4C778 /* UIColor.swift */; }; 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A79FFE0262E562A00D04320 /* Main.storyboard */; }; 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A79FFE5262E563F00D04320 /* LoginViewController.swift */; }; @@ -108,6 +111,9 @@ 5A25CFB0264B9A9300852035 /* Methods.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Methods.swift; sourceTree = ""; }; 5A25CFC1264B9AFF00852035 /* TeamPaginatedListable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamPaginatedListable.swift; sourceTree = ""; }; 5A25CFDE264BCF2C00852035 /* TicketFilterParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketFilterParameters.swift; sourceTree = ""; }; + 5A25CFE6264BD73D00852035 /* TicketFilterTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketFilterTableViewCell.swift; sourceTree = ""; }; + 5A25CFEB264BD90B00852035 /* CodablePickerTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodablePickerTableViewCell.swift; sourceTree = ""; }; + 5A25CFF0264BE28100852035 /* HasTitle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HasTitle.swift; sourceTree = ""; }; 5A67356F263BF2D200C4C778 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; 5A79FFE0262E562A00D04320 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 5A79FFE5262E563F00D04320 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; @@ -199,6 +205,7 @@ children = ( 5A00A044263A34CC0085C8C6 /* TicketTableViewCell.swift */, 5AC6458F26412F0C00F46137 /* TeamTableViewCell.swift */, + 5A25CFE6264BD73D00852035 /* TicketFilterTableViewCell.swift */, ); path = Components; sourceTree = ""; @@ -207,6 +214,7 @@ isa = PBXGroup; children = ( 5A25CFC1264B9AFF00852035 /* TeamPaginatedListable.swift */, + 5A25CFF0264BE28100852035 /* HasTitle.swift */, ); path = Interfaces; sourceTree = ""; @@ -345,6 +353,7 @@ 5A9C4C1B2640BC4E00B270AB /* TeamTableViewController.swift */, 5A9C4C202641113D00B270AB /* TeamSelectorContainerViewController.swift */, 5A25CFAB264B966300852035 /* TicketFilterTableViewController.swift */, + 5A25CFEB264BD90B00852035 /* CodablePickerTableViewCell.swift */, ); path = "View Controllers"; sourceTree = ""; @@ -494,6 +503,7 @@ 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */, 5AB1C8FC262FF1690010F85E /* TicketRecord.swift in Sources */, 5A25CFDF264BCF2C00852035 /* TicketFilterParameters.swift in Sources */, + 5A25CFE7264BD73D00852035 /* TicketFilterTableViewCell.swift in Sources */, 3A150A7F263CBA860052934B /* TicketDetailViewController.swift in Sources */, 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */, 5A00A0142639EDEA0085C8C6 /* URLRequestBuilder.swift in Sources */, @@ -501,6 +511,7 @@ 2FD70D5D2636144300D13C75 /* LogoutMessage.swift in Sources */, 5AB1C8EA262FE2EF0010F85E /* MemberManager.swift in Sources */, 5A9C4C1C2640BC4E00B270AB /* TeamTableViewController.swift in Sources */, + 5A25CFEC264BD90B00852035 /* CodablePickerTableViewCell.swift in Sources */, 2F3AD7AD2627C8EF00B61A97 /* TeamRecord.swift in Sources */, 7DC54AE526437AB3000C8300 /* PinnedTicketRecord.swift in Sources */, 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */, @@ -513,6 +524,7 @@ 2F3AD7A32627C86A00B61A97 /* UserRecord.swift in Sources */, 052258702636100B00B49CE4 /* LaunchViewController.swift in Sources */, 3A150A6726365A9A0052934B /* StatusRecord.swift in Sources */, + 5A25CFF1264BE28100852035 /* HasTitle.swift in Sources */, 7D53BE6926433D6600400D0E /* PinnedTicketManager.swift in Sources */, 5ACC0F062630CA2700DCF83E /* Exceptions.swift in Sources */, 5A25CFA7264B934400852035 /* StatusManager.swift in Sources */, diff --git a/Sluggo/Components/TicketFilterTableViewCell.swift b/Sluggo/Components/TicketFilterTableViewCell.swift new file mode 100644 index 0000000..2158be1 --- /dev/null +++ b/Sluggo/Components/TicketFilterTableViewCell.swift @@ -0,0 +1,23 @@ +// +// TicketFilterTableViewCell.swift +// Sluggo +// +// Created by Samuel Schmidt on 5/12/21. +// + +import UIKit + +class TicketFilterTableViewCell: UITableViewCell { + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + // Configure the view for the selected state + } + +} diff --git a/Sluggo/Models/Codables/MemberRecord.swift b/Sluggo/Models/Codables/MemberRecord.swift index 14e4597..89f1696 100644 --- a/Sluggo/Models/Codables/MemberRecord.swift +++ b/Sluggo/Models/Codables/MemberRecord.swift @@ -8,7 +8,7 @@ import Foundation import NullCodable -struct MemberRecord: Codable{ +struct MemberRecord: Codable, HasTitle { var id: String var owner: UserRecord var team_id: Int @@ -18,4 +18,8 @@ struct MemberRecord: Codable{ var created: Date @NullCodable var activated: Date? @NullCodable var deactivated: Date? + + func getTitle() -> String { + return owner.username + } } diff --git a/Sluggo/Models/Codables/StatusRecord.swift b/Sluggo/Models/Codables/StatusRecord.swift index 0e17f8e..8405535 100644 --- a/Sluggo/Models/Codables/StatusRecord.swift +++ b/Sluggo/Models/Codables/StatusRecord.swift @@ -8,7 +8,7 @@ import Foundation import NullCodable -struct StatusRecord: Codable { +struct StatusRecord: Codable, HasTitle { var id: Int var object_uuid: UUID var title: String @@ -17,4 +17,7 @@ struct StatusRecord: Codable { @NullCodable var activated: Date? @NullCodable var deactivated: Date? + func getTitle() -> String { + return title + } } diff --git a/Sluggo/Models/Codables/TagRecord.swift b/Sluggo/Models/Codables/TagRecord.swift index f0b2938..549b33c 100644 --- a/Sluggo/Models/Codables/TagRecord.swift +++ b/Sluggo/Models/Codables/TagRecord.swift @@ -8,7 +8,7 @@ import Foundation import NullCodable -struct TagRecord: Codable { +struct TagRecord: Codable, HasTitle { var id: Int var team_id: Int var object_uuid: UUID @@ -16,4 +16,8 @@ struct TagRecord: Codable { var created: Date @NullCodable var activated: Date? @NullCodable var deactivated: Date? + + func getTitle() -> String { + return title + } } diff --git a/Sluggo/Models/Interfaces/HasTitle.swift b/Sluggo/Models/Interfaces/HasTitle.swift new file mode 100644 index 0000000..bcdf2fc --- /dev/null +++ b/Sluggo/Models/Interfaces/HasTitle.swift @@ -0,0 +1,12 @@ +// +// File.swift +// Sluggo +// +// Created by Samuel Schmidt on 5/12/21. +// + +import Foundation + +protocol HasTitle { + func getTitle() -> String +} diff --git a/Sluggo/Models/Managers/StatusManager.swift b/Sluggo/Models/Managers/StatusManager.swift index d040663..1be323d 100644 --- a/Sluggo/Models/Managers/StatusManager.swift +++ b/Sluggo/Models/Managers/StatusManager.swift @@ -4,8 +4,6 @@ // // Created by Samuel Schmidt on 5/11/21. // -// Rip pop smoke -// import Foundation diff --git a/Sluggo/Storyboards/Ticket.storyboard b/Sluggo/Storyboards/Ticket.storyboard index 74eebef..81dc365 100644 --- a/Sluggo/Storyboards/Ticket.storyboard +++ b/Sluggo/Storyboards/Ticket.storyboard @@ -11,17 +11,26 @@ - + - + + + + diff --git a/Sluggo/View Controllers/CodablePickerTableViewCell.swift b/Sluggo/View Controllers/CodablePickerTableViewCell.swift new file mode 100644 index 0000000..f40742c --- /dev/null +++ b/Sluggo/View Controllers/CodablePickerTableViewCell.swift @@ -0,0 +1,41 @@ +// +// CodablePickerTableViewCell.swift +// Sluggo +// +// Created by Samuel Schmidt on 5/12/21. +// + +import UIKit + +class CodablePickerTableViewCell: UITableViewCell, UIPickerViewDelegate, UIPickerViewDataSource { + + var pickerData: [T]! + var getTitle: ((T) -> String)! // this could proabaly get replaced with computer properties + var onCompletion: ((T) -> Void)! + + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + // Configure the view for the selected state + } + + // MARK: data source contract + func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + + func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return pickerData.count + } + + func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + return self.getTitle(pickerData[row]) + } + +} diff --git a/Sluggo/View Controllers/TicketFilterTableViewController.swift b/Sluggo/View Controllers/TicketFilterTableViewController.swift index 80bbb05..1bb50bd 100644 --- a/Sluggo/View Controllers/TicketFilterTableViewController.swift +++ b/Sluggo/View Controllers/TicketFilterTableViewController.swift @@ -16,20 +16,29 @@ enum FilterViewCategories: Int { class TicketFilterTableViewController: UITableViewController { // TODO: wire this wonderful code together - var identity: AppIdentity! = nil + var identity: AppIdentity! var completion: ((TicketFilterParameters) -> Void)? + var filterParams: TicketFilterParameters! - private var filterParams: TicketFilterParameters = TicketFilterParameters() private var teamMembers: [MemberRecord] = [] private var ticketTags: [TagRecord] = [] private var ticketStatuses: [StatusRecord] = [] private let semaphore = DispatchSemaphore(value: 1) private static let numSections = 3 + + private var sectionSelectedMap = [ + FilterViewCategories.assignedUsers.rawValue: nil, + FilterViewCategories.ticketTags.rawValue: nil, + FilterViewCategories.ticketStatuses.rawValue: nil + ] as [Int: Int?] override func viewDidLoad() { super.viewDidLoad() configureRefreshControl() configureBarItems() + handleRefreshAction() { + self.preselectItems() + } // Uncomment the following line to preserve selection between presentations // self.clearsSelectionOnViewWillAppear = false @@ -54,16 +63,95 @@ class TicketFilterTableViewController: UITableViewController { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // #warning Incomplete implementation, return the number of rows - switch(section) { + switch(section){ + case FilterViewCategories.assignedUsers.rawValue: + return teamMembers.count + case FilterViewCategories.ticketTags.rawValue: + return ticketTags.count + case FilterViewCategories.ticketStatuses.rawValue: + return ticketStatuses.count + default: + fatalError("no such section") + } + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "FilterEntry", for: indexPath) + var text = "" + + switch(indexPath.section) { case FilterViewCategories.assignedUsers.rawValue: - return self.teamMembers.count + text = teamMembers[indexPath.row].getTitle() + break case FilterViewCategories.ticketTags.rawValue: - return self.ticketTags.count + text = ticketTags[indexPath.row].getTitle() + break case FilterViewCategories.ticketStatuses.rawValue: - return self.ticketStatuses.count + text = ticketStatuses[indexPath.row].getTitle() + break default: - fatalError("Invalid section count queried") + fatalError("no such section") } + + cell.textLabel?.text = text + return cell + } + + // MARK: duplicaty nonsense + func preselectItems() { + if let row = self.teamMembers.firstIndex(where: { record in + record.object_uuid == self.filterParams.assignedUser?.object_uuid + }) { + let indexPath = IndexPath(row: row, section: FilterViewCategories.assignedUsers.rawValue) + tableView.selectRow(at: indexPath, animated: true, scrollPosition: .top) + } + + if let row = self.ticketStatuses.firstIndex(where: { record in + record.object_uuid == self.filterParams.ticketStatus?.object_uuid + }) { + let indexPath = IndexPath(row: row, section: FilterViewCategories.ticketStatuses.rawValue) + tableView.selectRow(at: indexPath, animated: true, scrollPosition: .top) + } + + if let row = self.ticketTags.firstIndex(where: { record in + record.object_uuid == self.filterParams.ticketTag?.object_uuid + }) { + let indexPath = IndexPath(row: row, section: FilterViewCategories.ticketTags.rawValue) + tableView.selectRow(at: indexPath, animated: true, scrollPosition: .top) + } + + } + + override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { + switch(indexPath.section){ + case FilterViewCategories.assignedUsers.rawValue: + self.filterParams.assignedUser = nil + case FilterViewCategories.ticketTags.rawValue: + self.filterParams.ticketTag = nil + case FilterViewCategories.ticketStatuses.rawValue: + self.filterParams.ticketStatus = nil + default: + fatalError("no such section") + } + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + switch(indexPath.section){ + case FilterViewCategories.assignedUsers.rawValue: + self.filterParams.assignedUser = self.teamMembers[indexPath.row] + case FilterViewCategories.ticketTags.rawValue: + self.filterParams.ticketTag = self.ticketTags[indexPath.row] + case FilterViewCategories.ticketStatuses.rawValue: + self.filterParams.ticketStatus = self.ticketStatuses[indexPath.row] + default: + fatalError("no such section") + } + + if let previousInSection = self.sectionSelectedMap[indexPath.section]! { + tableView.deselectRow(at: IndexPath(row: previousInSection, section: indexPath.section), animated: true) + } + self.sectionSelectedMap[indexPath.section] = indexPath.row + } // MARK: refresh stuff @@ -72,7 +160,7 @@ class TicketFilterTableViewController: UITableViewController { refreshControl?.addTarget(self, action: #selector(handleRefreshAction), for: .valueChanged) } - @objc func handleRefreshAction() { + @objc func handleRefreshAction(after: (() -> Void)? = nil) { DispatchQueue.global(qos: .userInitiated).async { self.semaphore.wait() @@ -89,6 +177,8 @@ class TicketFilterTableViewController: UITableViewController { if (completed == TicketFilterTableViewController.numSections) { DispatchQueue.main.async { self.refreshControl?.endRefreshing() + self.tableView.reloadData() // we reload all the mf data + after?() } self.semaphore.signal() @@ -125,6 +215,19 @@ class TicketFilterTableViewController: UITableViewController { } } + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + switch section { + case FilterViewCategories.assignedUsers.rawValue: + return "Members" + case FilterViewCategories.ticketTags.rawValue: + return "Tags" + case FilterViewCategories.ticketStatuses.rawValue: + return "Statuses" + default: + return "Error!" + } + } + // override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // switch(indexPath.section) { // case HomepageCategories.assigned.rawValue: diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index 4247646..b3d2ddc 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -89,6 +89,11 @@ class TicketListController: UITableViewController { if let vc = storyboard?.instantiateViewController(identifier: "FilterNavigationController") { if let child = vc.children[0] as? TicketFilterTableViewController { child.identity = self.identity + child.filterParams = self.filterParams + child.completion = { queryParams in + self.filterParams = queryParams + self.handleRefreshAction() + } } self.present(vc, animated: true, completion: nil) From 34d1e356a49079259226fe1ccb07982728978f78 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 12 May 2021 12:20:18 -0700 Subject: [PATCH 146/224] fix issue where preselection is called before reloadingData --- Sluggo.xcodeproj/project.pbxproj | 2 +- .../TicketFilterTableViewCell.swift | 1 + Sluggo/Storyboards/Ticket.storyboard | 2 +- .../TeamTableViewController.swift | 1 + .../TicketFilterTableViewController.swift | 54 ++++++------------- 5 files changed, 19 insertions(+), 41 deletions(-) diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 38170a8..2ac1009 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -348,10 +348,10 @@ 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */, 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */, 5A9FF88D26386D4700378550 /* TicketListController.swift */, - 7D23834A264119050091C7E8 /* HomeTableViewController.swift */, 3A150A7E263CBA860052934B /* TicketDetailViewController.swift */, 5A9C4C1B2640BC4E00B270AB /* TeamTableViewController.swift */, 5A9C4C202641113D00B270AB /* TeamSelectorContainerViewController.swift */, + 7D23834A264119050091C7E8 /* HomeTableViewController.swift */, 5A25CFAB264B966300852035 /* TicketFilterTableViewController.swift */, 5A25CFEB264BD90B00852035 /* CodablePickerTableViewCell.swift */, ); diff --git a/Sluggo/Components/TicketFilterTableViewCell.swift b/Sluggo/Components/TicketFilterTableViewCell.swift index 2158be1..276b362 100644 --- a/Sluggo/Components/TicketFilterTableViewCell.swift +++ b/Sluggo/Components/TicketFilterTableViewCell.swift @@ -17,6 +17,7 @@ class TicketFilterTableViewCell: UITableViewCell { override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) + super.accessoryType = selected ? .checkmark : .none // Configure the view for the selected state } diff --git a/Sluggo/Storyboards/Ticket.storyboard b/Sluggo/Storyboards/Ticket.storyboard index 81dc365..c52b65e 100644 --- a/Sluggo/Storyboards/Ticket.storyboard +++ b/Sluggo/Storyboards/Ticket.storyboard @@ -16,7 +16,7 @@ - + diff --git a/Sluggo/View Controllers/TeamTableViewController.swift b/Sluggo/View Controllers/TeamTableViewController.swift index 3f4aa38..18ca4dc 100644 --- a/Sluggo/View Controllers/TeamTableViewController.swift +++ b/Sluggo/View Controllers/TeamTableViewController.swift @@ -68,6 +68,7 @@ class TeamTableViewController: UITableViewController { let after = { () -> Void in DispatchQueue.main.async { self.refreshControl?.endRefreshing() + self.tableView.reloadData() self.preselectRow() } self.semaphore.signal() diff --git a/Sluggo/View Controllers/TicketFilterTableViewController.swift b/Sluggo/View Controllers/TicketFilterTableViewController.swift index 1bb50bd..ee7de54 100644 --- a/Sluggo/View Controllers/TicketFilterTableViewController.swift +++ b/Sluggo/View Controllers/TicketFilterTableViewController.swift @@ -39,12 +39,6 @@ class TicketFilterTableViewController: UITableViewController { handleRefreshAction() { self.preselectItems() } - - // Uncomment the following line to preserve selection between presentations - // self.clearsSelectionOnViewWillAppear = false - - // Uncomment the following line to display an Edit button in the navigation bar for this view controller. - // self.navigationItem.rightBarButtonItem = self.editButtonItem } func configureBarItems() { @@ -150,8 +144,21 @@ class TicketFilterTableViewController: UITableViewController { if let previousInSection = self.sectionSelectedMap[indexPath.section]! { tableView.deselectRow(at: IndexPath(row: previousInSection, section: indexPath.section), animated: true) } - self.sectionSelectedMap[indexPath.section] = indexPath.row + self.sectionSelectedMap[indexPath.section] = indexPath.row + } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + switch section { + case FilterViewCategories.assignedUsers.rawValue: + return "Members" + case FilterViewCategories.ticketTags.rawValue: + return "Tags" + case FilterViewCategories.ticketStatuses.rawValue: + return "Statuses" + default: + return "Error!" + } } // MARK: refresh stuff @@ -178,7 +185,7 @@ class TicketFilterTableViewController: UITableViewController { DispatchQueue.main.async { self.refreshControl?.endRefreshing() self.tableView.reloadData() // we reload all the mf data - after?() + self.preselectItems() } self.semaphore.signal() @@ -215,36 +222,5 @@ class TicketFilterTableViewController: UITableViewController { } } - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - switch section { - case FilterViewCategories.assignedUsers.rawValue: - return "Members" - case FilterViewCategories.ticketTags.rawValue: - return "Tags" - case FilterViewCategories.ticketStatuses.rawValue: - return "Statuses" - default: - return "Error!" - } - } - -// override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { -// switch(indexPath.section) { -// case HomepageCategories.assigned.rawValue: -// let cell = tableView.dequeueReusableCell(withIdentifier: "TicketCell", for: indexPath) as! TicketTableViewCell -// cell.loadFromTicketRecord(ticket: assignedTickets[indexPath.row]) -// return cell -// case HomepageCategories.pinned.rawValue: -// let cell = tableView.dequeueReusableCell(withIdentifier: "TicketCell", for: indexPath) as! TicketTableViewCell -// cell.loadFromTicketRecord(ticket: pinnedTickets[indexPath.row].ticket) -// return cell -// case HomepageCategories.tags.rawValue: -// let cell = tableView.dequeueReusableCell(withIdentifier: "PlaceholderCell")! -// cell.textLabel?.text = "Not yet implemented." -// return cell -// default: -// fatalError("Accessed section outside of scope, should never occur") -// } -// } } From 0f23b1c517333d6148601d21285e5c01b2b40997 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 12 May 2021 16:48:37 -0700 Subject: [PATCH 147/224] notifications for ticketListView --- Sluggo.xcodeproj/project.pbxproj | 4 - Sluggo/Models/Constants.swift | 4 + .../Interfaces/TeamPaginatedListable.swift | 3 +- Sluggo/Models/Managers/MemberManager.swift | 2 +- Sluggo/Models/Managers/StatusManager.swift | 2 +- Sluggo/Models/Managers/TagManager.swift | 3 +- Sluggo/Models/Managers/TeamManager.swift | 2 +- Sluggo/Models/Methods.swift | 102 +++++++++--------- .../CodablePickerTableViewCell.swift | 41 ------- ...SluggoSidebarContainerViewController.swift | 1 + .../TeamTableViewController.swift | 2 +- .../TicketFilterTableViewController.swift | 36 ++++--- .../TicketListController.swift | 6 ++ 13 files changed, 90 insertions(+), 118 deletions(-) delete mode 100644 Sluggo/View Controllers/CodablePickerTableViewCell.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 2ac1009..de0dc30 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -31,7 +31,6 @@ 5A25CFC2264B9AFF00852035 /* TeamPaginatedListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFC1264B9AFF00852035 /* TeamPaginatedListable.swift */; }; 5A25CFDF264BCF2C00852035 /* TicketFilterParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFDE264BCF2C00852035 /* TicketFilterParameters.swift */; }; 5A25CFE7264BD73D00852035 /* TicketFilterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFE6264BD73D00852035 /* TicketFilterTableViewCell.swift */; }; - 5A25CFEC264BD90B00852035 /* CodablePickerTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFEB264BD90B00852035 /* CodablePickerTableViewCell.swift */; }; 5A25CFF1264BE28100852035 /* HasTitle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFF0264BE28100852035 /* HasTitle.swift */; }; 5A673570263BF2D200C4C778 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A67356F263BF2D200C4C778 /* UIColor.swift */; }; 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A79FFE0262E562A00D04320 /* Main.storyboard */; }; @@ -112,7 +111,6 @@ 5A25CFC1264B9AFF00852035 /* TeamPaginatedListable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamPaginatedListable.swift; sourceTree = ""; }; 5A25CFDE264BCF2C00852035 /* TicketFilterParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketFilterParameters.swift; sourceTree = ""; }; 5A25CFE6264BD73D00852035 /* TicketFilterTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketFilterTableViewCell.swift; sourceTree = ""; }; - 5A25CFEB264BD90B00852035 /* CodablePickerTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodablePickerTableViewCell.swift; sourceTree = ""; }; 5A25CFF0264BE28100852035 /* HasTitle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HasTitle.swift; sourceTree = ""; }; 5A67356F263BF2D200C4C778 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; 5A79FFE0262E562A00D04320 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; @@ -353,7 +351,6 @@ 5A9C4C202641113D00B270AB /* TeamSelectorContainerViewController.swift */, 7D23834A264119050091C7E8 /* HomeTableViewController.swift */, 5A25CFAB264B966300852035 /* TicketFilterTableViewController.swift */, - 5A25CFEB264BD90B00852035 /* CodablePickerTableViewCell.swift */, ); path = "View Controllers"; sourceTree = ""; @@ -511,7 +508,6 @@ 2FD70D5D2636144300D13C75 /* LogoutMessage.swift in Sources */, 5AB1C8EA262FE2EF0010F85E /* MemberManager.swift in Sources */, 5A9C4C1C2640BC4E00B270AB /* TeamTableViewController.swift in Sources */, - 5A25CFEC264BD90B00852035 /* CodablePickerTableViewCell.swift in Sources */, 2F3AD7AD2627C8EF00B61A97 /* TeamRecord.swift in Sources */, 7DC54AE526437AB3000C8300 /* PinnedTicketRecord.swift in Sources */, 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */, diff --git a/Sluggo/Models/Constants.swift b/Sluggo/Models/Constants.swift index 570be79..59d4b7e 100644 --- a/Sluggo/Models/Constants.swift +++ b/Sluggo/Models/Constants.swift @@ -21,4 +21,8 @@ struct Constants { static let kURL = "url" } + + struct Signals { + static let TEAM_CHANGE_NOTIFICATION = NSNotification.Name(rawValue: "teamChange") + } } diff --git a/Sluggo/Models/Interfaces/TeamPaginatedListable.swift b/Sluggo/Models/Interfaces/TeamPaginatedListable.swift index 4486512..90e6808 100644 --- a/Sluggo/Models/Interfaces/TeamPaginatedListable.swift +++ b/Sluggo/Models/Interfaces/TeamPaginatedListable.swift @@ -8,5 +8,6 @@ import Foundation protocol TeamPaginatedListable { - func listFromTeams(page: Int, completionHandler: @escaping(Result, Error>) -> Void) -> Void + associatedtype Record where Record: Codable + func listFromTeams(page: Int, completionHandler: @escaping(Result, Error>) -> Void) -> Void } diff --git a/Sluggo/Models/Managers/MemberManager.swift b/Sluggo/Models/Managers/MemberManager.swift index 9b68f74..357fbc2 100644 --- a/Sluggo/Models/Managers/MemberManager.swift +++ b/Sluggo/Models/Managers/MemberManager.swift @@ -42,7 +42,7 @@ class MemberManager: TeamPaginatedListable { } - func listFromTeams(page: Int, completionHandler: @escaping (Result, Error>) -> Void) where T : Decodable, T : Encodable { + func listFromTeams(page: Int, completionHandler: @escaping (Result, Error>) -> Void) { let requestBuilder = URLRequestBuilder(url: makeListUrl(page: page)) .setIdentity(identity: identity) diff --git a/Sluggo/Models/Managers/StatusManager.swift b/Sluggo/Models/Managers/StatusManager.swift index 1be323d..aa65fbe 100644 --- a/Sluggo/Models/Managers/StatusManager.swift +++ b/Sluggo/Models/Managers/StatusManager.swift @@ -24,7 +24,7 @@ class StatusManager: TeamPaginatedListable { return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + StatusManager.urlBase + "?page=\(page)")! } - func listFromTeams(page: Int, completionHandler: @escaping (Result, Error>) -> Void) where T : Decodable, T : Encodable { + func listFromTeams(page: Int, completionHandler: @escaping (Result, Error>) -> Void) { let requestBuilder = URLRequestBuilder(url: makeListUrl(page: page)) .setIdentity(identity: identity) .setMethod(method: .GET) diff --git a/Sluggo/Models/Managers/TagManager.swift b/Sluggo/Models/Managers/TagManager.swift index 9e232ac..c12664c 100644 --- a/Sluggo/Models/Managers/TagManager.swift +++ b/Sluggo/Models/Managers/TagManager.swift @@ -8,7 +8,6 @@ import Foundation class TagManager: TeamPaginatedListable { - static let urlBase = "/tags/" private let identity: AppIdentity @@ -25,7 +24,7 @@ class TagManager: TeamPaginatedListable { return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + TagManager.urlBase + "?page=\(page)")! } - func listFromTeams(page: Int, completionHandler: @escaping (Result, Error>) -> Void) where T : Decodable, T : Encodable { + func listFromTeams(page: Int, completionHandler: @escaping (Result, Error>) -> Void) { let requestBuilder = URLRequestBuilder(url: makeListUrl(page: page)) .setIdentity(identity: identity) .setMethod(method: .GET) diff --git a/Sluggo/Models/Managers/TeamManager.swift b/Sluggo/Models/Managers/TeamManager.swift index a621242..e7b3145 100644 --- a/Sluggo/Models/Managers/TeamManager.swift +++ b/Sluggo/Models/Managers/TeamManager.swift @@ -17,7 +17,7 @@ class TeamManager: TeamPaginatedListable { self.identity = identity } - func listFromTeams(page: Int, completionHandler: @escaping (Result, Error>) -> Void) where T : Decodable, T : Encodable { + func listFromTeams(page: Int, completionHandler: @escaping (Result, Error>) -> Void) { let requestBuilder = URLRequestBuilder(url: URL(string: identity.baseAddress + TeamManager.urlBase + "?page=\(page)")!) .setIdentity(identity: identity) diff --git a/Sluggo/Models/Methods.swift b/Sluggo/Models/Methods.swift index 6ff066d..5689944 100644 --- a/Sluggo/Models/Methods.swift +++ b/Sluggo/Models/Methods.swift @@ -7,20 +7,20 @@ import Foundation -class UnwindState { - let manager: TeamPaginatedListable +class UnwindState { + let manager: M var page: Int var maxCount: Int - var codableArray: [T] - var onSuccess: (([T]) -> Void)? + var codableArray: [M.Record] + var onSuccess: (([M.Record]) -> Void)? var onFailure: ((Error) -> Void)? var after: (() -> Void)? - init(manager: TeamPaginatedListable, + init(manager: M, page: Int, maxCount: Int, - codableArray: [T], - onSuccess: (([T]) -> Void)?, + codableArray: [M.Record], + onSuccess: (([M.Record]) -> Void)?, onFailure: ((Error) -> Void)?, after: (() -> Void)?) { @@ -32,55 +32,57 @@ class UnwindState { self.onFailure = onFailure self.after = after } +} + +func unwindPaginationRecurse(state: UnwindState) -> Void { - static func unwindPaginationRecurse(state: UnwindState) -> Void { - - state.manager.listFromTeams(page: state.page) { - (result: Result, Error>) -> Void in + state.manager.listFromTeams(page: state.page) { + (result: Result, Error>) -> Void in - switch (result) { - case .success(let record): - state.maxCount = record.count - - for entry in record.results { - state.codableArray.append(entry) - } + switch (result) { + case .success(let record): + state.maxCount = record.count + + for entry in record.results { + state.codableArray.append(entry) + } + + if (state.codableArray.count < state.maxCount) { + state.page += 1 - if (state.codableArray.count < state.maxCount) { - state.page += 1 - - // tail recurse to get remaining data - unwindPaginationRecurse(state: state) - } else { - state.onSuccess?(state.codableArray) - state.after?() - } - break - case .failure(let error): - print("error occured") - state.onFailure?(error) + // tail recurse to get remaining data + unwindPaginationRecurse(state: state) + } else { + state.onSuccess?(state.codableArray) state.after?() - break } - }; - } + break + case .failure(let error): + print("error occured") + state.onFailure?(error) + state.after?() + break + } + }; +} - static func unwindPagination(manager: TeamPaginatedListable, - startingPage: Int, - onSuccess: (([T]) -> Void)?, - onFailure: ((Error) -> Void)?, - after: (() -> Void)?) -> Void { - - let state = UnwindState(manager: manager, - page: startingPage, - maxCount: 0, - codableArray: [], - onSuccess: onSuccess, - onFailure: onFailure, - after: after) - - unwindPaginationRecurse(state: state) - } +func unwindPagination(manager: M, + startingPage: Int, + onSuccess: (([M.Record]) -> Void)?, + onFailure: ((Error) -> Void)?, + after: (() -> Void)?) -> Void { + + let state = UnwindState(manager: manager, + page: startingPage, + maxCount: 0, + codableArray: [], + onSuccess: onSuccess, + onFailure: onFailure, + after: after) + + unwindPaginationRecurse(state: state) } + + diff --git a/Sluggo/View Controllers/CodablePickerTableViewCell.swift b/Sluggo/View Controllers/CodablePickerTableViewCell.swift deleted file mode 100644 index f40742c..0000000 --- a/Sluggo/View Controllers/CodablePickerTableViewCell.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// CodablePickerTableViewCell.swift -// Sluggo -// -// Created by Samuel Schmidt on 5/12/21. -// - -import UIKit - -class CodablePickerTableViewCell: UITableViewCell, UIPickerViewDelegate, UIPickerViewDataSource { - - var pickerData: [T]! - var getTitle: ((T) -> String)! // this could proabaly get replaced with computer properties - var onCompletion: ((T) -> Void)! - - - override func awakeFromNib() { - super.awakeFromNib() - // Initialization code - } - - override func setSelected(_ selected: Bool, animated: Bool) { - super.setSelected(selected, animated: animated) - - // Configure the view for the selected state - } - - // MARK: data source contract - func numberOfComponents(in pickerView: UIPickerView) -> Int { - return 1 - } - - func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { - return pickerData.count - } - - func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - return self.getTitle(pickerData[row]) - } - -} diff --git a/Sluggo/View Controllers/SluggoSidebarContainerViewController.swift b/Sluggo/View Controllers/SluggoSidebarContainerViewController.swift index 0281162..49e51af 100644 --- a/Sluggo/View Controllers/SluggoSidebarContainerViewController.swift +++ b/Sluggo/View Controllers/SluggoSidebarContainerViewController.swift @@ -103,6 +103,7 @@ class SluggoSidebarContainerViewController: UIViewController { vc?.identity = self.identity vc?.completion = { team in self.identity.team = team + NotificationCenter.default.post(name: Constants.Signals.TEAM_CHANGE_NOTIFICATION, object: nil) } return vc } diff --git a/Sluggo/View Controllers/TeamTableViewController.swift b/Sluggo/View Controllers/TeamTableViewController.swift index 18ca4dc..780bb8b 100644 --- a/Sluggo/View Controllers/TeamTableViewController.swift +++ b/Sluggo/View Controllers/TeamTableViewController.swift @@ -75,7 +75,7 @@ class TeamTableViewController: UITableViewController { } let teamManager = TeamManager(identity: self.identity) - UnwindState.unwindPagination(manager: teamManager, + unwindPagination(manager: teamManager, startingPage: 1, onSuccess: onSuccess, onFailure: self.presentErrorFromMainThread, diff --git a/Sluggo/View Controllers/TicketFilterTableViewController.swift b/Sluggo/View Controllers/TicketFilterTableViewController.swift index ee7de54..8af05dc 100644 --- a/Sluggo/View Controllers/TicketFilterTableViewController.swift +++ b/Sluggo/View Controllers/TicketFilterTableViewController.swift @@ -98,6 +98,7 @@ class TicketFilterTableViewController: UITableViewController { }) { let indexPath = IndexPath(row: row, section: FilterViewCategories.assignedUsers.rawValue) tableView.selectRow(at: indexPath, animated: true, scrollPosition: .top) + sectionSelectedMap[FilterViewCategories.assignedUsers.rawValue] = indexPath.row } if let row = self.ticketStatuses.firstIndex(where: { record in @@ -105,6 +106,8 @@ class TicketFilterTableViewController: UITableViewController { }) { let indexPath = IndexPath(row: row, section: FilterViewCategories.ticketStatuses.rawValue) tableView.selectRow(at: indexPath, animated: true, scrollPosition: .top) + sectionSelectedMap[FilterViewCategories.ticketStatuses.rawValue] = indexPath.row + } if let row = self.ticketTags.firstIndex(where: { record in @@ -112,8 +115,9 @@ class TicketFilterTableViewController: UITableViewController { }) { let indexPath = IndexPath(row: row, section: FilterViewCategories.ticketTags.rawValue) tableView.selectRow(at: indexPath, animated: true, scrollPosition: .top) + sectionSelectedMap[FilterViewCategories.ticketTags.rawValue] = indexPath.row + } - } override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { @@ -194,7 +198,7 @@ class TicketFilterTableViewController: UITableViewController { } let tagManager = TagManager(identity: self.identity) - UnwindState.unwindPagination(manager: tagManager, + unwindPagination(manager: tagManager, startingPage: 1, onSuccess: { (tags: [TagRecord]) -> Void in self.ticketTags = tags @@ -203,22 +207,22 @@ class TicketFilterTableViewController: UITableViewController { after: after) let statusManger = StatusManager(identity: self.identity) - UnwindState.unwindPagination(manager: statusManger, - startingPage: 1, - onSuccess: { (statuses: [StatusRecord]) -> Void in - self.ticketStatuses = statuses - }, - onFailure: self.presentErrorFromMainThread, - after: after) + unwindPagination(manager: statusManger, + startingPage: 1, + onSuccess: { (statuses: [StatusRecord]) -> Void in + self.ticketStatuses = statuses + }, + onFailure: self.presentErrorFromMainThread, + after: after) let memberManager = MemberManager(identity: self.identity) - UnwindState.unwindPagination(manager: memberManager, - startingPage: 1, - onSuccess: { (members: [MemberRecord]) -> Void in - self.teamMembers = members - }, - onFailure: self.presentErrorFromMainThread, - after: after) + unwindPagination(manager: memberManager, + startingPage: 1, + onSuccess: { (members: [MemberRecord]) -> Void in + self.teamMembers = members + }, + onFailure: self.presentErrorFromMainThread, + after: after) } } diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index b3d2ddc..141980b 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -30,6 +30,12 @@ class TicketListController: UITableViewController { setupMenuBar() loadData(page: 1) + NotificationCenter.default.addObserver(forName: Constants.Signals.TEAM_CHANGE_NOTIFICATION, + object: nil, + queue: nil) { _ in + self.filterParams = TicketFilterParameters() + self.handleRefreshAction() + } NotificationCenter.default.addObserver(self, selector: #selector(handleRefreshAction), name: .refreshTrigger, object: nil) } From 728c8d97b954950f89456168b7ee005b43601152 Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 12 May 2021 16:53:03 -0700 Subject: [PATCH 148/224] minor style change --- Sluggo/Storyboards/Ticket.storyboard | 4 ++-- Sluggo/View Controllers/HomeTableViewController.swift | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Sluggo/Storyboards/Ticket.storyboard b/Sluggo/Storyboards/Ticket.storyboard index c52b65e..c08c844 100644 --- a/Sluggo/Storyboards/Ticket.storyboard +++ b/Sluggo/Storyboards/Ticket.storyboard @@ -11,13 +11,13 @@ - + - + diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index 89e3801..c43bdb8 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -37,6 +37,11 @@ class HomeTableViewController: UITableViewController { loadMember(completionHandler: refreshContent) // Setup refresh control + NotificationCenter.default.addObserver(forName: Constants.Signals.TEAM_CHANGE_NOTIFICATION, + object: nil, + queue: nil) { _ in + self.refreshContent() + } self.refreshControl?.addTarget(self, action: #selector(refreshContent), for: .valueChanged) // Uncomment the following line to preserve selection between presentations From 66c08a41da563bfec17f787987ff34384db864f5 Mon Sep 17 00:00:00 2001 From: tdimhcsleumas <56523092+tdimhcsleumas@users.noreply.github.com> Date: Wed, 12 May 2021 16:58:16 -0700 Subject: [PATCH 149/224] Update TicketFilterTableViewController.swift --- Sluggo/View Controllers/TicketFilterTableViewController.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Sluggo/View Controllers/TicketFilterTableViewController.swift b/Sluggo/View Controllers/TicketFilterTableViewController.swift index 8af05dc..1f96215 100644 --- a/Sluggo/View Controllers/TicketFilterTableViewController.swift +++ b/Sluggo/View Controllers/TicketFilterTableViewController.swift @@ -15,7 +15,6 @@ enum FilterViewCategories: Int { class TicketFilterTableViewController: UITableViewController { - // TODO: wire this wonderful code together var identity: AppIdentity! var completion: ((TicketFilterParameters) -> Void)? var filterParams: TicketFilterParameters! @@ -51,12 +50,10 @@ class TicketFilterTableViewController: UITableViewController { } override func numberOfSections(in tableView: UITableView) -> Int { - // #warning Incomplete implementation, return the number of sections return TicketFilterTableViewController.numSections } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - // #warning Incomplete implementation, return the number of rows switch(section){ case FilterViewCategories.assignedUsers.rawValue: return teamMembers.count From bb75113c3fb7892ff8d52f8b6d0321c550300550 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Wed, 12 May 2021 17:04:19 -0700 Subject: [PATCH 150/224] removed completion and updated writeticketrecord --- Sluggo/Models/Codables/TicketRecord.swift | 4 ++-- Sluggo/Models/Managers/TicketManager.swift | 4 +++- Sluggo/Storyboards/TicketDetail.storyboard | 2 +- .../HomeTableViewController.swift | 4 ++-- .../TicketDetailTableViewController.swift | 3 +-- .../TicketDetailViewController.swift | 9 ++++---- .../TicketListController.swift | 6 ++--- SluggoTests/TicketManagerTests.swift | 22 ++++++++++++++----- 8 files changed, 32 insertions(+), 22 deletions(-) diff --git a/Sluggo/Models/Codables/TicketRecord.swift b/Sluggo/Models/Codables/TicketRecord.swift index 146c830..35df060 100644 --- a/Sluggo/Models/Codables/TicketRecord.swift +++ b/Sluggo/Models/Codables/TicketRecord.swift @@ -28,9 +28,9 @@ struct TicketRecord: Codable { } struct WriteTicketRecord: Codable{ - var tag_list: [TagRecord] + var tag_list: [Int] @NullCodable var assigned_user: String? - @NullCodable var status: StatusRecord? + @NullCodable var status: Int? var title: String @NullCodable var description: String? @NullCodable var due_date: Date? diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index 776e634..c747af4 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -53,7 +53,9 @@ class TicketManager { } public func updateTicket(ticket: TicketRecord, completionHandler: @escaping(Result) -> Void)-> Void { - let writeTicket = WriteTicketRecord(tag_list: ticket.tag_list, assigned_user: ticket.assigned_user?.id, status: ticket.status, title: ticket.title, description: ticket.description, due_date: ticket.due_date) + let tags_list: [Int] = ticket.tag_list.map{$0.id} + + let writeTicket = WriteTicketRecord(tag_list: tags_list, assigned_user: ticket.assigned_user?.id, status: ticket.status?.id, title: ticket.title, description: ticket.description, due_date: ticket.due_date) guard let body = JsonLoader.encode(object: writeTicket) else { completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize ticket JSON for updateTicket in TicketManager"))) return diff --git a/Sluggo/Storyboards/TicketDetail.storyboard b/Sluggo/Storyboards/TicketDetail.storyboard index 17d41b0..09e6276 100644 --- a/Sluggo/Storyboards/TicketDetail.storyboard +++ b/Sluggo/Storyboards/TicketDetail.storyboard @@ -224,7 +224,7 @@ - + diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index 311e1e5..592e83b 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -187,9 +187,9 @@ class HomeTableViewController: UITableViewController { let selectedPath = tableView.indexPathForSelectedRow switch(selectedPath!.section) { case HomepageCategories.assigned.rawValue: - return TicketDetailViewController(coder: coder, identity: self.identity, ticket: assignedTickets[selectedPath!.row], completion: nil) + return TicketDetailViewController(coder: coder, identity: self.identity, ticket: assignedTickets[selectedPath!.row]) case HomepageCategories.pinned.rawValue: - return TicketDetailViewController(coder: coder, identity: self.identity, ticket: pinnedTickets[selectedPath!.row].ticket, completion: nil) + return TicketDetailViewController(coder: coder, identity: self.identity, ticket: pinnedTickets[selectedPath!.row].ticket) default: fatalError("Nothing should be selectable from unimplemented categories!") } diff --git a/Sluggo/View Controllers/TicketDetailTableViewController.swift b/Sluggo/View Controllers/TicketDetailTableViewController.swift index 0b9d940..36db40d 100644 --- a/Sluggo/View Controllers/TicketDetailTableViewController.swift +++ b/Sluggo/View Controllers/TicketDetailTableViewController.swift @@ -26,9 +26,8 @@ class TicketDetailTableViewController: UITableViewController { var teamMembers: [MemberRecord?] = [nil] var currentMember: MemberRecord? = nil - init? (coder: NSCoder, identity: AppIdentity, ticket: TicketRecord?, completion: (() -> Void)?) { + init? (coder: NSCoder, identity: AppIdentity, ticket: TicketRecord?) { self.identity = identity - self.completion = completion self.ticket = ticket super.init(coder: coder) } diff --git a/Sluggo/View Controllers/TicketDetailViewController.swift b/Sluggo/View Controllers/TicketDetailViewController.swift index 6df069f..1744bdd 100644 --- a/Sluggo/View Controllers/TicketDetailViewController.swift +++ b/Sluggo/View Controllers/TicketDetailViewController.swift @@ -25,15 +25,13 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate, UIPicker var identity: AppIdentity var ticket: TicketRecord? var pickerView: UIPickerView = UIPickerView() - var completion: (() ->Void)? var editingTicket = false var teamMembers: [MemberRecord?] = [nil] var currentMember: MemberRecord? = nil - init? (coder: NSCoder, identity: AppIdentity, ticket: TicketRecord?, completion: (() -> Void)?) { + init? (coder: NSCoder, identity: AppIdentity, ticket: TicketRecord?) { self.identity = identity - self.completion = completion self.ticket = ticket super.init(coder: coder) } @@ -164,7 +162,8 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate, UIPicker switch(result){ case .success(_): DispatchQueue.main.async { - self.dismiss(animated: true, completion: self.completion) + self.dismiss(animated: true, completion: nil) + NotificationCenter.default.post(name: .refreshTrigger, object: self) } case .failure(let error): DispatchQueue.main.async { @@ -242,7 +241,7 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate, UIPicker @IBSegueAction func segueToDetail(_ coder: NSCoder) -> TicketDetailTableViewController? { - return TicketDetailTableViewController(coder: coder, identity: identity, ticket: ticket, completion: nil) + return TicketDetailTableViewController(coder: coder, identity: identity, ticket: ticket) } diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index 32ea1e7..31d2c88 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -49,7 +49,7 @@ class TicketListController: UITableViewController { let identity = self.identity let detailStoryboard = UIStoryboard(name: "TicketDetail", bundle: nil) if let vc = detailStoryboard.instantiateViewController(identifier: "TicketDetail", creator:{ coder in - return TicketDetailViewController(coder: coder, identity: identity, ticket: self.tickets[indexPath.row], completion: nil) + return TicketDetailViewController(coder: coder, identity: identity, ticket: self.tickets[indexPath.row]) }) as TicketDetailViewController? { navigationController?.pushViewController(vc, animated: true) } @@ -59,9 +59,7 @@ class TicketListController: UITableViewController { let identity = self.identity let detailStoryboard = UIStoryboard(name: "TicketDetail", bundle: nil) if let vc = detailStoryboard.instantiateViewController(identifier: "TicketDetail", creator:{ coder in - return TicketDetailViewController(coder: coder, identity: identity, ticket: nil, completion: { - self.handleRefreshAction() - }) + return TicketDetailViewController(coder: coder, identity: identity, ticket: nil) }) as TicketDetailViewController? { self.present(vc, animated: true, completion: nil) } diff --git a/SluggoTests/TicketManagerTests.swift b/SluggoTests/TicketManagerTests.swift index 490cd3f..7742c1e 100644 --- a/SluggoTests/TicketManagerTests.swift +++ b/SluggoTests/TicketManagerTests.swift @@ -12,16 +12,28 @@ class TicketManagerTests: XCTestCase { var identity: AppIdentity! var exampleUser: UserRecord! var exampleMember: MemberRecord! - + override func setUp() { let team = TeamRecord(id: 1, name: "bugslotics", object_uuid: UUID(), ticket_head: 1, created: Date(timeIntervalSince1970: 0), activated: nil, deactivated: nil) - + identity = AppIdentity() identity.team = team identity.baseAddress = Constants.Config.URL_BASE - - exampleUser = UserRecord(id: 1, email: "sammy@ucsc.edu", first_name: "Sammy", last_name: "Slug", username: "sammytheslug") - exampleMember = MemberRecord(id: UUID().uuidString, owner: exampleUser, team_id: 1, object_uuid: UUID(), role: "AD", bio: nil, created: Date(timeIntervalSince1970: 0), activated: nil, deactivated: nil) + + exampleUser = UserRecord(id: 1, + email: "sammy@ucsc.edu", + first_name: "Sammy", + last_name: "Slug", + username: "sammytheslug") + exampleMember = MemberRecord(id: UUID().uuidString, + owner: exampleUser, + team_id: 1, + object_uuid: UUID(), + role: "AD", + bio: nil, + created: Date(timeIntervalSince1970: 0), + activated: nil, + deactivated: nil) } func testListURLGeneration() throws { From ea131b4166d734ae2702e8294453f68342b1e54c Mon Sep 17 00:00:00 2001 From: Samuel Schmidt Date: Wed, 12 May 2021 17:18:25 -0700 Subject: [PATCH 151/224] fix query --- Sluggo/Models/TicketFilterParameters.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sluggo/Models/TicketFilterParameters.swift b/Sluggo/Models/TicketFilterParameters.swift index 766fd77..d41a8c4 100644 --- a/Sluggo/Models/TicketFilterParameters.swift +++ b/Sluggo/Models/TicketFilterParameters.swift @@ -27,11 +27,11 @@ struct TicketFilterParameters { } if let tag = ticketTag { - paramString += "&tag_list__title=\(tag.title)" + paramString += "&tag_list__id=\(tag.id)" } if let status = ticketStatus { - paramString += "&status__title=\(status.title)" + paramString += "&status__id=\(status.id)" } return paramString From 4fc2490ab34c329181eb3f6824b7debcca5f2292 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Wed, 12 May 2021 20:10:53 -0700 Subject: [PATCH 152/224] new ticketDetail replaces old. Serialization works for save, now we need create. --- Sluggo/Storyboards/Sidebar.storyboard | 10 +- Sluggo/Storyboards/Ticket.storyboard | 16 +-- Sluggo/Storyboards/TicketDetail.storyboard | 54 +++---- .../HomeTableViewController.swift | 6 +- .../TicketDetailTableViewController.swift | 133 ++++++++++++++++-- .../TicketDetailViewController.swift | 24 +--- .../TicketListController.swift | 8 +- 7 files changed, 170 insertions(+), 81 deletions(-) diff --git a/Sluggo/Storyboards/Sidebar.storyboard b/Sluggo/Storyboards/Sidebar.storyboard index 701c00e..90d4d02 100644 --- a/Sluggo/Storyboards/Sidebar.storyboard +++ b/Sluggo/Storyboards/Sidebar.storyboard @@ -1,8 +1,8 @@ - + - + @@ -18,14 +18,14 @@ - + - + @@ -273,9 +269,17 @@ - + + + + + + + + + - + @@ -284,7 +288,7 @@ - + diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index dd098fc..4b88e31 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -192,13 +192,13 @@ class HomeTableViewController: UITableViewController { // } // } - @IBSegueAction func gotoTicketDetail(_ coder: NSCoder) -> TicketDetailViewController? { + @IBSegueAction func gotoTicketDetail(_ coder: NSCoder) -> TicketDetailTableViewController? { let selectedPath = tableView.indexPathForSelectedRow switch(selectedPath!.section) { case HomepageCategories.assigned.rawValue: - return TicketDetailViewController(coder: coder, identity: self.identity, ticket: assignedTickets[selectedPath!.row]) + return TicketDetailTableViewController(coder: coder, identity: self.identity, ticket: assignedTickets[selectedPath!.row]) case HomepageCategories.pinned.rawValue: - return TicketDetailViewController(coder: coder, identity: self.identity, ticket: pinnedTickets[selectedPath!.row].ticket) + return TicketDetailTableViewController(coder: coder, identity: self.identity, ticket: pinnedTickets[selectedPath!.row].ticket) default: fatalError("Nothing should be selectable from unimplemented categories!") } diff --git a/Sluggo/View Controllers/TicketDetailTableViewController.swift b/Sluggo/View Controllers/TicketDetailTableViewController.swift index 36db40d..134df4c 100644 --- a/Sluggo/View Controllers/TicketDetailTableViewController.swift +++ b/Sluggo/View Controllers/TicketDetailTableViewController.swift @@ -12,14 +12,13 @@ class TicketDetailTableViewController: UITableViewController { // MARK: Outlets @IBOutlet var ticketTitle: UITextField! @IBOutlet var ticketDescription: UITextView! - @IBOutlet var assignedLabel: UILabel! + @IBOutlet var assignedField: UITextField! @IBOutlet var dueDatePicker: UIDatePicker! @IBOutlet var dueDateSwitch: UISwitch! // MARK: Variables var identity: AppIdentity var ticket: TicketRecord? - var completion: (() ->Void)? var pickerView: UIPickerView = UIPickerView() var editingTicket = false @@ -42,22 +41,17 @@ class TicketDetailTableViewController: UITableViewController { dueDatePicker.isEnabled = dueDateSwitch.isOn let memberManager = MemberManager(identity: self.identity) - memberManager.listTeamMembers(){ result in - switch(result){ - case .success(let record): + memberManager.listFromTeams(page: 1) { result in + self.processResult(result: result, onSuccess: { + record in self.teamMembers = [nil] for user in record.results{ self.teamMembers.append(user) } - - case .failure(let error): - DispatchQueue.main.async { - let alert = UIAlertController.errorController(error: error) - self.present(alert, animated: true, completion: nil) - } - } + }) } - + pickerView.dataSource = self + pickerView.delegate = self updateUI() } @@ -66,11 +60,122 @@ class TicketDetailTableViewController: UITableViewController { ticketTitle.text = self.ticket?.title ?? "" ticketDescription.text = ticket?.description ?? "" - assignedLabel.text = ticket?.assigned_user?.owner.username ?? "No Assigned User" + assignedField.text = ticket?.assigned_user?.owner.username ?? "No Assigned User" dueDatePicker.date = ticket?.due_date ?? Date() dueDateSwitch.isEnabled = (ticket?.due_date != nil) dueDatePicker.isEnabled = dueDateSwitch.isEnabled + assignedField.isEnabled = true + setEditMode(false) + createUserPicker() + } + + func setEditMode(_ editing: Bool) { + editingTicket = editing + ticketTitle.isUserInteractionEnabled = editing + ticketDescription.isUserInteractionEnabled = editing + dueDatePicker.isEnabled = true + dueDatePicker.isUserInteractionEnabled = editing + dueDateSwitch.isUserInteractionEnabled = editing + assignedField.isUserInteractionEnabled = editing + dueDateSwitch.isHidden = !editing + } + + func doSave() { + let title = ticketTitle.text ?? "Default Title" + let description = ticketDescription.text + let date = dueDateSwitch.isOn ? dueDatePicker.date : nil + let member = currentMember?.id + + if editingTicket { + ticket!.title = title + ticket!.description = description + ticket!.due_date = date + ticket!.assigned_user = currentMember + + let manager = TicketManager(identity) + + manager.updateTicket(ticket: ticket!) { + result in + self.processResult(result: result) { + _ in + DispatchQueue.main.async { + self.setEditMode(false) + NotificationCenter.default.post(name: .refreshTrigger, object: self) + } + } + } + } + else { + let ticket = WriteTicketRecord(tag_list: [], + assigned_user: member, + status: nil, + title: title, + description: description, + due_date: date) + let manager = TicketManager(identity) + manager.makeTicket(ticket: ticket) { + result in + self.processResult(result: result) { + _ in + DispatchQueue.main.async { + NotificationCenter.default.post(name: .refreshTrigger, object: self) + self.dismiss(animated: true, completion: nil) + } + } + } + } + } + + @IBAction func EditButtonHit(_ sender: Any) { + if let barButton = sender as? UIBarButtonItem { + if barButton.title == "Edit" { + setEditMode(true) + barButton.title = "Save" + } else { + doSave() + barButton.title = "Edit" + } + } } +} + +extension TicketDetailTableViewController: UIPickerViewDelegate, UIPickerViewDataSource { + + func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + + func createUserPicker() { + let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 35)) + toolbar.sizeToFit() + toolbar.translatesAutoresizingMaskIntoConstraints = false + + //create bar button + let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(userPicked)) + toolbar.setItems([doneButton], animated: true) + assignedField.inputAccessoryView = toolbar + + assignedField.inputView = pickerView + } + + @objc func userPicked(){ + assignedField.text = currentMember?.owner.username ?? "No Assigned User" + self.view.endEditing(true) + } + + func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return teamMembers.count + } + + func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + return teamMembers[row]?.owner.username ?? "No Assigned User" + } + + func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + currentMember = teamMembers[row] + } + + } diff --git a/Sluggo/View Controllers/TicketDetailViewController.swift b/Sluggo/View Controllers/TicketDetailViewController.swift index 734e017..c42bafb 100644 --- a/Sluggo/View Controllers/TicketDetailViewController.swift +++ b/Sluggo/View Controllers/TicketDetailViewController.swift @@ -46,20 +46,14 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate, UIPicker let memberManager = MemberManager(identity: self.identity) - memberManager.listFromTeams(page: 1){ (result: Result, Error>) -> Void in - switch(result){ - case .success(let record): + memberManager.listFromTeams(page: 1){ result in + self.processResult(result: result, onSuccess: { + record in self.teamMembers = [nil] for user in record.results{ self.teamMembers.append(user) } - - case .failure(let error): - DispatchQueue.main.async { - let alert = UIAlertController.errorController(error: error) - self.present(alert, animated: true, completion: nil) - } - } + }) } pickerView.dataSource = self @@ -148,10 +142,7 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate, UIPicker } case .failure(let error): - DispatchQueue.main.async { - let alert = UIAlertController.errorController(error: error) - self.present(alert, animated: true, completion: nil) - } + self.presentErrorFromMainThread(error: error) } } } @@ -166,10 +157,7 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate, UIPicker NotificationCenter.default.post(name: .refreshTrigger, object: self) } case .failure(let error): - DispatchQueue.main.async { - let alert = UIAlertController.errorController(error: error) - self.present(alert, animated: true, completion: nil) - } + self.presentErrorFromMainThread(error: error) } } } diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index 82db918..f29c881 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -71,8 +71,8 @@ class TicketListController: UITableViewController { let identity = self.identity let detailStoryboard = UIStoryboard(name: "TicketDetail", bundle: nil) if let vc = detailStoryboard.instantiateViewController(identifier: "TicketDetail", creator:{ coder in - return TicketDetailViewController(coder: coder, identity: identity, ticket: self.tickets[indexPath.row]) - }) as TicketDetailViewController? { + return TicketDetailTableViewController(coder: coder, identity: identity, ticket: self.tickets[indexPath.row]) + }) as TicketDetailTableViewController? { navigationController?.pushViewController(vc, animated: true) } } @@ -82,8 +82,8 @@ class TicketListController: UITableViewController { let identity = self.identity let detailStoryboard = UIStoryboard(name: "TicketDetail", bundle: nil) if let vc = detailStoryboard.instantiateViewController(identifier: "TicketDetail", creator:{ coder in - return TicketDetailViewController(coder: coder, identity: identity, ticket: nil) - }) as TicketDetailViewController? { + return TicketDetailTableViewController(coder: coder, identity: identity, ticket: nil) + }) as TicketDetailTableViewController? { self.present(vc, animated: true, completion: nil) } } From 07f2f90ca244e52db9747602692e24ed1b7bd4f9 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Thu, 13 May 2021 15:25:14 -0700 Subject: [PATCH 153/224] SwiftLint changes. --- Sluggo.xcodeproj/project.pbxproj | 21 +++ Sluggo/AppDelegate.swift | 20 +- Sluggo/Components/TicketTableViewCell.swift | 6 +- Sluggo/Extensions/UIColor.swift | 2 +- Sluggo/Models/AppIdentity.swift | 76 ++++---- Sluggo/Models/Codables/MemberRecord.swift | 4 +- Sluggo/Models/Codables/PaginatedList.swift | 4 +- .../Models/Codables/PinnedTicketRecord.swift | 2 +- Sluggo/Models/Codables/StatusRecord.swift | 1 + Sluggo/Models/Codables/TagRecord.swift | 3 +- Sluggo/Models/Codables/TeamRecord.swift | 3 +- Sluggo/Models/Codables/TicketRecord.swift | 7 +- Sluggo/Models/Codables/TokenRecord.swift | 1 - Sluggo/Models/Codables/UserRecord.swift | 1 + Sluggo/Models/Constants.swift | 15 +- Sluggo/Models/Exceptions.swift | 31 ++-- .../Interfaces/TeamPaginatedListable.swift | 2 +- Sluggo/Models/JsonLoader.swift | 52 +++--- Sluggo/Models/Managers/MemberManager.swift | 51 +++--- .../Models/Managers/PinnedTicketManager.swift | 43 +++-- Sluggo/Models/Managers/StatusManager.swift | 16 +- Sluggo/Models/Managers/TagManager.swift | 22 ++- Sluggo/Models/Managers/TeamManager.swift | 21 ++- Sluggo/Models/Managers/TicketManager.swift | 61 ++++--- Sluggo/Models/Managers/UserManager.swift | 30 +-- Sluggo/Models/Methods.swift | 41 ++--- Sluggo/Models/SidebarData.swift | 1 + Sluggo/Models/TicketFilterParameters.swift | 18 +- Sluggo/Models/URLRequestBuilder.swift | 20 +- .../Extensions/ViewControllerExtensions.swift | 22 +-- .../HomeTableViewController.swift | 75 ++++---- .../LaunchViewController.swift | 50 +++-- .../LoginViewController.swift | 50 +++-- .../View Controllers/RootViewController.swift | 31 ++-- ...SluggoSidebarContainerViewController.swift | 71 ++++---- .../TeamSelectorContainerViewController.swift | 12 +- .../TeamTableViewContainer.swift | 13 +- .../TeamTableViewController.swift | 32 ++-- .../TicketDetailTableViewController.swift | 75 ++++---- .../TicketDetailViewController.swift | 171 +++++++++--------- .../TicketFilterTableViewController.swift | 64 +++---- .../TicketListController.swift | 78 ++++---- SluggoTests/MemberTests.swift | 70 +++++-- SluggoTests/SluggoTests.swift | 8 +- SluggoTests/TagTests.swift | 23 ++- SluggoTests/TicketManagerTests.swift | 20 +- SluggoTests/TicketTests.swift | 44 +++-- SluggoTests/UserTests.swift | 28 +-- SluggoUITests/SluggoUITests.swift | 9 +- 49 files changed, 818 insertions(+), 703 deletions(-) diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 0499fc4..e014fc6 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -368,6 +368,7 @@ 7D99639E261EF3A4002338E7 /* Sources */, 7D99639F261EF3A4002338E7 /* Frameworks */, 7D9963A0261EF3A4002338E7 /* Resources */, + 2F0A5167264DB61C00276172 /* ShellScript */, ); buildRules = ( ); @@ -494,6 +495,26 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 2F0A5167264DB61C00276172 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 7D99639E261EF3A4002338E7 /* Sources */ = { isa = PBXSourcesBuildPhase; diff --git a/Sluggo/AppDelegate.swift b/Sluggo/AppDelegate.swift index d74bd3e..dc669c1 100644 --- a/Sluggo/AppDelegate.swift +++ b/Sluggo/AppDelegate.swift @@ -11,20 +11,21 @@ import UIKit class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + func application(_ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. configureInitialViewController() return true } - + // https://stackoverflow.com/questions/33714671/value-of-type-appdelegate-has-no-member-window private func configureInitialViewController() { let storyboard = UIStoryboard(name: "Main", bundle: nil) let initialViewController: UIViewController - + window = UIWindow() let identity = AppIdentity.loadFromDisk() ?? AppIdentity() - + initialViewController = storyboard.instantiateViewController(identifier: "launchView", creator: { coder in return LaunchViewController(coder: coder, identity: identity) }) @@ -34,7 +35,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // MARK: UISceneSession Lifecycle -// func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { +// func application(_ application: UIApplication, +// configurationForConnecting connectingSceneSession: UISceneSession, +// options: UIScene.ConnectionOptions) -> UISceneConfiguration { // // Called when a new scene session is being created. // // Use this method to select a configuration to create the new scene with. // return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) @@ -42,9 +45,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // // func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { // // Called when the user discards a scene session. -// // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. -// // Use this method to release any resources that were specific to the discarded scenes, as they will not return. +// // If any sessions were discarded while the application was not running, +// // this will be called shortly after application:didFinishLaunchingWithOptions. +// // Use this method to release any resources that were specific to the discarded scenes, +// // as they will not return. // } } - diff --git a/Sluggo/Components/TicketTableViewCell.swift b/Sluggo/Components/TicketTableViewCell.swift index cd6f6d9..425738e 100644 --- a/Sluggo/Components/TicketTableViewCell.swift +++ b/Sluggo/Components/TicketTableViewCell.swift @@ -15,20 +15,20 @@ class TicketTableViewCell: UITableViewCell { } @IBOutlet var titleLabel: UILabel! @IBOutlet var assignedLabel: UILabel! - + func loadFromTicketRecord(ticket: TicketRecord) { let formatter = DateFormatter() formatter.dateStyle = .medium formatter.timeStyle = .none formatter.locale = Locale(identifier: "en_US") - + titleLabel.text = "\(ticket.ticket_number) | \(ticket.title.capitalized)" if let date = ticket.due_date { assignedLabel.text = formatter.string(from: date) } else { assignedLabel.text = "" } - + if let color = ticket.status?.color { containerView.backgroundColor = UIColor(hex: color.lowercased()) } else { diff --git a/Sluggo/Extensions/UIColor.swift b/Sluggo/Extensions/UIColor.swift index 37fdd74..0aa04b5 100644 --- a/Sluggo/Extensions/UIColor.swift +++ b/Sluggo/Extensions/UIColor.swift @@ -7,9 +7,9 @@ import UIKit - // from: https://www.hackingwithswift.com/example-code/uicolor/how-to-convert-a-hex-color-to-a-uicolor // I did not write this nor do I know what it does +// swiftlint:disable identifier_name extension UIColor { public convenience init?(hex: String) { let r, g, b, a: CGFloat diff --git a/Sluggo/Models/AppIdentity.swift b/Sluggo/Models/AppIdentity.swift index c926185..c507253 100644 --- a/Sluggo/Models/AppIdentity.swift +++ b/Sluggo/Models/AppIdentity.swift @@ -8,117 +8,115 @@ import Foundation class AppIdentity: Codable { - + private var _authenticatedUser: AuthRecord? private var _team: TeamRecord? private var _token: String? private var _pageSize = 10 private var _baseAddress: String = Constants.Config.URL_BASE private var persist: Bool = false - + // MARK: Computed Properties var authenticatedUser: AuthRecord? { + get { + return _authenticatedUser + } set(newUser) { _authenticatedUser = newUser enqueueSave() } - get { - return _authenticatedUser - } } var team: TeamRecord? { + get { + return _team + } set(newTeam) { _team = newTeam enqueueSave() } - get { - return _team - } } var token: String? { + get { + return _token + } set (newToken) { _token = newToken enqueueSave() } - get { - return _token - } } var pageSize: Int { + get { + return _pageSize + } set(newPageSize) { _pageSize = newPageSize enqueueSave() } - get { - return _pageSize - } } var baseAddress: String { + get { + return _baseAddress + } set(newBaseAddress) { _baseAddress = newBaseAddress enqueueSave() } - get { - return _baseAddress - } } - + private static var persistencePath: URL { - get { - let path = URL(fileURLWithPath: NSHomeDirectory().appending("/Library/appdata.json")) - print(path) - return path - } + let path = URL(fileURLWithPath: NSHomeDirectory().appending("/Library/appdata.json")) + print(path) + return path } - + public func setPersistData(persist: Bool) { self.persist = persist if persist { enqueueSave() } else { - let _ = AppIdentity.deletePersistenceFile() + _ = AppIdentity.deletePersistenceFile() } } - + private func enqueueSave() { if self.persist { DispatchQueue.global().async { - let _ = self.saveToDisk() + _ = self.saveToDisk() } } } - + static func loadFromDisk() -> AppIdentity? { guard let persistenceFileContents = try? String(contentsOf: persistencePath) else { return nil } - + guard let persistenceFileData = persistenceFileContents.data(using: .utf8) else { // File exists but was corrupted, so clean it up - let _ = deletePersistenceFile() + _ = deletePersistenceFile() return nil } - + let appIdentity: AppIdentity? = JsonLoader.decode(data: persistenceFileData) - if(appIdentity == nil) { + if appIdentity == nil { // File exists, but failed to deserialize, so clean it up - let _ = deletePersistenceFile() + _ = deletePersistenceFile() } - + return appIdentity } - + func saveToDisk() -> Bool { guard let appIdentityData = JsonLoader.encode(object: self) else { print("Failed to encode app identity with JSON, could not persist") return false } - + guard let appIdentityContents = String(data: appIdentityData, encoding: .utf8) else { print("Failed to encode app identity encoded data as string, could not persist") return false } - + do { try appIdentityContents.write(to: AppIdentity.persistencePath, atomically: true, encoding: .utf8) return true @@ -127,14 +125,14 @@ class AppIdentity: Codable { return false } } - + static func deletePersistenceFile() -> Bool { // Succeed if the persistence file doesn't exist. This allows us to clean up a bad file. if !(FileManager.default.fileExists(atPath: self.persistencePath.path)) { print("Attempted to delete persistence file when it doesn't exist, returning") return true } - + do { try FileManager.default.removeItem(at: self.persistencePath) return true diff --git a/Sluggo/Models/Codables/MemberRecord.swift b/Sluggo/Models/Codables/MemberRecord.swift index 89f1696..82f2e1b 100644 --- a/Sluggo/Models/Codables/MemberRecord.swift +++ b/Sluggo/Models/Codables/MemberRecord.swift @@ -7,7 +7,7 @@ import Foundation import NullCodable - +// swiftlint:disable identifier_name struct MemberRecord: Codable, HasTitle { var id: String var owner: UserRecord @@ -18,7 +18,7 @@ struct MemberRecord: Codable, HasTitle { var created: Date @NullCodable var activated: Date? @NullCodable var deactivated: Date? - + func getTitle() -> String { return owner.username } diff --git a/Sluggo/Models/Codables/PaginatedList.swift b/Sluggo/Models/Codables/PaginatedList.swift index 8351add..6a61192 100644 --- a/Sluggo/Models/Codables/PaginatedList.swift +++ b/Sluggo/Models/Codables/PaginatedList.swift @@ -8,10 +8,10 @@ import Foundation import NullCodable -struct PaginatedList : Codable { +struct PaginatedList: Codable { var count: Int @NullCodable var next: String? @NullCodable var previous: String? let results: [T] - + } diff --git a/Sluggo/Models/Codables/PinnedTicketRecord.swift b/Sluggo/Models/Codables/PinnedTicketRecord.swift index 27a383b..d94a27b 100644 --- a/Sluggo/Models/Codables/PinnedTicketRecord.swift +++ b/Sluggo/Models/Codables/PinnedTicketRecord.swift @@ -6,7 +6,7 @@ // import Foundation - +// swiftlint:disable identifier_name struct PinnedTicketRecord: Codable { let id: String // Primary key let object_uuid: String diff --git a/Sluggo/Models/Codables/StatusRecord.swift b/Sluggo/Models/Codables/StatusRecord.swift index 8405535..ceab83f 100644 --- a/Sluggo/Models/Codables/StatusRecord.swift +++ b/Sluggo/Models/Codables/StatusRecord.swift @@ -8,6 +8,7 @@ import Foundation import NullCodable +// swiftlint:disable identifier_name struct StatusRecord: Codable, HasTitle { var id: Int var object_uuid: UUID diff --git a/Sluggo/Models/Codables/TagRecord.swift b/Sluggo/Models/Codables/TagRecord.swift index 549b33c..7072b11 100644 --- a/Sluggo/Models/Codables/TagRecord.swift +++ b/Sluggo/Models/Codables/TagRecord.swift @@ -8,6 +8,7 @@ import Foundation import NullCodable +// swiftlint:disable identifier_name struct TagRecord: Codable, HasTitle { var id: Int var team_id: Int @@ -16,7 +17,7 @@ struct TagRecord: Codable, HasTitle { var created: Date @NullCodable var activated: Date? @NullCodable var deactivated: Date? - + func getTitle() -> String { return title } diff --git a/Sluggo/Models/Codables/TeamRecord.swift b/Sluggo/Models/Codables/TeamRecord.swift index d3d8c30..c82f041 100644 --- a/Sluggo/Models/Codables/TeamRecord.swift +++ b/Sluggo/Models/Codables/TeamRecord.swift @@ -8,6 +8,7 @@ import Foundation import NullCodable +// swiftlint:disable identifier_name struct TeamRecord: Codable, Equatable { var id: Int var name: String @@ -16,7 +17,7 @@ struct TeamRecord: Codable, Equatable { var created: Date @NullCodable var activated: Date? @NullCodable var deactivated: Date? - + static func == (lhs: TeamRecord, rhs: TeamRecord) -> Bool { return lhs.object_uuid == rhs.object_uuid } diff --git a/Sluggo/Models/Codables/TicketRecord.swift b/Sluggo/Models/Codables/TicketRecord.swift index 35df060..9f10a5d 100644 --- a/Sluggo/Models/Codables/TicketRecord.swift +++ b/Sluggo/Models/Codables/TicketRecord.swift @@ -8,7 +8,7 @@ import Foundation import NullCodable - +// swiftlint:disable identifier_name struct TicketRecord: Codable { var id: Int var ticket_number: Int @@ -23,11 +23,10 @@ struct TicketRecord: Codable { var created: Date @NullCodable var activated: Date? @NullCodable var deactivated: Date? - - + } -struct WriteTicketRecord: Codable{ +struct WriteTicketRecord: Codable { var tag_list: [Int] @NullCodable var assigned_user: String? @NullCodable var status: Int? diff --git a/Sluggo/Models/Codables/TokenRecord.swift b/Sluggo/Models/Codables/TokenRecord.swift index 53d5696..1af5f76 100644 --- a/Sluggo/Models/Codables/TokenRecord.swift +++ b/Sluggo/Models/Codables/TokenRecord.swift @@ -7,7 +7,6 @@ import Foundation - struct TokenRecord: Codable { var key: String } diff --git a/Sluggo/Models/Codables/UserRecord.swift b/Sluggo/Models/Codables/UserRecord.swift index 9e72725..50cb3d0 100644 --- a/Sluggo/Models/Codables/UserRecord.swift +++ b/Sluggo/Models/Codables/UserRecord.swift @@ -8,6 +8,7 @@ import Foundation import NullCodable +// swiftlint:disable identifier_name struct UserRecord: Codable { var id: Int var email: String diff --git a/Sluggo/Models/Constants.swift b/Sluggo/Models/Constants.swift index 59d4b7e..e259aec 100644 --- a/Sluggo/Models/Constants.swift +++ b/Sluggo/Models/Constants.swift @@ -7,21 +7,22 @@ import Foundation +// swiftlint:disable identifier_name struct Constants { - + struct FilePaths { - + static let persistencePath = "/Library/sluggodata.json" - + } - + struct Config { - + static let URL_BASE = "http://127.0.0.1:8000/" static let kURL = "url" - + } - + struct Signals { static let TEAM_CHANGE_NOTIFICATION = NSNotification.Name(rawValue: "teamChange") } diff --git a/Sluggo/Models/Exceptions.swift b/Sluggo/Models/Exceptions.swift index 168fdc9..3eb7ce8 100644 --- a/Sluggo/Models/Exceptions.swift +++ b/Sluggo/Models/Exceptions.swift @@ -20,38 +20,39 @@ extension UIAlertController { static func errorController(error: Error, handler: ((UIAlertAction) -> Void)?) -> UIAlertController { // Setup message var message: String? - + if let failedRequestError = error as? RESTException { - switch(failedRequestError) { + switch failedRequestError { case .failedRequest(let errMsg): message = errMsg } } - + if let exceptionError = error as? Exception { - switch(exceptionError) { + switch exceptionError { case .runtimeError(let errMsg): message = errMsg } } - - let alert = UIAlertController(title: "Error", message: message ?? "Error message not provided", preferredStyle: .alert) + + let alert = UIAlertController(title: "Error", + message: message ?? "Error message not provided", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default, handler: handler)) - + return alert } - + static func errorController(error: Error) -> UIAlertController { return self.errorController(error: error, handler: nil) } - - static func createAndPresentError(vc: UIViewController, error: Error, completion: ((UIAlertAction) -> Void)?) { + + static func createAndPresentError(view: UIViewController, error: Error, completion: ((UIAlertAction) -> Void)?) { let alertController = UIAlertController.errorController(error: error, handler: completion) - - vc.present(alertController, animated: true, completion: nil) + + view.present(alertController, animated: true, completion: nil) } - - static func createAndPresentError(vc: UIViewController, error: Error) { - UIAlertController.createAndPresentError(vc: vc, error: error, completion: nil) + + static func createAndPresentError(view: UIViewController, error: Error) { + UIAlertController.createAndPresentError(view: view, error: error, completion: nil) } } diff --git a/Sluggo/Models/Interfaces/TeamPaginatedListable.swift b/Sluggo/Models/Interfaces/TeamPaginatedListable.swift index 90e6808..be66140 100644 --- a/Sluggo/Models/Interfaces/TeamPaginatedListable.swift +++ b/Sluggo/Models/Interfaces/TeamPaginatedListable.swift @@ -9,5 +9,5 @@ import Foundation protocol TeamPaginatedListable { associatedtype Record where Record: Codable - func listFromTeams(page: Int, completionHandler: @escaping(Result, Error>) -> Void) -> Void + func listFromTeams(page: Int, completionHandler: @escaping(Result, Error>) -> Void) } diff --git a/Sluggo/Models/JsonLoader.swift b/Sluggo/Models/JsonLoader.swift index ab8acd0..1339f8c 100644 --- a/Sluggo/Models/JsonLoader.swift +++ b/Sluggo/Models/JsonLoader.swift @@ -12,73 +12,77 @@ class JsonLoader { // Attempt decoding let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 - - var decodedValue: T? = nil + + var decodedValue: T? do { decodedValue = try decoder.decode(T.self, from: data) } catch DecodingError.dataCorrupted(let context) { print("\(context.codingPath) . \(context.debugDescription)") - } catch (let context) { - switch (context) { + } catch let context { + switch context { case DecodingError.dataCorrupted(let value): print(value.debugDescription) - break default: print(context.localizedDescription) } } - return decodedValue } - + static func encode(object data: T) -> Data? { let encoder = JSONEncoder() encoder.dateEncodingStrategy = .iso8601 encoder.outputFormatting = .prettyPrinted - + // Attempt encoding guard let jsonData = try? encoder.encode(data) else { print("Failed to encode object into JSON data.") return nil - + } - + // Attempt stringifying the data, this is failable, which is fine since property is optional. return jsonData } - - static func executeCodableRequest(request: URLRequest, completionHandler: @escaping (Result) -> Void) -> Void { - + + static func executeCodableRequest(request: URLRequest, + completionHandler: @escaping (Result) -> Void) { + let session = URLSession.shared session.dataTask(with: request, completionHandler: { data, response, error -> Void in if error != nil { completionHandler(.failure(Exception.runtimeError(message: "Server Error!"))) return } - + // swiftlint:disable:next force_cast let resp = response as! HTTPURLResponse - if (resp.statusCode <= 299 && resp.statusCode >= 200) { + if resp.statusCode <= 299 && resp.statusCode >= 200 { if let fetchedData = data { guard let record: T = JsonLoader.decode(data: fetchedData) else { print(String(data: fetchedData, encoding: .utf8) ?? "Failed to print returned values") - completionHandler(.failure(RESTException.failedRequest(message: "Failure to decode retrieved model in JsonLoader Codable Request"))) - return; + let errorMessage = "Failure to decode retrieved model in JsonLoader Codable Request" + completionHandler(.failure(RESTException.failedRequest(message: errorMessage))) + return } completionHandler(.success(record)) - return; + return } else { - completionHandler(.failure(RESTException.failedRequest(message: "Failure to decode retrieved data in JsonLoader Codable request"))) - return; + let errorMessage = "Failure to decode retrieved data in JsonLoader Codable request" + completionHandler(.failure(RESTException.failedRequest(message: errorMessage))) + return } } else { if let fetchedData = data { - completionHandler(.failure(RESTException.failedRequest(message: "HTTP Error \(resp.statusCode): \(String(data: fetchedData, encoding: .utf8) ?? "A parsing error occurred")"))) - return; + let fetchedString = String(data: fetchedData, encoding: .utf8) ?? "A parsing error occurred" + let errorMessage = "HTTP Error \(resp.statusCode): \(fetchedString)" + completionHandler(.failure(RESTException.failedRequest(message: errorMessage))) + return } - completionHandler(.failure(RESTException.failedRequest(message: "HTTP Error \(resp.statusCode): An unknown error occured."))) - return; + let errorMessage = "HTTP Error \(resp.statusCode): An unknown error occured." + completionHandler(.failure(RESTException.failedRequest(message: errorMessage))) + return } }).resume() } diff --git a/Sluggo/Models/Managers/MemberManager.swift b/Sluggo/Models/Managers/MemberManager.swift index 357fbc2..98112e3 100644 --- a/Sluggo/Models/Managers/MemberManager.swift +++ b/Sluggo/Models/Managers/MemberManager.swift @@ -10,26 +10,31 @@ import CryptoKit class MemberManager: TeamPaginatedListable { - static let urlBase = "/members/" private var identity: AppIdentity - + init(identity: AppIdentity) { self.identity = identity } - + private func makeDetailUrl(id: String) -> URL { - return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + MemberManager.urlBase + "\(id)/")! + let urlString = identity.baseAddress + TeamManager.urlBase + + "\(identity.team!.id)" + MemberManager.urlBase + "\(id)/" + return URL(string: urlString)! } - + private func makeListUrl(page: Int) -> URL { - return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + MemberManager.urlBase + "?page=\(page)")! + let urlString = identity.baseAddress + TeamManager.urlBase + + "\(identity.team!.id)" + MemberManager.urlBase + "?page=\(page)" + return URL(string: urlString)! } - - public func updateMemberRecord(_ memberRecord: MemberRecord, completionHandler: @escaping(Result) -> Void) -> Void { - + + public func updateMemberRecord(_ memberRecord: MemberRecord, + completionHandler: @escaping(Result) -> Void) { + guard let body = JsonLoader.encode(object: memberRecord) else { - completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize member JSON for updateMemberRecord in MemberManager"))) + let errorMessage = "Failed to serialize member JSON for updateMemberRecord in MemberManager" + completionHandler(.failure(Exception.runtimeError(message: errorMessage))) return } @@ -37,42 +42,44 @@ class MemberManager: TeamPaginatedListable { .setData(data: body) .setMethod(method: .PUT) .setIdentity(identity: identity) - + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) - + } - + func listFromTeams(page: Int, completionHandler: @escaping (Result, Error>) -> Void) { - + let requestBuilder = URLRequestBuilder(url: makeListUrl(page: page)) .setIdentity(identity: identity) .setMethod(method: .GET) - + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } - - public func getMemberRecord(user: AuthRecord, identity: AppIdentity, completionHandler: @escaping(Result) -> Void) -> Void { + + public func getMemberRecord(user: AuthRecord, + identity: AppIdentity, + completionHandler: @escaping(Result) -> Void) { // MARK: MD5 hashing convenience func MD5String(for str: String) -> String { let digest = Insecure.MD5.hash(data: str.data(using: .utf8)!) - + return digest.map { String(format: "%02hhx", $0) }.joined() } - + // Calculate the member PK let teamHash = MD5String(for: String(identity.team!.id)) let userHash = MD5String(for: user.username) - + let memberPk = teamHash.appending(userHash) - + // Execute request // TODO: this needs to handle pagination correclty! let request = URLRequestBuilder(url: URL(string: makeDetailUrl(id: memberPk).absoluteString)!) .setMethod(method: .GET) .setIdentity(identity: identity) - + JsonLoader.executeCodableRequest(request: request.getRequest(), completionHandler: completionHandler) } } diff --git a/Sluggo/Models/Managers/PinnedTicketManager.swift b/Sluggo/Models/Managers/PinnedTicketManager.swift index 2d2cc1b..e06161b 100644 --- a/Sluggo/Models/Managers/PinnedTicketManager.swift +++ b/Sluggo/Models/Managers/PinnedTicketManager.swift @@ -11,51 +11,58 @@ class PinnedTicketManager { static let urlBase = "/pinned_tickets/" private var identity: AppIdentity private var member: MemberRecord - + init(identity: AppIdentity, member: MemberRecord) { self.identity = identity self.member = member } - + private func makeListURL() -> URL { - return URL(string: identity.baseAddress + TeamManager.urlBase + String(identity.team!.id) + MemberManager.urlBase + member.id + PinnedTicketManager.urlBase)!; + let urlString = identity.baseAddress + TeamManager.urlBase + String(identity.team!.id) + + MemberManager.urlBase + member.id + PinnedTicketManager.urlBase + return URL(string: urlString)! } - + private func makeDetailURL(desiredID: String) -> URL { return URL(string: makeListURL().absoluteString + "/\(desiredID)/")! } - - public func fetchPinnedTickets(completionHandler: @escaping(Result<[PinnedTicketRecord], Error>) -> Void) -> Void { + + public func fetchPinnedTickets(completionHandler: @escaping(Result<[PinnedTicketRecord], Error>) -> Void) { let requestBuilder = URLRequestBuilder(url: makeListURL()) .setMethod(method: .GET) .setIdentity(identity: identity) - + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } - - public func postPinnedTicket(member: MemberRecord, ticket: TicketRecord, completionHandler: @escaping(Result) -> Void) -> Void { - + + public func postPinnedTicket(member: MemberRecord, + ticket: TicketRecord, + completionHandler: @escaping(Result) -> Void) { + let writeRecord = WritePinnedTicketRecord(ticketID: ticket.id) - + guard let body = JsonLoader.encode(object: writeRecord) else { - completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize write pinned ticket record in PinnedTicketManager"))) + let errorMessage = "Failed to serialize write pinned ticket record in PinnedTicketManager" + completionHandler(.failure(Exception.runtimeError(message: errorMessage))) return } - + let requestBuilder = URLRequestBuilder(url: makeListURL()) .setMethod(method: .POST) .setData(data: body) .setIdentity(identity: identity) - + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } - - public func deletePinnedTicket(member: MemberRecord, pinned: PinnedTicketRecord, completionHandler: @escaping(Result) -> Void) -> Void { - + + public func deletePinnedTicket(member: MemberRecord, + pinned: PinnedTicketRecord, + completionHandler: @escaping(Result) -> Void) { + let requestBuilder = URLRequestBuilder(url: makeDetailURL(desiredID: pinned.id)) .setMethod(method: .DELETE) .setIdentity(identity: identity) - + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } } diff --git a/Sluggo/Models/Managers/StatusManager.swift b/Sluggo/Models/Managers/StatusManager.swift index aa65fbe..f3a648a 100644 --- a/Sluggo/Models/Managers/StatusManager.swift +++ b/Sluggo/Models/Managers/StatusManager.swift @@ -11,24 +11,28 @@ class StatusManager: TeamPaginatedListable { static let urlBase = "/statuses/" private let identity: AppIdentity - + init(identity: AppIdentity) { self.identity = identity } private func makeDetailUrl(statusRecord: StatusRecord) -> URL { - return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + StatusManager.urlBase + "\(statusRecord.id)/")! + let urlString = identity.baseAddress + TeamManager.urlBase + + "\(identity.team!.id)" + StatusManager.urlBase + "\(statusRecord.id)/" + return URL(string: urlString)! } - + private func makeListUrl(page: Int) -> URL { - return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + StatusManager.urlBase + "?page=\(page)")! + let urlString = identity.baseAddress + TeamManager.urlBase + + "\(identity.team!.id)" + StatusManager.urlBase + "?page=\(page)" + return URL(string: urlString)! } - + func listFromTeams(page: Int, completionHandler: @escaping (Result, Error>) -> Void) { let requestBuilder = URLRequestBuilder(url: makeListUrl(page: page)) .setIdentity(identity: identity) .setMethod(method: .GET) - + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } } diff --git a/Sluggo/Models/Managers/TagManager.swift b/Sluggo/Models/Managers/TagManager.swift index c12664c..3e9074d 100644 --- a/Sluggo/Models/Managers/TagManager.swift +++ b/Sluggo/Models/Managers/TagManager.swift @@ -8,28 +8,32 @@ import Foundation class TagManager: TeamPaginatedListable { - + static let urlBase = "/tags/" private let identity: AppIdentity - + init(identity: AppIdentity) { self.identity = identity } - + private func makeDetailUrl(tagRecord: TagRecord) -> URL { - return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + TagManager.urlBase + "\(tagRecord.id)/")! + let urlString = identity.baseAddress + TeamManager.urlBase + + "\(identity.team!.id)" + TagManager.urlBase + "\(tagRecord.id)/" + return URL(string: urlString)! } - + private func makeListUrl(page: Int) -> URL { - return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + TagManager.urlBase + "?page=\(page)")! + let urlString = identity.baseAddress + TeamManager.urlBase + + "\(identity.team!.id)" + TagManager.urlBase + "?page=\(page)" + return URL(string: urlString)! } - + func listFromTeams(page: Int, completionHandler: @escaping (Result, Error>) -> Void) { let requestBuilder = URLRequestBuilder(url: makeListUrl(page: page)) .setIdentity(identity: identity) .setMethod(method: .GET) - + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } - + } diff --git a/Sluggo/Models/Managers/TeamManager.swift b/Sluggo/Models/Managers/TeamManager.swift index e7b3145..f860204 100644 --- a/Sluggo/Models/Managers/TeamManager.swift +++ b/Sluggo/Models/Managers/TeamManager.swift @@ -9,28 +9,29 @@ import Foundation class TeamManager: TeamPaginatedListable { - static let urlBase = "api/teams/" private var identity: AppIdentity - + init(identity: AppIdentity) { self.identity = identity } - + func listFromTeams(page: Int, completionHandler: @escaping (Result, Error>) -> Void) { - - let requestBuilder = URLRequestBuilder(url: URL(string: identity.baseAddress + TeamManager.urlBase + "?page=\(page)")!) + + let urlString = identity.baseAddress + TeamManager.urlBase + "?page=\(page)" + let requestBuilder = URLRequestBuilder(url: URL(string: urlString)!) .setIdentity(identity: identity) .setMethod(method: .GET) - + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } - - public func getTeam(team: TeamRecord, completionHandler: @escaping(Result) -> Void) -> Void { - let requestBuilder = URLRequestBuilder(url: URL(string: identity.baseAddress + TeamManager.urlBase + "\(team.id)/")!) + + public func getTeam(team: TeamRecord, completionHandler: @escaping(Result) -> Void) { + let urlString = identity.baseAddress + TeamManager.urlBase + "\(team.id)/" + let requestBuilder = URLRequestBuilder(url: URL(string: urlString)!) .setIdentity(identity: identity) .setMethod(method: .GET) - + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } } diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index 2b82258..0e3174d 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -10,68 +10,81 @@ import Foundation class TicketManager { static let urlBase = "/tickets/" private var identity: AppIdentity - + init(_ identity: AppIdentity) { self.identity = identity } - + private func makeDetailUrl(_ ticketRecord: TicketRecord) -> URL { - return URL(string: identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + TicketManager.urlBase + "\(ticketRecord.id)/")! + let urlString = identity.baseAddress + TeamManager.urlBase + + "\(identity.team!.id)" + TicketManager.urlBase + "\(ticketRecord.id)/" + return URL(string: urlString)! } - + public func makeListUrl(page: Int, queryParams: TicketFilterParameters) -> URL { - var urlString = identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + TicketManager.urlBase + "?page=\(page)" + var urlString = identity.baseAddress + TeamManager.urlBase + + "\(identity.team!.id)" + TicketManager.urlBase + "?page=\(page)" urlString += queryParams.toParamString() - + return URL(string: urlString)! } - + // TODO: nest a params class that will make querying slightly easier public func makeListUrl(page: Int, assignedMember: MemberRecord?) -> URL { let queryParams = TicketFilterParameters(assignedUser: assignedMember) - + return makeListUrl(page: page, queryParams: queryParams) } - - public func listTeamTickets(page: Int, queryParams: TicketFilterParameters, completionHandler: @escaping (Result, Error>) -> Void) -> Void { + + public func listTeamTickets(page: Int, + queryParams: TicketFilterParameters, + completionHandler: @escaping (Result, Error>) -> Void) { let requestBuilder = URLRequestBuilder(url: makeListUrl(page: page, queryParams: queryParams)) .setMethod(method: .GET) .setIdentity(identity: self.identity) - + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } - - public func makeTicket(ticket: WriteTicketRecord, completionHandler: @escaping(Result) -> Void)->Void{ + + public func makeTicket(ticket: WriteTicketRecord, + completionHandler: @escaping(Result) -> Void) { guard let body = JsonLoader.encode(object: ticket) else { - completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize ticket JSON for makeTicket in TicketManager"))) + let errorMessage = "Failed to serialize ticket JSON for makeTicket in TicketManager" + completionHandler(.failure(Exception.runtimeError(message: errorMessage))) return } - + // TODO: this works but the page here does effectively nothing. I think for clarity introducing a separate // function for making the url might be beneficial let requestBuilder = URLRequestBuilder(url: makeListUrl(page: 1, assignedMember: nil)) .setMethod(method: .POST) .setData(data: body) .setIdentity(identity: self.identity) - + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } - - public func updateTicket(ticket: TicketRecord, completionHandler: @escaping(Result) -> Void)-> Void { - let tags_list: [Int] = ticket.tag_list.map{$0.id} - - let writeTicket = WriteTicketRecord(tag_list: tags_list, assigned_user: ticket.assigned_user?.id, status: ticket.status?.id, title: ticket.title, description: ticket.description, due_date: ticket.due_date) + + public func updateTicket(ticket: TicketRecord, completionHandler: @escaping(Result) -> Void) { + let tagsList: [Int] = ticket.tag_list.map {$0.id} + + let writeTicket = WriteTicketRecord(tag_list: tagsList, + assigned_user: ticket.assigned_user?.id, + status: ticket.status?.id, + title: ticket.title, + description: ticket.description, + due_date: ticket.due_date) guard let body = JsonLoader.encode(object: writeTicket) else { - completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize ticket JSON for updateTicket in TicketManager"))) + let errorMessage = "Failed to serialize ticket JSON for updateTicket in TicketManager" + completionHandler(.failure(Exception.runtimeError(message: errorMessage))) return } - + let requestBuilder = URLRequestBuilder(url: makeDetailUrl(ticket)) .setMethod(method: .PUT) .setData(data: body) .setIdentity(identity: self.identity) JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) - + } } diff --git a/Sluggo/Models/Managers/UserManager.swift b/Sluggo/Models/Managers/UserManager.swift index 62e5885..5278ffd 100644 --- a/Sluggo/Models/Managers/UserManager.swift +++ b/Sluggo/Models/Managers/UserManager.swift @@ -10,38 +10,42 @@ import Foundation class UserManager { static let urlBase = "auth/" private var identity: AppIdentity - + init(identity: AppIdentity) { self.identity = identity } - + public func getUser(completitonHandler: @escaping(Result) -> Void) { let requestBuilder = URLRequestBuilder(url: URL(string: identity.baseAddress + "auth/user/")!) .setMethod(method: .GET) .setIdentity(identity: self.identity) - + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completitonHandler) } - - public func doLogin(username: String, password: String, completionHandler: @escaping(Result) -> Void) -> Void { - let params = ["username":username, "password":password] as Dictionary + + public func doLogin(username: String, + password: String, completionHandler: @escaping(Result) -> Void) { + let params = ["username": username, "password": password] as [String: String] guard let body = try? JSONSerialization.data(withJSONObject: params, options: []) else { - completionHandler(.failure(Exception.runtimeError(message: "Failed to serialize JSON for doLogin in UserManager"))) - return; + completionHandler(.failure(Exception.runtimeError(message: + "Failed to serialize JSON for doLogin in UserManager"))) + return } - + let requestBuilder = URLRequestBuilder(url: URL(string: identity.baseAddress + UserManager.urlBase + "login/")!) .setData(data: body) .setMethod(method: .POST) JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } - - public func doLogout(completionHandler: @escaping(Result) -> Void) -> Void { // TODO: this is probably incorrect - let requestBuilder = URLRequestBuilder(url: URL(string: identity.baseAddress + UserManager.urlBase + "logout/")!) + + // TODO: this is probably incorrect + public func doLogout(completionHandler: @escaping(Result) -> Void) { + let url = URL(string: identity.baseAddress + UserManager.urlBase + "logout/")! + let requestBuilder = URLRequestBuilder(url: url) .setMethod(method: .POST) .setIdentity(identity: self.identity) - + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } } diff --git a/Sluggo/Models/Methods.swift b/Sluggo/Models/Methods.swift index 5689944..5b0f561 100644 --- a/Sluggo/Models/Methods.swift +++ b/Sluggo/Models/Methods.swift @@ -15,7 +15,7 @@ class UnwindState { var onSuccess: (([M.Record]) -> Void)? var onFailure: ((Error) -> Void)? var after: (() -> Void)? - + init(manager: M, page: Int, maxCount: Int, @@ -23,7 +23,7 @@ class UnwindState { onSuccess: (([M.Record]) -> Void)?, onFailure: ((Error) -> Void)?, after: (() -> Void)?) { - + self.manager = manager self.page = page self.maxCount = maxCount @@ -34,44 +34,41 @@ class UnwindState { } } -func unwindPaginationRecurse(state: UnwindState) -> Void { - - state.manager.listFromTeams(page: state.page) { - (result: Result, Error>) -> Void in +func unwindPaginationRecurse(state: UnwindState) { - switch (result) { + state.manager.listFromTeams(page: state.page) { (result: Result, Error>) -> Void in + + switch result { case .success(let record): state.maxCount = record.count - + for entry in record.results { state.codableArray.append(entry) } - - if (state.codableArray.count < state.maxCount) { + + if state.codableArray.count < state.maxCount { state.page += 1 - + // tail recurse to get remaining data unwindPaginationRecurse(state: state) } else { state.onSuccess?(state.codableArray) state.after?() } - break case .failure(let error): print("error occured") state.onFailure?(error) state.after?() - break } - }; + } } func unwindPagination(manager: M, - startingPage: Int, - onSuccess: (([M.Record]) -> Void)?, - onFailure: ((Error) -> Void)?, - after: (() -> Void)?) -> Void { - + startingPage: Int, + onSuccess: (([M.Record]) -> Void)?, + onFailure: ((Error) -> Void)?, + after: (() -> Void)?) { + let state = UnwindState(manager: manager, page: startingPage, maxCount: 0, @@ -79,10 +76,6 @@ func unwindPagination(manager: M, onSuccess: onSuccess, onFailure: onFailure, after: after) - + unwindPaginationRecurse(state: state) } - - - - diff --git a/Sluggo/Models/SidebarData.swift b/Sluggo/Models/SidebarData.swift index d6ee905..479830c 100644 --- a/Sluggo/Models/SidebarData.swift +++ b/Sluggo/Models/SidebarData.swift @@ -7,6 +7,7 @@ import Foundation +// swiftlint:disable identifier_name enum SidebarStatus { case open case closed diff --git a/Sluggo/Models/TicketFilterParameters.swift b/Sluggo/Models/TicketFilterParameters.swift index d41a8c4..d711ae5 100644 --- a/Sluggo/Models/TicketFilterParameters.swift +++ b/Sluggo/Models/TicketFilterParameters.swift @@ -8,32 +8,32 @@ import Foundation struct TicketFilterParameters { - var assignedUser: MemberRecord? = nil - var ticketTag: TagRecord? = nil - var ticketStatus: StatusRecord? = nil - + var assignedUser: MemberRecord? + var ticketTag: TagRecord? + var ticketStatus: StatusRecord? + init(assignedUser: MemberRecord? = nil, ticketTag: TagRecord? = nil, ticketStatus: StatusRecord? = nil) { self.assignedUser = assignedUser self.ticketTag = ticketTag self.ticketStatus = ticketStatus } - + // does not include the initial ? so that it may work with pagination func toParamString() -> String { var paramString = "" - + if let member = assignedUser { paramString += "&assigned_user__owner__username=\(member.owner.username)" } - + if let tag = ticketTag { paramString += "&tag_list__id=\(tag.id)" } - + if let status = ticketStatus { paramString += "&status__id=\(status.id)" } - + return paramString } } diff --git a/Sluggo/Models/URLRequestBuilder.swift b/Sluggo/Models/URLRequestBuilder.swift index e6c298d..a37b707 100644 --- a/Sluggo/Models/URLRequestBuilder.swift +++ b/Sluggo/Models/URLRequestBuilder.swift @@ -8,36 +8,36 @@ import Foundation enum HTTPMethod: String { - case GET = "GET" - case POST = "POST" - case PUT = "PUT" - case PATCH = "PATCH" - case DELETE = "DELETE" + case GET + case POST + case PUT + case PATCH + case DELETE } class URLRequestBuilder { private var request: URLRequest - + init(url: URL) { request = URLRequest(url: url) request.addValue("application/json", forHTTPHeaderField: "Content-Type") } - + func setMethod(method: HTTPMethod) -> URLRequestBuilder { request.httpMethod = method.rawValue return self } - + func setData(data: Data) -> URLRequestBuilder { request.httpBody = data return self } - + func setIdentity(identity: AppIdentity) -> URLRequestBuilder { request.setValue("Bearer \(identity.token!)", forHTTPHeaderField: "Authorization") return self } - + func getRequest() -> URLRequest { return request } diff --git a/Sluggo/View Controllers/Extensions/ViewControllerExtensions.swift b/Sluggo/View Controllers/Extensions/ViewControllerExtensions.swift index bfba86d..56da994 100644 --- a/Sluggo/View Controllers/Extensions/ViewControllerExtensions.swift +++ b/Sluggo/View Controllers/Extensions/ViewControllerExtensions.swift @@ -9,25 +9,25 @@ import UIKit extension UIViewController { func presentError(error: Error, completion: ((UIAlertAction) -> Void)?) { - UIAlertController.createAndPresentError(vc: self, error: error, completion: completion) + UIAlertController.createAndPresentError(view: self, error: error, completion: completion) } - + func presentError(error: Error) { - UIAlertController.createAndPresentError(vc: self, error: error) + UIAlertController.createAndPresentError(view: self, error: error) } - + func presentErrorFromMainThread(error: Error) { DispatchQueue.main.async { self.presentError(error: error) } } - - func processResult(result: Result, onSuccess: @escaping ((T) -> Void), onError: ((Error) -> Void)?, after: (() -> Void)?) { + + func processResult(result: Result, onSuccess: @escaping ((T) -> Void), + onError: ((Error) -> Void)?, after: (() -> Void)?) { DispatchQueue.main.sync { - switch(result) { + switch result { case .success(let successObj): onSuccess(successObj) - break case .failure(let error): if let errorHandler = onError { errorHandler(error) @@ -35,16 +35,16 @@ extension UIViewController { presentError(error: error) } } - + // Continue chains if necessary after?() } } - + func processResult(result: Result, onSuccess: @escaping ((T) -> Void), after: (() -> Void)?) { self.processResult(result: result, onSuccess: onSuccess, onError: nil, after: after) } - + func processResult(result: Result, onSuccess: @escaping ((T) -> Void)) { self.processResult(result: result, onSuccess: onSuccess, onError: nil, after: nil) } diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index 4b88e31..6953c92 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -18,24 +18,24 @@ class HomeTableViewController: UITableViewController { var member: MemberRecord! var assignedTickets: [TicketRecord] = [] var pinnedTickets: [PinnedTicketRecord] = [] - + // Injection for identity init? (coder: NSCoder, identity: AppIdentity) { self.identity = identity super.init(coder: coder) } - + required init?(coder: NSCoder) { super.init(coder: coder) } override func viewDidLoad() { super.viewDidLoad() - + self.refreshControl?.beginRefreshing() // Fetch items and begin populating the table view loadMember(completionHandler: refreshContent) - + // Setup refresh control NotificationCenter.default.addObserver(forName: Constants.Signals.TEAM_CHANGE_NOTIFICATION, object: nil, @@ -43,14 +43,14 @@ class HomeTableViewController: UITableViewController { self.refreshContent() } self.refreshControl?.addTarget(self, action: #selector(refreshContent), for: .valueChanged) - + // Uncomment the following line to preserve selection between presentations // self.clearsSelectionOnViewWillAppear = false // Uncomment the following line to display an Edit button in the navigation bar for this view controller. // self.navigationItem.rightBarButtonItem = self.editButtonItem } - + func loadMember(completionHandler: (() -> Void)?) { let memberManager = MemberManager(identity: identity) memberManager.getMemberRecord(user: identity.authenticatedUser!, identity: identity) { result in @@ -59,7 +59,7 @@ class HomeTableViewController: UITableViewController { }, after: completionHandler) } } - + func loadAssignedTickets(completionHandler: (() -> Void)?) { // Note: This will only grab the first page. // This is an OK assumption, since we assume the total page size is > 3. @@ -67,7 +67,7 @@ class HomeTableViewController: UITableViewController { // but this will suffice for now let ticketsManager = TicketManager(identity) let queryParams = TicketFilterParameters(assignedUser: member) - + ticketsManager.listTeamTickets(page: 1, queryParams: queryParams) { result in self.processResult(result: result, onSuccess: { retrievedAssignedTickets in self.assignedTickets = retrievedAssignedTickets.results @@ -75,57 +75,57 @@ class HomeTableViewController: UITableViewController { }, after: completionHandler) } } - + func loadPinnedTickets(completionHandler: (() -> Void)?) { let pinnedTicketsManager = PinnedTicketManager(identity: self.identity, member: self.member) - pinnedTicketsManager.fetchPinnedTickets() { result in + pinnedTicketsManager.fetchPinnedTickets { result in self.processResult(result: result, onSuccess: { retrievedPinned in self.pinnedTickets = retrievedPinned self.tableView.reloadSections([HomepageCategories.pinned.rawValue], with: .automatic) }, after: completionHandler) } } - + @objc func refreshContent() { // Dispatch to background thread so we don't permanently sleep the main thread // This is done since processResult handles closures back on the main thread DispatchQueue.global(qos: .userInitiated).async { - if(self.member == nil) { + if self.member == nil { // Not safe to make the call // Might make sense to migrate to optionals in data layer // for future iterations of the app DispatchQueue.main.async { self.refreshControl?.endRefreshing() } - return; + return } - + // Setup gating semaphores and variables let sem = DispatchSemaphore(value: 0) var loadedAssigned = false var loadedPinned = false - + // Make calls to reload data, with completions to signal semaphore - self.loadAssignedTickets() { + self.loadAssignedTickets { loadedAssigned = true sem.signal() } - self.loadPinnedTickets() { + self.loadPinnedTickets { loadedPinned = true sem.signal() } - + // Wait for everything to complete and avoid blocking - while(!(loadedPinned && loadedAssigned)) { + while !(loadedPinned && loadedAssigned) { sem.wait() } - + // Stop the refresh control (being careful to dispatch to main thread) DispatchQueue.main.async { self.refreshControl?.endRefreshing() } } - + } // MARK: - Table view data source @@ -137,7 +137,7 @@ class HomeTableViewController: UITableViewController { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // #warning Incomplete implementation, return the number of rows - switch(section) { + switch section { case HomepageCategories.assigned.rawValue: return self.assignedTickets.count case HomepageCategories.pinned.rawValue: @@ -148,15 +148,17 @@ class HomeTableViewController: UITableViewController { fatalError("Invalid section count queried") } } - + // swiftlint:disable force_cast override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - switch(indexPath.section) { + switch indexPath.section { case HomepageCategories.assigned.rawValue: - let cell = tableView.dequeueReusableCell(withIdentifier: "TicketCell", for: indexPath) as! TicketTableViewCell + let cell = tableView.dequeueReusableCell(withIdentifier: "TicketCell", + for: indexPath) as! TicketTableViewCell cell.loadFromTicketRecord(ticket: assignedTickets[indexPath.row]) return cell case HomepageCategories.pinned.rawValue: - let cell = tableView.dequeueReusableCell(withIdentifier: "TicketCell", for: indexPath) as! TicketTableViewCell + let cell = tableView.dequeueReusableCell(withIdentifier: "TicketCell", + for: indexPath) as! TicketTableViewCell cell.loadFromTicketRecord(ticket: pinnedTickets[indexPath.row].ticket) return cell case HomepageCategories.tags.rawValue: @@ -167,7 +169,7 @@ class HomeTableViewController: UITableViewController { fatalError("Accessed section outside of scope, should never occur") } } - + // swiftlint:enable force_cast override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { switch section { case HomepageCategories.assigned.rawValue: @@ -180,7 +182,7 @@ class HomeTableViewController: UITableViewController { return "Error!" } } - + // override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // if let _ = self.tableView(self.tableView, cellForRowAt: indexPath) as? TicketTableViewCell { // // Present error @@ -194,11 +196,15 @@ class HomeTableViewController: UITableViewController { @IBSegueAction func gotoTicketDetail(_ coder: NSCoder) -> TicketDetailTableViewController? { let selectedPath = tableView.indexPathForSelectedRow - switch(selectedPath!.section) { + switch selectedPath!.section { case HomepageCategories.assigned.rawValue: - return TicketDetailTableViewController(coder: coder, identity: self.identity, ticket: assignedTickets[selectedPath!.row]) + return TicketDetailTableViewController(coder: coder, + identity: self.identity, + ticket: assignedTickets[selectedPath!.row]) case HomepageCategories.pinned.rawValue: - return TicketDetailTableViewController(coder: coder, identity: self.identity, ticket: pinnedTickets[selectedPath!.row].ticket) + return TicketDetailTableViewController(coder: coder, + identity: self.identity, + ticket: pinnedTickets[selectedPath!.row].ticket) default: fatalError("Nothing should be selectable from unimplemented categories!") } @@ -213,12 +219,15 @@ class HomeTableViewController: UITableViewController { /* // Override to support editing the table view. - override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { + override func tableView(_ tableView: UITableView, + commit editingStyle: UITableViewCell.EditingStyle, + forRowAt indexPath: IndexPath) { if editingStyle == .delete { // Delete the row from the data source tableView.deleteRows(at: [indexPath], with: .fade) } else if editingStyle == .insert { - // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view + // Create a new instance of the appropriate class + // insert it into the array, and add a new row to the table view } } */ diff --git a/Sluggo/View Controllers/LaunchViewController.swift b/Sluggo/View Controllers/LaunchViewController.swift index 3c9a4ae..6d43dfe 100644 --- a/Sluggo/View Controllers/LaunchViewController.swift +++ b/Sluggo/View Controllers/LaunchViewController.swift @@ -8,35 +8,34 @@ import UIKit class LaunchViewController: UIViewController { - + var identity: AppIdentity - + init? (coder: NSCoder, identity: AppIdentity) { self.identity = identity super.init(coder: coder) } - + required init? (coder: NSCoder) { fatalError("must create class with identity") } - + override func viewDidLoad() { super.viewDidLoad() } - + override func viewDidAppear(_ animated: Bool) { - + let remember = (self.identity.token != nil) let userManager = UserManager(identity: self.identity) - - if(remember) { + + if remember { // Call login function from remembered. If failed go to login - userManager.getUser() { loginResult in + userManager.getUser { loginResult in switch loginResult { case .success( _): - //Need to also check for invalid saved team + // Need to also check for invalid saved team self.tryTeam() - break case .failure(let error): print(error) DispatchQueue.main.sync { @@ -48,10 +47,10 @@ class LaunchViewController: UIViewController { self.showLogin() } } - + // runs on a background thread private func tryTeam() { - if let team = identity.team{ + if let team = identity.team { let teamManager = TeamManager(identity: self.identity) teamManager.getTeam(team: team) { result in switch result { @@ -60,7 +59,6 @@ class LaunchViewController: UIViewController { DispatchQueue.main.sync { self.continueLogin() } - break case .failure(let error): print(error) DispatchQueue.main.sync { @@ -74,41 +72,41 @@ class LaunchViewController: UIViewController { } } } - + func showLogin() { - if let vc = self.storyboard?.instantiateViewController(identifier: "loginPage", creator: {coder in + if let view = self.storyboard?.instantiateViewController(identifier: "loginPage", creator: {coder in return LoginViewController(coder: coder, identity: self.identity, completion: { self.showTeams() }) }) { - vc.isModalInPresentation = true - self.present(vc, animated: true) + view.isModalInPresentation = true + self.present(view, animated: true) } } - + func showTeams() { - if let vc = self.storyboard?.instantiateViewController(identifier: "TableViewContainer", creator: {coder in + if let view = self.storyboard?.instantiateViewController(identifier: "TableViewContainer", creator: {coder in return TeamSelectorContainerViewController(coder: coder, identity: self.identity, completion: { self.continueLogin() }, failure: { - + // reset the data, go back to the login page. self.identity.token = nil self.identity.authenticatedUser = nil self.identity.team = nil - + self.showLogin() }) }) { - vc.isModalInPresentation = true - self.present(vc, animated: true) + view.isModalInPresentation = true + self.present(view, animated: true) } } - + func continueLogin() { self.performSegue(withIdentifier: "automaticLogin", sender: self) } - + @IBSegueAction func createRoot(_ coder: NSCoder) -> UIViewController? { return RootViewController(coder: coder, identity: identity) } diff --git a/Sluggo/View Controllers/LoginViewController.swift b/Sluggo/View Controllers/LoginViewController.swift index d77c99f..5b04499 100644 --- a/Sluggo/View Controllers/LoginViewController.swift +++ b/Sluggo/View Controllers/LoginViewController.swift @@ -13,44 +13,44 @@ class LoginViewController: UIViewController { @IBOutlet weak var password: UITextField! @IBOutlet weak var persistButton: UIButton! @IBOutlet weak var loginButton: UIButton! - + // TODO: AppIdentity be migrated when login flow progressed // This should really be managed by the AppDelegate and passed into VCs along // segues and otherwise. - + // Attempt to load from disk, otherwise, use the new one. var identity: AppIdentity var completion: (() -> Void)? - + init? (coder: NSCoder, identity: AppIdentity, completion: (() -> Void)?) { self.identity = identity self.completion = completion super.init(coder: coder) } - + // This is also necessary when extending the superclass. required init?(coder aDecoder: NSCoder) { fatalError("must be created with identity") } - + override func viewDidLoad() { super.viewDidLoad() self.isModalInPresentation = true - + persistButton.setImage(UIImage(systemName: "checkmark.square.fill"), for: [.highlighted, .selected]) } - + override func viewDidAppear(_ animated: Bool) { super.viewWillAppear(animated) } - + @IBAction func loginButton(_ sender: Any) { let userString = username.text let passString = password.text - + print("button pressed") - - if(userString!.isEmpty || passString!.isEmpty) { + + if userString!.isEmpty || passString!.isEmpty { // login Error print("No username or password provided, not attempting login") return @@ -60,41 +60,36 @@ class LoginViewController: UIViewController { } } } - - + func attemptLogin(username: String, password: String) { let userManager = UserManager(identity: identity) userManager.doLogin(username: username, password: password) { result in - switch(result) { + switch result { case .success(let record): print("Successful login and retrieved token " + record.key) - + // Save to identity self.identity.token = record.key - + // Wait for user record to also be fetched - userManager.getUser() { result in - switch(result) { + userManager.getUser { result in + switch result { case .success(let userRecord): self.identity.authenticatedUser = userRecord - + // Segue out of VC DispatchQueue.main.async { self.dismiss(animated: true, completion: self.completion) } - - break + case .failure(let error): print("FAILURE!") DispatchQueue.main.sync { - UIAlertController.createAndPresentError(vc: self, error: error, completion: nil) + UIAlertController.createAndPresentError(view: self, error: error, completion: nil) } } } - - - - break; + case .failure(let error): DispatchQueue.main.async { let alert = UIAlertController.errorController(error: error) @@ -103,11 +98,10 @@ class LoginViewController: UIViewController { } } } - + @IBAction func persistLoginButton(_ sender: Any) { self.persistButton.isSelected.toggle() self.identity.setPersistData(persist: self.persistButton.isSelected) print(self.persistButton.isSelected) } } - diff --git a/Sluggo/View Controllers/RootViewController.swift b/Sluggo/View Controllers/RootViewController.swift index 1a94ef3..a8f1726 100644 --- a/Sluggo/View Controllers/RootViewController.swift +++ b/Sluggo/View Controllers/RootViewController.swift @@ -13,39 +13,40 @@ class RootViewController: UIViewController { @IBOutlet weak var sidebarContainerView: UIView! // contains the sidebar container view controller @IBOutlet var swipeRight: UISwipeGestureRecognizer! @IBOutlet var swipeLeft: UISwipeGestureRecognizer! - + init? (coder: NSCoder, identity: AppIdentity) { self.identity = identity super.init(coder: coder) } - + required init?(coder: NSCoder) { fatalError("must be called with identity") } - + // swiftlint:disable:next identifier_name @objc func onSidebarNotificationRecieved(_notification: Notification) { guard let status = _notification.userInfo?[Sidebar.USER_INFO_KEY] as? SidebarStatus else { - return; + return } - + updateForSidebarStatus(status: status) } - + func updateForSidebarStatus(status: SidebarStatus) { sidebarContainerView.isUserInteractionEnabled = (status == .open) ? true : false } - + override func viewDidLoad() { super.viewDidLoad() - - NotificationCenter.default.addObserver(self, selector: #selector(onSidebarNotificationRecieved), name: .onSidebarTrigger, object: nil) + + NotificationCenter.default.addObserver(self, + selector: #selector(onSidebarNotificationRecieved), + name: .onSidebarTrigger, object: nil) // Do any additional setup after loading the view. } // MARK: - Navigation - // these are interesting. // while connecting them from view controller segues in the tab bar controller // did not actually call these, wrapping each tab in a navigation controlller @@ -54,16 +55,18 @@ class RootViewController: UIViewController { @IBSegueAction func createHome(_ coder: NSCoder) -> HomeTableViewController? { return HomeTableViewController(coder: coder, identity: identity) } - + @IBSegueAction func createTicket(_ coder: NSCoder) -> TicketListController? { return TicketListController(coder: coder, identity: identity) } - + @IBAction func receivedGesture() { - NotificationCenter.default.post(name: .onSidebarTrigger, object: self, userInfo: [Sidebar.USER_INFO_KEY: SidebarStatus.open]) + NotificationCenter.default.post(name: .onSidebarTrigger, + object: self, userInfo: [Sidebar.USER_INFO_KEY: SidebarStatus.open]) } @IBAction func receieveLeft() { - NotificationCenter.default.post(name: .onSidebarTrigger, object: self, userInfo: [Sidebar.USER_INFO_KEY: SidebarStatus.closed]) + NotificationCenter.default.post(name: .onSidebarTrigger, + object: self, userInfo: [Sidebar.USER_INFO_KEY: SidebarStatus.closed]) } @IBSegueAction func showSidebar(_ coder: NSCoder) -> UIViewController? { return SluggoSidebarContainerViewController(coder: coder, identity: self.identity) diff --git a/Sluggo/View Controllers/SluggoSidebarContainerViewController.swift b/Sluggo/View Controllers/SluggoSidebarContainerViewController.swift index 49e51af..c2ac7bb 100644 --- a/Sluggo/View Controllers/SluggoSidebarContainerViewController.swift +++ b/Sluggo/View Controllers/SluggoSidebarContainerViewController.swift @@ -13,81 +13,80 @@ class SluggoSidebarContainerViewController: UIViewController { @IBOutlet weak var sidebarContainerView: UIView! @IBOutlet weak var sidebarWidthConstraint: NSLayoutConstraint! @IBOutlet weak var sidebarContainerLeadingConstraint: NSLayoutConstraint! - + private var identity: AppIdentity - + init? (coder: NSCoder, identity: AppIdentity) { self.identity = identity super.init(coder: coder) } - + required init? (coder: NSCoder) { fatalError("must include identity") } - + // MARK: Properties var sidebarPresenting: SidebarStatus = .closed - + // MARK: Computed var sidebarLeadingConstant: CGFloat { - get { - let width = sidebarWidthConstraint.constant - return (sidebarPresenting == .open) ? 0 : -1 * width; - } + let width = sidebarWidthConstraint.constant + return (sidebarPresenting == .open) ? 0 : -1 * width } - + var backgroundOpacity: CGFloat { - get { - return (sidebarPresenting == .open) ? 0.4 : 0.0; - } + return (sidebarPresenting == .open) ? 0.4 : 0.0 } - + var foregroundOpacity: CGFloat { - get { - return (sidebarPresenting == .open) ? 1.0 : 0.0; - } + return (sidebarPresenting == .open) ? 1.0 : 0.0 } - + // MARK: Functions + // swiftlint:disable:next identifier_name @objc func triggerSidebar(_notification: Notification) { guard let sidebarState = _notification.userInfo?[Sidebar.USER_INFO_KEY] as? SidebarStatus else { print("Sidebar triggered without explicit state.") return // TODO log this as an error } - + sidebarPresenting = sidebarState - + updateSidebar() } - + @IBAction func backgroundTapGestureRecognized(_ sender: UITapGestureRecognizer) { print("RECOGNIZED") - NotificationCenter.default.post(name: .onSidebarTrigger, object: self, userInfo: [Sidebar.USER_INFO_KEY: SidebarStatus.closed]) - + NotificationCenter.default.post(name: .onSidebarTrigger, + object: self, + userInfo: [Sidebar.USER_INFO_KEY: SidebarStatus.closed]) + } - + func updateSidebar() { sidebarContainerLeadingConstraint.constant = sidebarLeadingConstant - + UIView.animate(withDuration: 0.25) { self.backgroundView.alpha = self.backgroundOpacity self.view.alpha = self.foregroundOpacity self.view.layoutIfNeeded() } } - + // MARK: VC Overrides override func viewDidLoad() { super.viewDidLoad() - + updateSidebar() - + // Register for notification of sidebar changes - NotificationCenter.default.addObserver(self, selector: #selector(triggerSidebar), name: .onSidebarTrigger, object: nil) - + NotificationCenter.default.addObserver(self, + selector: #selector(triggerSidebar), + name: .onSidebarTrigger, + object: nil) + // Do any additional setup after loading the view. } - /* // MARK: - Navigation @@ -99,15 +98,15 @@ class SluggoSidebarContainerViewController: UIViewController { } */ @IBSegueAction func launchSidebar(_ coder: NSCoder) -> UITableViewController? { - let vc = TeamTableViewController(coder: coder) - vc?.identity = self.identity - vc?.completion = { team in + let view = TeamTableViewController(coder: coder) + view?.identity = self.identity + view?.completion = { team in self.identity.team = team NotificationCenter.default.post(name: Constants.Signals.TEAM_CHANGE_NOTIFICATION, object: nil) } - return vc + return view } - + } extension Notification.Name { diff --git a/Sluggo/View Controllers/TeamSelectorContainerViewController.swift b/Sluggo/View Controllers/TeamSelectorContainerViewController.swift index 73f8fff..d625167 100644 --- a/Sluggo/View Controllers/TeamSelectorContainerViewController.swift +++ b/Sluggo/View Controllers/TeamSelectorContainerViewController.swift @@ -12,14 +12,14 @@ class TeamSelectorContainerViewController: UIViewController { private var completion: (() -> Void)? private var failure: (() -> Void)? @IBOutlet var cancelButton: UIButton! - + init? (coder: NSCoder, identity: AppIdentity, completion: (() -> Void)?, failure: (() -> Void)?) { self.identity = identity self.completion = completion self.failure = failure super.init(coder: coder) } - + required init? (coder: NSCoder) { fatalError("must init with identity") } @@ -29,18 +29,16 @@ class TeamSelectorContainerViewController: UIViewController { // Do any additional setup after loading the view. } - - // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepare(for segue: UIStoryboardSegue, sender: Any?) { // Get the new view controller using segue.destination. // Pass the selected object to the new view controller. - if let vc = segue.destination as? TeamTableViewController { - vc.identity = self.identity - vc.completion = { team in + if let view = segue.destination as? TeamTableViewController { + view.identity = self.identity + view.completion = { team in self.identity.team = team self.dismiss(animated: true, completion: self.completion) } diff --git a/Sluggo/View Controllers/TeamTableViewContainer.swift b/Sluggo/View Controllers/TeamTableViewContainer.swift index 15386d2..860457a 100644 --- a/Sluggo/View Controllers/TeamTableViewContainer.swift +++ b/Sluggo/View Controllers/TeamTableViewContainer.swift @@ -10,13 +10,13 @@ import UIKit class TeamSelectorContainerViewController: UIViewController { private var identity: AppIdentity private var completion: (() -> Void)? - + init? (coder: NSCoder, identity: AppIdentity, completion: (() -> Void)?) { self.identity = identity self.completion = completion super.init(coder: coder) } - + required init? (coder: NSCoder) { fatalError("must init with identity") } @@ -26,23 +26,20 @@ class TeamSelectorContainerViewController: UIViewController { // Do any additional setup after loading the view. } - - // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepare(for segue: UIStoryboardSegue, sender: Any?) { // Get the new view controller using segue.destination. // Pass the selected object to the new view controller. - if let vc = segue.destination as? TeamTableViewController { - vc.identity = self.identity - vc.completion = { team in + if let view = segue.destination as? TeamTableViewController { + view.identity = self.identity + view.completion = { team in self.identity.team = team self.dismiss(animated: true, completion: self.completion) } } } - } diff --git a/Sluggo/View Controllers/TeamTableViewController.swift b/Sluggo/View Controllers/TeamTableViewController.swift index 780bb8b..0cdacb9 100644 --- a/Sluggo/View Controllers/TeamTableViewController.swift +++ b/Sluggo/View Controllers/TeamTableViewController.swift @@ -15,56 +15,56 @@ class TeamTableViewController: UITableViewController { private var teams: [TeamRecord] = [] private var isFetching = false private var semaphore = DispatchSemaphore(value: 1) - + override func viewDidLoad() { self.configureRefreshControl() self.handleRefreshAction() } - + func configureRefreshControl() { refreshControl = UIRefreshControl() refreshControl?.addTarget(self, action: #selector(handleRefreshAction), for: .valueChanged) } - + private func preselectRow() { - if (teams.count == 0) { return } - - for i in 0...teams.count-1 { - let team = teams[i] - let indexPath = IndexPath(row: i, section: 0) + if teams.count == 0 { return } + + for iter in 0...teams.count-1 { + let team = teams[iter] + let indexPath = IndexPath(row: iter, section: 0) if team == self.identity.team { tableView.selectRow(at: indexPath, animated: true, scrollPosition: .top) break } } } - + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { self.completion?(self.teams[indexPath.row]) } - + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return teams.count } - + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "SLGSidebarCell", for: indexPath) as UITableViewCell let team = self.teams[indexPath.row] cell.textLabel?.text = team.name cell.accessoryType = (team == self.identity.team) ? .checkmark : .none - + return cell } - + @objc func handleRefreshAction() { DispatchQueue.global(qos: .userInitiated).async { self.semaphore.wait() - + let onSuccess = { (teams: [TeamRecord]) -> Void in self.teams = teams } - + let after = { () -> Void in DispatchQueue.main.async { self.refreshControl?.endRefreshing() @@ -73,7 +73,7 @@ class TeamTableViewController: UITableViewController { } self.semaphore.signal() } - + let teamManager = TeamManager(identity: self.identity) unwindPagination(manager: teamManager, startingPage: 1, diff --git a/Sluggo/View Controllers/TicketDetailTableViewController.swift b/Sluggo/View Controllers/TicketDetailTableViewController.swift index 134df4c..78dc84c 100644 --- a/Sluggo/View Controllers/TicketDetailTableViewController.swift +++ b/Sluggo/View Controllers/TicketDetailTableViewController.swift @@ -15,37 +15,36 @@ class TicketDetailTableViewController: UITableViewController { @IBOutlet var assignedField: UITextField! @IBOutlet var dueDatePicker: UIDatePicker! @IBOutlet var dueDateSwitch: UISwitch! - + // MARK: Variables var identity: AppIdentity var ticket: TicketRecord? var pickerView: UIPickerView = UIPickerView() var editingTicket = false - + var teamMembers: [MemberRecord?] = [nil] - var currentMember: MemberRecord? = nil - + var currentMember: MemberRecord? + init? (coder: NSCoder, identity: AppIdentity, ticket: TicketRecord?) { self.identity = identity self.ticket = ticket super.init(coder: coder) } - + required init? (coder: NSCoder) { fatalError("must be initialized with identity") } - + override func viewDidLoad() { super.viewDidLoad() - + dueDatePicker.isEnabled = dueDateSwitch.isOn - + let memberManager = MemberManager(identity: self.identity) memberManager.listFromTeams(page: 1) { result in - self.processResult(result: result, onSuccess: { - record in + self.processResult(result: result, onSuccess: { record in self.teamMembers = [nil] - for user in record.results{ + for user in record.results { self.teamMembers.append(user) } }) @@ -57,7 +56,7 @@ class TicketDetailTableViewController: UITableViewController { } func updateUI() { - + ticketTitle.text = self.ticket?.title ?? "" ticketDescription.text = ticket?.description ?? "" assignedField.text = ticket?.assigned_user?.owner.username ?? "No Assigned User" @@ -68,7 +67,7 @@ class TicketDetailTableViewController: UITableViewController { setEditMode(false) createUserPicker() } - + func setEditMode(_ editing: Bool) { editingTicket = editing ticketTitle.isUserInteractionEnabled = editing @@ -79,33 +78,30 @@ class TicketDetailTableViewController: UITableViewController { assignedField.isUserInteractionEnabled = editing dueDateSwitch.isHidden = !editing } - + func doSave() { let title = ticketTitle.text ?? "Default Title" let description = ticketDescription.text let date = dueDateSwitch.isOn ? dueDatePicker.date : nil let member = currentMember?.id - + if editingTicket { ticket!.title = title ticket!.description = description ticket!.due_date = date ticket!.assigned_user = currentMember - + let manager = TicketManager(identity) - - manager.updateTicket(ticket: ticket!) { - result in - self.processResult(result: result) { - _ in + + manager.updateTicket(ticket: ticket!) { result in + self.processResult(result: result) { _ in DispatchQueue.main.async { self.setEditMode(false) NotificationCenter.default.post(name: .refreshTrigger, object: self) } } } - } - else { + } else { let ticket = WriteTicketRecord(tag_list: [], assigned_user: member, status: nil, @@ -113,10 +109,8 @@ class TicketDetailTableViewController: UITableViewController { description: description, due_date: date) let manager = TicketManager(identity) - manager.makeTicket(ticket: ticket) { - result in - self.processResult(result: result) { - _ in + manager.makeTicket(ticket: ticket) { result in + self.processResult(result: result) { _ in DispatchQueue.main.async { NotificationCenter.default.post(name: .refreshTrigger, object: self) self.dismiss(animated: true, completion: nil) @@ -125,8 +119,8 @@ class TicketDetailTableViewController: UITableViewController { } } } - - @IBAction func EditButtonHit(_ sender: Any) { + + @IBAction func editButtonHit(_ sender: Any) { if let barButton = sender as? UIBarButtonItem { if barButton.title == "Edit" { setEditMode(true) @@ -137,34 +131,34 @@ class TicketDetailTableViewController: UITableViewController { } } } - + } extension TicketDetailTableViewController: UIPickerViewDelegate, UIPickerViewDataSource { - + func numberOfComponents(in pickerView: UIPickerView) -> Int { return 1 } - + func createUserPicker() { let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 35)) toolbar.sizeToFit() toolbar.translatesAutoresizingMaskIntoConstraints = false - - //create bar button + + // create bar button let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(userPicked)) toolbar.setItems([doneButton], animated: true) assignedField.inputAccessoryView = toolbar - + assignedField.inputView = pickerView } - - @objc func userPicked(){ + + @objc func userPicked() { assignedField.text = currentMember?.owner.username ?? "No Assigned User" self.view.endEditing(true) } - + func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return teamMembers.count } @@ -172,10 +166,9 @@ extension TicketDetailTableViewController: UIPickerViewDelegate, UIPickerViewDat func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return teamMembers[row]?.owner.username ?? "No Assigned User" } - + func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { currentMember = teamMembers[row] } - - + } diff --git a/Sluggo/View Controllers/TicketDetailViewController.swift b/Sluggo/View Controllers/TicketDetailViewController.swift index c42bafb..7d9edce 100644 --- a/Sluggo/View Controllers/TicketDetailViewController.swift +++ b/Sluggo/View Controllers/TicketDetailViewController.swift @@ -7,11 +7,8 @@ import UIKit - - class TicketDetailViewController: UIViewController, UITextViewDelegate, UIPickerViewDelegate, UIPickerViewDataSource { - - + @IBOutlet weak var ticketTitle: UITextField! @IBOutlet weak var ticketDescription: UITextView! @IBOutlet weak var navigationItemDisplay: UINavigationItem! @@ -21,50 +18,49 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate, UIPicker @IBOutlet weak var ticketTitleTopConstraint: NSLayoutConstraint! @IBOutlet weak var dueDateSwitch: UISwitch! @IBOutlet weak var dueDateLabel: UILabel! - + var identity: AppIdentity var ticket: TicketRecord? var pickerView: UIPickerView = UIPickerView() var editingTicket = false - + var teamMembers: [MemberRecord?] = [nil] - var currentMember: MemberRecord? = nil - + var currentMember: MemberRecord? + init? (coder: NSCoder, identity: AppIdentity, ticket: TicketRecord?) { self.identity = identity self.ticket = ticket super.init(coder: coder) } - + required init? (coder: NSCoder) { fatalError("must be initialized with identity") } - + + // swiftlint:disable:next function_body_length override func viewDidLoad() { super.viewDidLoad() dateTimePicker.isEnabled = dueDateSwitch.isOn - let memberManager = MemberManager(identity: self.identity) - memberManager.listFromTeams(page: 1){ result in - self.processResult(result: result, onSuccess: { - record in + memberManager.listFromTeams(page: 1) { result in + self.processResult(result: result, onSuccess: { record in self.teamMembers = [nil] - for user in record.results{ + for user in record.results { self.teamMembers.append(user) } }) } - + pickerView.dataSource = self pickerView.delegate = self createUserPicker() - + ticketDescription.delegate = self if let passedTicket = ticket { - + ticketTitle.text = passedTicket.title - + if let description = passedTicket.description { if description.isEmpty { ticketDescription.text = "Description of ticket" // Placeholder text @@ -76,20 +72,19 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate, UIPicker ticketDescription.text = "Description of ticket" // Placeholder text ticketDescription.textColor = .lightGray } - + if let assignedName = passedTicket.assigned_user?.owner.username { assignedUserTextField.text = assignedName } - + if let date = passedTicket.due_date { dateTimePicker.date = date dateTimePicker.isEnabled = true - } - else{ + } else { dateTimePicker.isHidden = true dueDateLabel.isHidden = true } - + navBar.isHidden = true ticketTitleTopConstraint.constant = 0 ticketTitle.isUserInteractionEnabled = false @@ -97,76 +92,85 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate, UIPicker dateTimePicker.isUserInteractionEnabled = false assignedUserTextField.isUserInteractionEnabled = false dueDateSwitch.isHidden = true - navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(setToEditMode)) - } - else{ + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, + target: self, + action: #selector(setToEditMode)) + } else { ticketDescription.text = "Description of ticket" // Placeholder text ticketDescription.textColor = .lightGray - navigationItemDisplay.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelMode)) - navigationItemDisplay.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(submitTicketMode)) + navigationItemDisplay.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, + target: self, + action: #selector(cancelMode)) + navigationItemDisplay.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, + target: self, + action: #selector(submitTicketMode)) } } - + @IBAction func dueDateSwitch(_ sender: UISwitch) { dateTimePicker.isEnabled = sender.isOn } - + @objc func cancelMode() { self.dismiss(animated: true, completion: nil) } - - @objc func submitTicketMode(){ + + @objc func submitTicketMode() { let title = ticketTitle.text ?? "Default title (This is an error)" var description: String? = "" if ticketDescription.textColor != .lightGray { description = ticketDescription.text } - + let date = dueDateSwitch.isOn ? dateTimePicker.date : nil let member = currentMember?.id - - if(editingTicket){ // Edit Ticket - + + if editingTicket { // Edit Ticket + ticket!.title = title ticket!.description = description ticket!.due_date = date ticket!.assigned_user = currentMember - + let manager = TicketManager(identity) - manager.updateTicket(ticket: ticket!){ result in - switch(result){ - case .success(_): - DispatchQueue.main.async { - self.noEditingMode() - NotificationCenter.default.post(name: .refreshTrigger, object: self) - - } - case .failure(let error): - self.presentErrorFromMainThread(error: error) + manager.updateTicket(ticket: ticket!) { result in + switch result { + case .success(_): + DispatchQueue.main.async { + self.noEditingMode() + NotificationCenter.default.post(name: .refreshTrigger, object: self) + + } + case .failure(let error): + self.presentErrorFromMainThread(error: error) } } - } - else{ // Create Ticket - let ticket = WriteTicketRecord(tag_list: [], assigned_user: member, status: nil, title: title, description: description, due_date: date) + } else { // Create Ticket + let ticket = WriteTicketRecord(tag_list: [], + assigned_user: member, + status: nil, + title: title, + description: description, + due_date: date) let manager = TicketManager(identity) - manager.makeTicket(ticket: ticket){ result in - switch(result){ - case .success(_): - DispatchQueue.main.async { - self.dismiss(animated: true, completion: nil) - NotificationCenter.default.post(name: .refreshTrigger, object: self) - } - case .failure(let error): - self.presentErrorFromMainThread(error: error) + manager.makeTicket(ticket: ticket) { result in + switch result { + case .success(_): + DispatchQueue.main.async { + self.dismiss(animated: true, completion: nil) + NotificationCenter.default.post(name: .refreshTrigger, object: self) + } + case .failure(let error): + self.presentErrorFromMainThread(error: error) } } } } - + // MARK: Placeholder text for description // Whoever decided to code UITextView deserves to be beaten func textViewDidBeginEditing (_ textView: UITextView) { - if ticketDescription.textColor == .lightGray{ + if ticketDescription.textColor == .lightGray { ticketDescription.text = nil ticketDescription.textColor = .black } @@ -178,12 +182,14 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate, UIPicker ticketDescription.text = "Description of Ticket" } } - + // We understand that these two are bad, but for now, passing parameters to selectors is not very fun. - @objc func setToEditMode(){ + @objc func setToEditMode() { editingTicket = true navigationItem.rightBarButtonItem = nil - navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(submitTicketMode)) + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, + target: self, + action: #selector(submitTicketMode)) ticketTitle.isUserInteractionEnabled = true ticketDescription.isUserInteractionEnabled = true dateTimePicker.isUserInteractionEnabled = true @@ -193,11 +199,13 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate, UIPicker dateTimePicker.isHidden = false dueDateLabel.isHidden = false } - - func noEditingMode(){ + + func noEditingMode() { editingTicket = false navigationItem.rightBarButtonItem = nil - navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(setToEditMode)) + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, + target: self, + action: #selector(setToEditMode)) ticketTitle.isUserInteractionEnabled = false ticketDescription.isUserInteractionEnabled = false dateTimePicker.isUserInteractionEnabled = false @@ -206,35 +214,32 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate, UIPicker dateTimePicker.isHidden = !dateTimePicker.isEnabled dueDateLabel.isHidden = !dateTimePicker.isEnabled } - + func createUserPicker() { - //create toolbar + // create toolbar let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 35)) toolbar.sizeToFit() toolbar.translatesAutoresizingMaskIntoConstraints = false - - //create bar button + + // create bar button let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(userPicked)) toolbar.setItems([doneButton], animated: true) assignedUserTextField.inputAccessoryView = toolbar - + assignedUserTextField.inputView = pickerView } - - @objc func userPicked(){ + + @objc func userPicked() { assignedUserTextField.text = currentMember?.owner.username ?? "No Assigned User" self.view.endEditing(true) } - - + @IBSegueAction func segueToDetail(_ coder: NSCoder) -> TicketDetailTableViewController? { return TicketDetailTableViewController(coder: coder, identity: identity, ticket: ticket) } - - - - //MARK: UIPickerView manager + + // MARK: UIPickerView manager func numberOfComponents(in pickerView: UIPickerView) -> Int { return 1 } @@ -246,9 +251,9 @@ class TicketDetailViewController: UIViewController, UITextViewDelegate, UIPicker func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return teamMembers[row]?.owner.username ?? "No Assigned User" } - + func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { currentMember = teamMembers[row] } - + } diff --git a/Sluggo/View Controllers/TicketFilterTableViewController.swift b/Sluggo/View Controllers/TicketFilterTableViewController.swift index 1f96215..6b79a3e 100644 --- a/Sluggo/View Controllers/TicketFilterTableViewController.swift +++ b/Sluggo/View Controllers/TicketFilterTableViewController.swift @@ -14,17 +14,17 @@ enum FilterViewCategories: Int { } class TicketFilterTableViewController: UITableViewController { - + var identity: AppIdentity! var completion: ((TicketFilterParameters) -> Void)? var filterParams: TicketFilterParameters! - + private var teamMembers: [MemberRecord] = [] private var ticketTags: [TagRecord] = [] private var ticketStatuses: [StatusRecord] = [] private let semaphore = DispatchSemaphore(value: 1) private static let numSections = 3 - + private var sectionSelectedMap = [ FilterViewCategories.assignedUsers.rawValue: nil, FilterViewCategories.ticketTags.rawValue: nil, @@ -35,13 +35,13 @@ class TicketFilterTableViewController: UITableViewController { super.viewDidLoad() configureRefreshControl() configureBarItems() - handleRefreshAction() { + handleRefreshAction { self.preselectItems() } } - + func configureBarItems() { - let doneAction = UIAction() { action in + let doneAction = UIAction { _ in self.dismiss(animated: true) { self.completion?(self.filterParams) } @@ -54,7 +54,7 @@ class TicketFilterTableViewController: UITableViewController { } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - switch(section){ + switch section { case FilterViewCategories.assignedUsers.rawValue: return teamMembers.count case FilterViewCategories.ticketTags.rawValue: @@ -65,29 +65,26 @@ class TicketFilterTableViewController: UITableViewController { fatalError("no such section") } } - + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "FilterEntry", for: indexPath) var text = "" - - switch(indexPath.section) { + + switch indexPath.section { case FilterViewCategories.assignedUsers.rawValue: text = teamMembers[indexPath.row].getTitle() - break case FilterViewCategories.ticketTags.rawValue: text = ticketTags[indexPath.row].getTitle() - break case FilterViewCategories.ticketStatuses.rawValue: text = ticketStatuses[indexPath.row].getTitle() - break default: fatalError("no such section") } - + cell.textLabel?.text = text return cell } - + // MARK: duplicaty nonsense func preselectItems() { if let row = self.teamMembers.firstIndex(where: { record in @@ -97,7 +94,7 @@ class TicketFilterTableViewController: UITableViewController { tableView.selectRow(at: indexPath, animated: true, scrollPosition: .top) sectionSelectedMap[FilterViewCategories.assignedUsers.rawValue] = indexPath.row } - + if let row = self.ticketStatuses.firstIndex(where: { record in record.object_uuid == self.filterParams.ticketStatus?.object_uuid }) { @@ -106,7 +103,7 @@ class TicketFilterTableViewController: UITableViewController { sectionSelectedMap[FilterViewCategories.ticketStatuses.rawValue] = indexPath.row } - + if let row = self.ticketTags.firstIndex(where: { record in record.object_uuid == self.filterParams.ticketTag?.object_uuid }) { @@ -116,9 +113,9 @@ class TicketFilterTableViewController: UITableViewController { } } - + override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { - switch(indexPath.section){ + switch indexPath.section { case FilterViewCategories.assignedUsers.rawValue: self.filterParams.assignedUser = nil case FilterViewCategories.ticketTags.rawValue: @@ -129,9 +126,9 @@ class TicketFilterTableViewController: UITableViewController { fatalError("no such section") } } - + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - switch(indexPath.section){ + switch indexPath.section { case FilterViewCategories.assignedUsers.rawValue: self.filterParams.assignedUser = self.teamMembers[indexPath.row] case FilterViewCategories.ticketTags.rawValue: @@ -145,10 +142,10 @@ class TicketFilterTableViewController: UITableViewController { if let previousInSection = self.sectionSelectedMap[indexPath.section]! { tableView.deselectRow(at: IndexPath(row: previousInSection, section: indexPath.section), animated: true) } - + self.sectionSelectedMap[indexPath.section] = indexPath.row } - + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { switch section { case FilterViewCategories.assignedUsers.rawValue: @@ -161,39 +158,39 @@ class TicketFilterTableViewController: UITableViewController { return "Error!" } } - + // MARK: refresh stuff func configureRefreshControl() { refreshControl = UIRefreshControl() refreshControl?.addTarget(self, action: #selector(handleRefreshAction), for: .valueChanged) } - + @objc func handleRefreshAction(after: (() -> Void)? = nil) { DispatchQueue.global(qos: .userInitiated).async { self.semaphore.wait() - + // this mutex synchronizes the different api calls // and access to the completed count let mutex = DispatchSemaphore(value: 1) var completed = 0 - + let after = { () -> Void in mutex.wait() - + completed += 1 - if (completed == TicketFilterTableViewController.numSections) { + if completed == TicketFilterTableViewController.numSections { DispatchQueue.main.async { self.refreshControl?.endRefreshing() self.tableView.reloadData() // we reload all the mf data self.preselectItems() } - + self.semaphore.signal() } mutex.signal() } - + let tagManager = TagManager(identity: self.identity) unwindPagination(manager: tagManager, startingPage: 1, @@ -202,7 +199,7 @@ class TicketFilterTableViewController: UITableViewController { }, onFailure: self.presentErrorFromMainThread, after: after) - + let statusManger = StatusManager(identity: self.identity) unwindPagination(manager: statusManger, startingPage: 1, @@ -211,7 +208,7 @@ class TicketFilterTableViewController: UITableViewController { }, onFailure: self.presentErrorFromMainThread, after: after) - + let memberManager = MemberManager(identity: self.identity) unwindPagination(manager: memberManager, startingPage: 1, @@ -223,5 +220,4 @@ class TicketFilterTableViewController: UITableViewController { } } - } diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index f29c881..f1b78b8 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -14,16 +14,16 @@ class TicketListController: UITableViewController { var tickets: [TicketRecord] = [] var isFetching: Bool = false var filterParams: TicketFilterParameters = TicketFilterParameters() - + init? (coder: NSCoder, identity: AppIdentity) { self.identity = identity super.init(coder: coder) } - + required init? (coder: NSCoder) { fatalError("must be initialized with identity") } - + // MARK: initial actions override func viewDidLoad() { configureRefreshControl() @@ -36,9 +36,11 @@ class TicketListController: UITableViewController { self.filterParams = TicketFilterParameters() self.handleRefreshAction() } - NotificationCenter.default.addObserver(self, selector: #selector(handleRefreshAction), name: .refreshTrigger, object: nil) + NotificationCenter.default.addObserver(self, + selector: #selector(handleRefreshAction), + name: .refreshTrigger, object: nil) } - + func setupMenuBar() { let saveMenu = UIMenu(title: "", children: [ @@ -49,49 +51,50 @@ class TicketListController: UITableViewController { self.launchFilterPopup() } ]) - - navigationItem.rightBarButtonItem = UIBarButtonItem( image: UIImage(systemName: "ellipsis"), primaryAction: nil, menu: saveMenu) + + navigationItem.rightBarButtonItem = UIBarButtonItem( image: UIImage(systemName: "ellipsis"), + primaryAction: nil, menu: saveMenu) } - + func configureRefreshControl() { refreshControl = UIRefreshControl() refreshControl?.addTarget(self, action: #selector(handleRefreshAction), for: .valueChanged) } - + @objc func handleRefreshAction() { self.loadData(page: 1) } - // MARK: Table view stuff - + // @stephan this is probably where you'll spawn the detail views once you get going on that. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let identity = self.identity let detailStoryboard = UIStoryboard(name: "TicketDetail", bundle: nil) - if let vc = detailStoryboard.instantiateViewController(identifier: "TicketDetail", creator:{ coder in - return TicketDetailTableViewController(coder: coder, identity: identity, ticket: self.tickets[indexPath.row]) + if let view = detailStoryboard.instantiateViewController(identifier: "TicketDetail", creator: { coder in + return TicketDetailTableViewController(coder: coder, + identity: identity, ticket: self.tickets[indexPath.row]) }) as TicketDetailTableViewController? { - navigationController?.pushViewController(vc, animated: true) + navigationController?.pushViewController(view, animated: true) } } - + // MARK: menu item delegates func connectPopUp() { let identity = self.identity let detailStoryboard = UIStoryboard(name: "TicketDetail", bundle: nil) - if let vc = detailStoryboard.instantiateViewController(identifier: "TicketDetail", creator:{ coder in + if let view = detailStoryboard.instantiateViewController(identifier: "TicketDetail", creator: { coder in return TicketDetailTableViewController(coder: coder, identity: identity, ticket: nil) }) as TicketDetailTableViewController? { - self.present(vc, animated: true, completion: nil) + self.present(view, animated: true, completion: nil) } } - + func launchFilterPopup() { // launch the view controller holding the filter view - if let vc = storyboard?.instantiateViewController(identifier: "FilterNavigationController") { - if let child = vc.children[0] as? TicketFilterTableViewController { + if let view = storyboard?.instantiateViewController(identifier: "FilterNavigationController") { + if let child = view.children[0] as? TicketFilterTableViewController { child.identity = self.identity child.filterParams = self.filterParams child.completion = { queryParams in @@ -99,59 +102,58 @@ class TicketListController: UITableViewController { self.handleRefreshAction() } } - - self.present(vc, animated: true, completion: nil) + + self.present(view, animated: true, completion: nil) } } - - - + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.tickets.count } - + + // swiftlint:disable force_cast override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - + let cell = tableView.dequeueReusableCell(withIdentifier: "Ticket", for: indexPath) as! TicketTableViewCell - + cell.loadFromTicketRecord(ticket: tickets[indexPath.row]) - - if (indexPath.row == tickets.count - 1 && tickets.count < maxNumber && !isFetching) { + + if indexPath.row == tickets.count - 1 && tickets.count < maxNumber && !isFetching { DispatchQueue.main.async { self.loadData(page: ((indexPath.row + 1) / self.identity.pageSize) + 1) } } - + return cell } - + // swiftlint:enable force_cast + // MARK: API calls private func loadData(page: Int) { let ticketManager = TicketManager(identity) isFetching = true ticketManager.listTeamTickets(page: page, queryParams: self.filterParams) { result in - switch(result) { + switch result { case .success(let record): self.maxNumber = record.count - + var ticketsCopy = Array(self.tickets) // remove all after starting from the beginning of the first element in this page let pageOffset = (page - 1) * self.identity.pageSize - if (pageOffset < self.tickets.count) { + if pageOffset < self.tickets.count { ticketsCopy.removeSubrange(pageOffset...self.tickets.count-1) } - + for entry in record.results { ticketsCopy.append(entry) } - + DispatchQueue.main.sync { self.refreshControl?.endRefreshing() self.tickets = ticketsCopy self.tableView.reloadData() self.isFetching = false } - break; case .failure(let error): DispatchQueue.main.async { let alert = UIAlertController.errorController(error: error) diff --git a/SluggoTests/MemberTests.swift b/SluggoTests/MemberTests.swift index 62f2cec..2dcba2a 100644 --- a/SluggoTests/MemberTests.swift +++ b/SluggoTests/MemberTests.swift @@ -27,7 +27,7 @@ class MemberTests: XCTestCase { "deactivated": null } """ - + let testWorkingJsonWithExtraProps = """ { "id": "c4ca4238a0b923820dcc509a6f75849bfce3644a63f700b351c5afeb783c1cc2", @@ -46,52 +46,88 @@ class MemberTests: XCTestCase { "deactivated": "2021-04-13T23:17:22+0000" } """ - + let testPaginated = """ - {"count":1,"next":null,"previous":null,"results":[{"id":"c74d97b01eae257e44aa9d5bade97baf332532dcfaa1cbf61e2a266bd723612c","owner":{"id":1,"email":"sam@sam.sam","first_name":"","last_name":"","username":"sam"},"team_id":16,"object_uuid":"eb486ea8-660d-4d3b-850b-80c42bb4301f","role":"AD","bio":"asdf","pronouns":null,"created":"2021-05-06T20:58:47+0000","activated":null,"deactivated":null}]} + { + "count":1, + "next":null, + "previous":null, + "results":[ + { + "id":"c74d97b01eae257e44aa9d5bade97baf332532dcfaa1cbf61e2a266bd723612c", + "owner": { + "id":1, + "email":"sam@sam.sam", + "first_name":"", + "last_name":"", + "username":"sam" + }, + "team_id":16, + "object_uuid":"eb486ea8-660d-4d3b-850b-80c42bb4301f", + "role":"AD", + "bio":"asdf", + "pronouns":null, + "created":"2021-05-06T20:58:47+0000", + "activated":null, + "deactivated":null + } + ] + } """ - + func testSerializesMultiple() { let members: PaginatedList? = JsonLoader.decode(data: testPaginated.data(using: .utf8)!) XCTAssertNotNil(members) } - + func testMemberDoesDeserialize() { let member: MemberRecord? = JsonLoader.decode(data: testWorkingJson.data(using: .utf8)!) XCTAssertNotNil(member) - + XCTAssertEqual(member!.team_id, 1) XCTAssertEqual(member!.role, "UA") XCTAssertEqual(member!.id, "c4ca4238a0b923820dcc509a6f75849bfce3644a63f700b351c5afeb783c1cc2") - + XCTAssertNil(member!.bio) XCTAssertNil(member!.activated) XCTAssertNil(member!.deactivated) } - + func testUserDeserializesWithExtraProps() { let member: MemberRecord? = JsonLoader.decode(data: testWorkingJsonWithExtraProps.data(using: .utf8)!) XCTAssertNotNil(member) - + XCTAssertEqual(member!.team_id, 1) XCTAssertEqual(member!.role, "UA") XCTAssertEqual(member!.id, "c4ca4238a0b923820dcc509a6f75849bfce3644a63f700b351c5afeb783c1cc2") - + XCTAssertEqual(member!.bio, "I work on things very very well") } - + func testUserDoesSerialize() { - let user = UserRecord(id: 1, email: "wtpisaac@icloud.com", first_name: "Isaac", last_name: "Trimble-Pederson", username: "wtpisaac") - - let member = MemberRecord(id: "this is an md5 eventually", owner: user, team_id: 1, object_uuid: UUID(), role: "UA", bio: nil, created: Date(), activated: nil, deactivated: nil) - + let user = UserRecord(id: 1, + email: "wtpisaac@icloud.com", + first_name: "Isaac", + last_name: "Trimble-Pederson", + username: "wtpisaac") + + let member = MemberRecord(id: "this is an md5 eventually", + owner: user, + team_id: 1, + object_uuid: UUID(), + role: "UA", + bio: nil, + created: Date(), + activated: nil, + deactivated: nil) + let json = JsonLoader.encode(object: member) XCTAssertNotNil(json) print(json!) - + let memberDuplicate: MemberRecord? = JsonLoader.decode(data: json!) XCTAssertNotNil(memberDuplicate) - + XCTAssertEqual(member.object_uuid, memberDuplicate!.object_uuid) XCTAssertEqual(member.team_id, memberDuplicate!.team_id) XCTAssertEqual(member.role, memberDuplicate!.role) diff --git a/SluggoTests/SluggoTests.swift b/SluggoTests/SluggoTests.swift index 2ec0e3e..22397eb 100644 --- a/SluggoTests/SluggoTests.swift +++ b/SluggoTests/SluggoTests.swift @@ -5,10 +5,10 @@ // Created by Isaac Trimble-Pederson on 4/8/21. // // -//import XCTest -//@testable import Sluggo +// import XCTest +// @testable import Sluggo // -//class SluggoTests: XCTestCase { +// class SluggoTests: XCTestCase { // // override func setUpWithError() throws { // // Put setup code here. This method is called before the invocation of each test method in the class. @@ -30,4 +30,4 @@ // } // } // -//} +// } diff --git a/SluggoTests/TagTests.swift b/SluggoTests/TagTests.swift index 278acd0..94c95f1 100644 --- a/SluggoTests/TagTests.swift +++ b/SluggoTests/TagTests.swift @@ -22,20 +22,19 @@ class TagTests: XCTestCase { } """ - func testExample() { + func testExample() { let tag: TagRecord? = JsonLoader.decode(data: workingSingleJson.data(using: .utf8)!) - + XCTAssertNotNil(tag) let tagUnwrapped = tag! - + XCTAssertEqual(tagUnwrapped.id, 1) XCTAssertEqual(tagUnwrapped.team_id, 15) XCTAssertEqual(tagUnwrapped.object_uuid, UUID(uuidString: "a40cb545-3ada-4656-a02d-2fdaba81fc21")) XCTAssertEqual(tagUnwrapped.title, "word around office, the") } - - + let workingMultipleJson = """ { "count": 3, @@ -72,19 +71,19 @@ class TagTests: XCTestCase { ] } """ - + func testMultiple() { let tags: PaginatedList? = JsonLoader.decode(data: workingMultipleJson.data(using: .utf8)!) XCTAssertNotNil(tags) let tagsUnwrapped = tags! - + XCTAssertEqual(tagsUnwrapped.count, 3) XCTAssertEqual(tagsUnwrapped.next, nil) XCTAssertEqual(tagsUnwrapped.previous, nil) - + // the tags individually are probably correct if everything else goes well } - + let brokenMultipleJson = """ { "count": 3, @@ -113,12 +112,12 @@ class TagTests: XCTestCase { ] } """ - + func testNoneNil() { let tags: PaginatedList? = JsonLoader.decode(data: brokenMultipleJson.data(using: .utf8)!) - + // this should fail because the data in the array must never be null XCTAssertNil(tags) } - + } diff --git a/SluggoTests/TicketManagerTests.swift b/SluggoTests/TicketManagerTests.swift index 7742c1e..7768580 100644 --- a/SluggoTests/TicketManagerTests.swift +++ b/SluggoTests/TicketManagerTests.swift @@ -14,7 +14,13 @@ class TicketManagerTests: XCTestCase { var exampleMember: MemberRecord! override func setUp() { - let team = TeamRecord(id: 1, name: "bugslotics", object_uuid: UUID(), ticket_head: 1, created: Date(timeIntervalSince1970: 0), activated: nil, deactivated: nil) + let team = TeamRecord(id: 1, + name: "bugslotics", + object_uuid: UUID(), + ticket_head: 1, + created: Date(timeIntervalSince1970: 0), + activated: nil, + deactivated: nil) identity = AppIdentity() identity.team = team @@ -39,18 +45,20 @@ class TicketManagerTests: XCTestCase { func testListURLGeneration() throws { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. - + let ticketManager = TicketManager(identity) let obtainedURL = ticketManager.makeListUrl(page: 1, assignedMember: nil) - + XCTAssertEqual(obtainedURL, URL(string: "http://127.0.0.1:8000/api/teams/1/tickets/?page=1")!) } - + func testListURLGenerationWithAssignedFilter() throws { let ticketManager = TicketManager(identity) let obtainedURL = ticketManager.makeListUrl(page: 1, assignedMember: exampleMember) - - XCTAssertEqual(obtainedURL, URL(string: "http://127.0.0.1:8000/api/teams/1/tickets/?page=1&assigned_user__owner__username=sammytheslug")) + let url = URL(string: + "http://127.0.0.1:8000/api/teams/1/tickets/?page=1&assigned_user__owner__username=sammytheslug") + + XCTAssertEqual(obtainedURL, url) } } diff --git a/SluggoTests/TicketTests.swift b/SluggoTests/TicketTests.swift index 0ef0fc5..4e9d83f 100644 --- a/SluggoTests/TicketTests.swift +++ b/SluggoTests/TicketTests.swift @@ -21,7 +21,7 @@ class TicketTests: XCTestCase { "activated": "2021-04-28T20:31:43+0000", } """ - + let testWorkingJsonWithExtraProps = """ { "id": 2, @@ -54,7 +54,7 @@ class TicketTests: XCTestCase { "deactivated": null } """ - + func testTicketDoesDeserialize() { // CRASHES, PLEASE FIX! let ticket: TicketRecord? = JsonLoader.decode(data: testWorkingJson.data(using: .utf8)!) @@ -65,16 +65,13 @@ class TicketTests: XCTestCase { XCTAssertEqual(ticket!.title, "Hi") XCTAssertEqual(ticket!.id, 2) - XCTAssertNil(ticket!.status) XCTAssertNil(ticket!.assigned_user) - } - + func testTicketDeserializesWithExtraProps() { - - + let memberJson = """ { "id": "c4ca4238a0b923820dcc509a6f75849bfce3644a63f700b351c5afeb783c1cc2", @@ -95,31 +92,42 @@ class TicketTests: XCTestCase { } """ let ticket: TicketRecord? = JsonLoader.decode(data: testWorkingJsonWithExtraProps.data(using: .utf8)!) - + let member: MemberRecord? = JsonLoader.decode(data: memberJson.data(using: .utf8)!) - + XCTAssertNotNil(ticket) - + XCTAssertEqual(ticket!.ticket_number, 2) XCTAssertEqual(ticket!.title, "Hi") XCTAssertEqual(ticket!.id, 2) - + XCTAssertEqual(ticket!.assigned_user!.id, member!.id) XCTAssertEqual(ticket!.object_uuid, UUID(uuidString: "cefdf703-40ff-40d0-b07a-1583cc33d7d4")) - + } - + func testTicketDoesSerialize() { - - let ticket = TicketRecord(id: 1, ticket_number: 1, tag_list: [], object_uuid: UUID(uuidString: "aa80fe7c-d346-4944-9d9e-3d7286fc4ca7")!, assigned_user: nil, status: nil, title: "Howdy Partner", description: "This is a ticket", due_date: Date(), created: Date(), activated: Date(), deactivated: Date()) - + + let ticket = TicketRecord(id: 1, + ticket_number: 1, + tag_list: [], + object_uuid: UUID(uuidString: "aa80fe7c-d346-4944-9d9e-3d7286fc4ca7")!, + assigned_user: nil, + status: nil, + title: "Howdy Partner", + description: "This is a ticket", + due_date: Date(), + created: Date(), + activated: Date(), + deactivated: Date()) + let json = JsonLoader.encode(object: ticket) XCTAssertNotNil(json) print(json!) - + let ticketDuplicate: TicketRecord? = JsonLoader.decode(data: json!) XCTAssertNotNil(ticketDuplicate) - + XCTAssertEqual(ticket.id, ticketDuplicate!.id) XCTAssertEqual(ticket.ticket_number, ticketDuplicate!.ticket_number) XCTAssertEqual(ticket.title, ticketDuplicate!.title) diff --git a/SluggoTests/UserTests.swift b/SluggoTests/UserTests.swift index c888316..c112eaf 100644 --- a/SluggoTests/UserTests.swift +++ b/SluggoTests/UserTests.swift @@ -16,7 +16,7 @@ class UserTests: XCTestCase { "id": 1 } """ - + let testWorkingJsonWithExtraProps = """ { "username": "wtpisaac", @@ -26,41 +26,45 @@ class UserTests: XCTestCase { "last_name": "Trimble-Pederson" } """ - + func testUserDoesDeserialize() { let user: UserRecord? = JsonLoader.decode(data: testWorkingJson.data(using: .utf8)!) XCTAssertNotNil(user) - + XCTAssertEqual(user!.username, "wtpisaac") XCTAssertEqual(user!.email, "wtpisaac@icloud.com") XCTAssertEqual(user!.id, 1) - + XCTAssertNil(user!.first_name) XCTAssertNil(user!.last_name) } - + func testUserDeserializesWithExtraProps() { let user: UserRecord? = JsonLoader.decode(data: testWorkingJsonWithExtraProps.data(using: .utf8)!) XCTAssertNotNil(user) - + XCTAssertEqual(user!.username, "wtpisaac") XCTAssertEqual(user!.email, "wtpisaac@icloud.com") XCTAssertEqual(user!.id, 1) - + XCTAssertEqual(user!.first_name, "Isaac") XCTAssertEqual(user!.last_name, "Trimble-Pederson") } - + func testUserDoesSerialize() { - let user = UserRecord(id: 1, email: "wtpisaac@icloud.com", first_name: "Isaac", last_name: "Trimble-Pederson", username: "wtpisaac") - + let user = UserRecord(id: 1, + email: "wtpisaac@icloud.com", + first_name: "Isaac", + last_name: "Trimble-Pederson", + username: "wtpisaac") + let json = JsonLoader.encode(object: user) XCTAssertNotNil(json) print(json!) - + let userDuplicate: UserRecord? = JsonLoader.decode(data: json!) XCTAssertNotNil(userDuplicate) - + XCTAssertEqual(user.username, userDuplicate!.username) XCTAssertEqual(user.email, userDuplicate!.email) XCTAssertEqual(user.first_name, userDuplicate!.first_name) diff --git a/SluggoUITests/SluggoUITests.swift b/SluggoUITests/SluggoUITests.swift index 9deaf24..bf2a2f8 100644 --- a/SluggoUITests/SluggoUITests.swift +++ b/SluggoUITests/SluggoUITests.swift @@ -5,9 +5,9 @@ // Created by Isaac Trimble-Pederson on 4/8/21. // // -//import XCTest +// import XCTest // -//class SluggoUITests: XCTestCase { +// class SluggoUITests: XCTestCase { // // override func setUpWithError() throws { // // Put setup code here. This method is called before the invocation of each test method in the class. @@ -15,7 +15,8 @@ // // In UI tests it is usually best to stop immediately when a failure occurs. // continueAfterFailure = false // -// // 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. +// // 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. // } // // override func tearDownWithError() throws { @@ -39,4 +40,4 @@ // } // } // } -//} +// } From c0510a26cee3314bdd9e02739412b22ae7953071 Mon Sep 17 00:00:00 2001 From: Troy Ebert Date: Thu, 13 May 2021 17:23:36 -0700 Subject: [PATCH 154/224] Added basic member tab and associated list --- Sluggo.xcodeproj/project.pbxproj | 14 ++- Sluggo/Storyboards/Base.lproj/Root.storyboard | 19 +++- Sluggo/Storyboards/TeamMembers.storyboard | 67 +++++++++++++ .../MemberListViewController.swift | 97 +++++++++++++++++++ .../View Controllers/RootViewController.swift | 4 + 5 files changed, 194 insertions(+), 7 deletions(-) create mode 100644 Sluggo/Storyboards/TeamMembers.storyboard create mode 100644 Sluggo/View Controllers/MemberListViewController.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 8400af2..a92c7cd 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -9,6 +9,8 @@ /* Begin PBXBuildFile section */ 051AAB0B2630FD6500BCB9C7 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 051AAB0A2630FD6500BCB9C7 /* Constants.swift */; }; 052258702636100B00B49CE4 /* LaunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0522586F2636100B00B49CE4 /* LaunchViewController.swift */; }; + 05588A12264DB61E000D8674 /* TeamMembers.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 05588A11264DB61E000D8674 /* TeamMembers.storyboard */; }; + 05588A1A264DBDAB000D8674 /* MemberListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05588A19264DBDAB000D8674 /* MemberListViewController.swift */; }; 2F2247AC262920AC006C6EE6 /* TokenRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2247AB262920AC006C6EE6 /* TokenRecord.swift */; }; 2F3AD7A32627C86A00B61A97 /* UserRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A22627C86A00B61A97 /* UserRecord.swift */; }; 2F3AD7A82627C89B00B61A97 /* MemberRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A72627C89B00B61A97 /* MemberRecord.swift */; }; @@ -81,6 +83,8 @@ /* Begin PBXFileReference section */ 051AAB0A2630FD6500BCB9C7 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 0522586F2636100B00B49CE4 /* LaunchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchViewController.swift; sourceTree = ""; }; + 05588A11264DB61E000D8674 /* TeamMembers.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TeamMembers.storyboard; sourceTree = ""; }; + 05588A19264DBDAB000D8674 /* MemberListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberListViewController.swift; sourceTree = ""; }; 2F2247AB262920AC006C6EE6 /* TokenRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenRecord.swift; sourceTree = ""; }; 2F3AD7A22627C86A00B61A97 /* UserRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRecord.swift; sourceTree = ""; }; 2F3AD7A72627C89B00B61A97 /* MemberRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberRecord.swift; sourceTree = ""; }; @@ -302,6 +306,7 @@ 5A9FF88826386D1400378550 /* Ticket.storyboard */, 7DEA17842639E7B5004E5A71 /* LaunchScreen.storyboard */, 3A150A76263CB5F70052934B /* TicketDetail.storyboard */, + 05588A11264DB61E000D8674 /* TeamMembers.storyboard */, ); path = Storyboards; sourceTree = ""; @@ -312,13 +317,14 @@ 7D778C04264A374200D4061A /* Extensions */, 0522586F2636100B00B49CE4 /* LaunchViewController.swift */, 5A79FFE5262E563F00D04320 /* LoginViewController.swift */, + 5A9C4C202641113D00B270AB /* TeamSelectorContainerViewController.swift */, + 5A9C4C1B2640BC4E00B270AB /* TeamTableViewController.swift */, 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */, 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */, - 5A9FF88D26386D4700378550 /* TicketListController.swift */, 7D23834A264119050091C7E8 /* HomeTableViewController.swift */, + 5A9FF88D26386D4700378550 /* TicketListController.swift */, 3A150A7E263CBA860052934B /* TicketDetailViewController.swift */, - 5A9C4C1B2640BC4E00B270AB /* TeamTableViewController.swift */, - 5A9C4C202641113D00B270AB /* TeamSelectorContainerViewController.swift */, + 05588A19264DBDAB000D8674 /* MemberListViewController.swift */, ); path = "View Controllers"; sourceTree = ""; @@ -435,6 +441,7 @@ 7D9963B2261EF3A5002338E7 /* Assets.xcassets in Resources */, 5A9FF88926386D1400378550 /* Ticket.storyboard in Resources */, 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */, + 05588A12264DB61E000D8674 /* TeamMembers.storyboard in Resources */, 7D9963AD261EF3A4002338E7 /* Root.storyboard in Resources */, 7DEA17852639E7B5004E5A71 /* LaunchScreen.storyboard in Resources */, 3A150A77263CB5F70052934B /* TicketDetail.storyboard in Resources */, @@ -473,6 +480,7 @@ 2FD70D5D2636144300D13C75 /* LogoutMessage.swift in Sources */, 5AB1C8EA262FE2EF0010F85E /* MemberManager.swift in Sources */, 5A9C4C1C2640BC4E00B270AB /* TeamTableViewController.swift in Sources */, + 05588A1A264DBDAB000D8674 /* MemberListViewController.swift in Sources */, 2F3AD7AD2627C8EF00B61A97 /* TeamRecord.swift in Sources */, 7DC54AE526437AB3000C8300 /* PinnedTicketRecord.swift in Sources */, 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */, diff --git a/Sluggo/Storyboards/Base.lproj/Root.storyboard b/Sluggo/Storyboards/Base.lproj/Root.storyboard index 6fae314..f0bf767 100644 --- a/Sluggo/Storyboards/Base.lproj/Root.storyboard +++ b/Sluggo/Storyboards/Base.lproj/Root.storyboard @@ -74,7 +74,7 @@ - + @@ -88,11 +88,12 @@ + - + @@ -103,7 +104,7 @@ - + @@ -113,7 +114,17 @@ - + + + + + + + + + + + diff --git a/Sluggo/Storyboards/TeamMembers.storyboard b/Sluggo/Storyboards/TeamMembers.storyboard new file mode 100644 index 0000000..f71e4fc --- /dev/null +++ b/Sluggo/Storyboards/TeamMembers.storyboard @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sluggo/View Controllers/MemberListViewController.swift b/Sluggo/View Controllers/MemberListViewController.swift new file mode 100644 index 0000000..a855bf2 --- /dev/null +++ b/Sluggo/View Controllers/MemberListViewController.swift @@ -0,0 +1,97 @@ +// +// MemberList.swift +// Sluggo +// +// Created by Troy Ebert on 5/13/21. +// +import UIKit + +class MemberListViewController: UITableViewController { + var identity: AppIdentity! + private var maxNumber: Int = 0 + private var fetchingMembers: [MemberRecord] = [] + private var members: [MemberRecord] = [] + private var isFetching = false + + required init? (coder: NSCoder, identity: AppIdentity) { + self.identity = identity + super.init(coder: coder) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + + override func viewDidLoad() { + self.configureRefreshControl() + self.handleRefreshAction() + } + + func configureRefreshControl() { + refreshControl = UIRefreshControl() + refreshControl?.addTarget(self, action: #selector(handleRefreshAction), for: .valueChanged) + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return members.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "MemberCell", for: indexPath) as UITableViewCell + let member = self.members[indexPath.row] + cell.textLabel?.text = member.owner.username + + //cell.accessoryType = (member.owner.id == self.identity.authenticatedUser!.pk) ? .checkmark : .none + + return cell + } + + + @objc func handleRefreshAction() { + // enter the critical section + // do not wait + if (isFetching) { return } + + isFetching = true + self.loadData(page: 1) + } + + private func loadData(page: Int) { + let memberManager = MemberManager(identity: identity) + memberManager.listTeamMembers() { result in + switch(result) { + case .success(let record): + self.maxNumber = record.count + + let pageOffset = (page - 1) * self.identity.pageSize + if (pageOffset < self.fetchingMembers.count) { + self.fetchingMembers.removeSubrange(pageOffset...self.fetchingMembers.count-1) + } + + for entry in record.results { + self.fetchingMembers.append(entry) + } + + if (self.fetchingMembers.count >= self.maxNumber) { + DispatchQueue.main.async { + self.refreshControl?.endRefreshing() + self.members = Array(self.fetchingMembers) + self.fetchingMembers.removeAll() + self.tableView.reloadData() + self.isFetching = false + } + return + } else { + self.loadData(page: page + 1) + } + + break; + case .failure(let error): + DispatchQueue.main.async { + let alert = UIAlertController.errorController(error: error) + self.present(alert, animated: true, completion: nil) + } + } + } + } +} diff --git a/Sluggo/View Controllers/RootViewController.swift b/Sluggo/View Controllers/RootViewController.swift index 1a94ef3..7e9bf11 100644 --- a/Sluggo/View Controllers/RootViewController.swift +++ b/Sluggo/View Controllers/RootViewController.swift @@ -59,6 +59,10 @@ class RootViewController: UIViewController { return TicketListController(coder: coder, identity: identity) } + @IBSegueAction func createMembers(_ coder: NSCoder) -> MemberListViewController? { + return MemberListViewController(coder: coder, identity: identity) + } + @IBAction func receivedGesture() { NotificationCenter.default.post(name: .onSidebarTrigger, object: self, userInfo: [Sidebar.USER_INFO_KEY: SidebarStatus.open]) } From e5be8d5bf7ed5d0273626c51f5cf0e822a6a75e5 Mon Sep 17 00:00:00 2001 From: Troy Ebert Date: Fri, 14 May 2021 11:59:00 -0700 Subject: [PATCH 155/224] Added processResult into loadData --- .../MemberListViewController.swift | 39 ++++++++----------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/Sluggo/View Controllers/MemberListViewController.swift b/Sluggo/View Controllers/MemberListViewController.swift index a855bf2..4580b72 100644 --- a/Sluggo/View Controllers/MemberListViewController.swift +++ b/Sluggo/View Controllers/MemberListViewController.swift @@ -9,7 +9,6 @@ import UIKit class MemberListViewController: UITableViewController { var identity: AppIdentity! private var maxNumber: Int = 0 - private var fetchingMembers: [MemberRecord] = [] private var members: [MemberRecord] = [] private var isFetching = false @@ -58,40 +57,34 @@ class MemberListViewController: UITableViewController { private func loadData(page: Int) { let memberManager = MemberManager(identity: identity) + memberManager.listTeamMembers() { result in - switch(result) { - case .success(let record): + self.processResult(result: result, onSuccess: { record in self.maxNumber = record.count - + + var membersCopy = Array(self.members) let pageOffset = (page - 1) * self.identity.pageSize - if (pageOffset < self.fetchingMembers.count) { - self.fetchingMembers.removeSubrange(pageOffset...self.fetchingMembers.count-1) + + if (pageOffset < self.members.count) { + membersCopy.removeSubrange(pageOffset...self.members.count-1) } for entry in record.results { - self.fetchingMembers.append(entry) + membersCopy.append(entry) } - - if (self.fetchingMembers.count >= self.maxNumber) { - DispatchQueue.main.async { - self.refreshControl?.endRefreshing() - self.members = Array(self.fetchingMembers) - self.fetchingMembers.removeAll() - self.tableView.reloadData() - self.isFetching = false - } - return - } else { - self.loadData(page: page + 1) + + DispatchQueue.main.async { + self.refreshControl?.endRefreshing() + self.members = membersCopy + self.tableView.reloadData() + self.isFetching = false } - - break; - case .failure(let error): + }, onError: { error in DispatchQueue.main.async { let alert = UIAlertController.errorController(error: error) self.present(alert, animated: true, completion: nil) } - } + }, after: nil) } } } From b8ccfb13097097bdf8a4393c190e1637a3f0d902 Mon Sep 17 00:00:00 2001 From: Troy Ebert Date: Fri, 14 May 2021 13:27:28 -0700 Subject: [PATCH 156/224] Implemented Sam's unwinder in the member list --- Sluggo.xcodeproj/project.pbxproj | 13 ++- .../MemberListViewController.swift | 84 +++++++++++-------- 2 files changed, 57 insertions(+), 40 deletions(-) diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 89e64f3..ef56ced 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -10,7 +10,7 @@ 051AAB0B2630FD6500BCB9C7 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 051AAB0A2630FD6500BCB9C7 /* Constants.swift */; }; 052258702636100B00B49CE4 /* LaunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0522586F2636100B00B49CE4 /* LaunchViewController.swift */; }; 05588A12264DB61E000D8674 /* TeamMembers.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 05588A11264DB61E000D8674 /* TeamMembers.storyboard */; }; - 05588A1A264DBDAB000D8674 /* MemberListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05588A19264DBDAB000D8674 /* MemberListViewController.swift */; }; + 05DE0CD7264F11E400D69C61 /* MemberListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05DE0CD6264F11E400D69C61 /* MemberListViewController.swift */; }; 2F2247AC262920AC006C6EE6 /* TokenRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2247AB262920AC006C6EE6 /* TokenRecord.swift */; }; 2F3AD7A32627C86A00B61A97 /* UserRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A22627C86A00B61A97 /* UserRecord.swift */; }; 2F3AD7A82627C89B00B61A97 /* MemberRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3AD7A72627C89B00B61A97 /* MemberRecord.swift */; }; @@ -92,7 +92,7 @@ 051AAB0A2630FD6500BCB9C7 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 0522586F2636100B00B49CE4 /* LaunchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchViewController.swift; sourceTree = ""; }; 05588A11264DB61E000D8674 /* TeamMembers.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TeamMembers.storyboard; sourceTree = ""; }; - 05588A19264DBDAB000D8674 /* MemberListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberListViewController.swift; sourceTree = ""; }; + 05DE0CD6264F11E400D69C61 /* MemberListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemberListViewController.swift; sourceTree = ""; }; 2F2247AB262920AC006C6EE6 /* TokenRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenRecord.swift; sourceTree = ""; }; 2F3AD7A22627C86A00B61A97 /* UserRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRecord.swift; sourceTree = ""; }; 2F3AD7A72627C89B00B61A97 /* MemberRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberRecord.swift; sourceTree = ""; }; @@ -348,16 +348,15 @@ 7D778C04264A374200D4061A /* Extensions */, 0522586F2636100B00B49CE4 /* LaunchViewController.swift */, 5A79FFE5262E563F00D04320 /* LoginViewController.swift */, + 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */, 5A9C4C202641113D00B270AB /* TeamSelectorContainerViewController.swift */, 5A9C4C1B2640BC4E00B270AB /* TeamTableViewController.swift */, - 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */, 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */, + 7D23834A264119050091C7E8 /* HomeTableViewController.swift */, 5A9FF88D26386D4700378550 /* TicketListController.swift */, 3A150A7E263CBA860052934B /* TicketDetailViewController.swift */, - 5A9C4C1B2640BC4E00B270AB /* TeamTableViewController.swift */, - 5A9C4C202641113D00B270AB /* TeamSelectorContainerViewController.swift */, - 7D23834A264119050091C7E8 /* HomeTableViewController.swift */, 5A25CFAB264B966300852035 /* TicketFilterTableViewController.swift */, + 05DE0CD6264F11E400D69C61 /* MemberListViewController.swift */, ); path = "View Controllers"; sourceTree = ""; @@ -516,7 +515,6 @@ 2FD70D5D2636144300D13C75 /* LogoutMessage.swift in Sources */, 5AB1C8EA262FE2EF0010F85E /* MemberManager.swift in Sources */, 5A9C4C1C2640BC4E00B270AB /* TeamTableViewController.swift in Sources */, - 05588A1A264DBDAB000D8674 /* MemberListViewController.swift in Sources */, 2F3AD7AD2627C8EF00B61A97 /* TeamRecord.swift in Sources */, 7DC54AE526437AB3000C8300 /* PinnedTicketRecord.swift in Sources */, 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */, @@ -532,6 +530,7 @@ 5A25CFF1264BE28100852035 /* HasTitle.swift in Sources */, 7D53BE6926433D6600400D0E /* PinnedTicketManager.swift in Sources */, 5ACC0F062630CA2700DCF83E /* Exceptions.swift in Sources */, + 05DE0CD7264F11E400D69C61 /* MemberListViewController.swift in Sources */, 5A25CFA7264B934400852035 /* StatusManager.swift in Sources */, 5AB1C8F7262FE3520010F85E /* AppIdentity.swift in Sources */, 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */, diff --git a/Sluggo/View Controllers/MemberListViewController.swift b/Sluggo/View Controllers/MemberListViewController.swift index 4580b72..96c85c7 100644 --- a/Sluggo/View Controllers/MemberListViewController.swift +++ b/Sluggo/View Controllers/MemberListViewController.swift @@ -1,5 +1,5 @@ // -// MemberList.swift +// MemberListViewController.swift // Sluggo // // Created by Troy Ebert on 5/13/21. @@ -11,6 +11,7 @@ class MemberListViewController: UITableViewController { private var maxNumber: Int = 0 private var members: [MemberRecord] = [] private var isFetching = false + private var semaphore = DispatchSemaphore(value: 1) required init? (coder: NSCoder, identity: AppIdentity) { self.identity = identity @@ -47,44 +48,61 @@ class MemberListViewController: UITableViewController { @objc func handleRefreshAction() { - // enter the critical section - // do not wait - if (isFetching) { return } - - isFetching = true - self.loadData(page: 1) - } - - private func loadData(page: Int) { - let memberManager = MemberManager(identity: identity) - - memberManager.listTeamMembers() { result in - self.processResult(result: result, onSuccess: { record in - self.maxNumber = record.count + + DispatchQueue.global(qos: .userInitiated).async { + self.semaphore.wait() - var membersCopy = Array(self.members) - let pageOffset = (page - 1) * self.identity.pageSize - - if (pageOffset < self.members.count) { - membersCopy.removeSubrange(pageOffset...self.members.count-1) - } - - for entry in record.results { - membersCopy.append(entry) - } + let onSuccess = { (members: [MemberRecord]) -> Void in + self.members = members + } + let after = { () -> Void in DispatchQueue.main.async { self.refreshControl?.endRefreshing() - self.members = membersCopy self.tableView.reloadData() - self.isFetching = false - } - }, onError: { error in - DispatchQueue.main.async { - let alert = UIAlertController.errorController(error: error) - self.present(alert, animated: true, completion: nil) } - }, after: nil) + self.semaphore.signal() + } + + let memberManager = MemberManager(identity: self.identity) + unwindPagination(manager: memberManager, + startingPage: 1, + onSuccess: onSuccess, + onFailure: self.presentErrorFromMainThread, + after: after) } } + +// private func loadData(page: Int) { +// let memberManager = MemberManager(identity: identity) +// +// memberManager.listFromTeams() { result in +// self.processResult(result: result, onSuccess: { record in +// self.maxNumber = record.count +// +// var membersCopy = Array(self.members) +// let pageOffset = (page - 1) * self.identity.pageSize +// +// if (pageOffset < self.members.count) { +// membersCopy.removeSubrange(pageOffset...self.members.count-1) +// } +// +// for entry in record.results { +// membersCopy.append(entry) +// } +// +// DispatchQueue.main.async { +// self.refreshControl?.endRefreshing() +// self.members = membersCopy +// self.tableView.reloadData() +// self.isFetching = false +// } +// }, onError: { error in +// DispatchQueue.main.async { +// let alert = UIAlertController.errorController(error: error) +// self.present(alert, animated: true, completion: nil) +// } +// }, after: nil) +// } +// } } From 95a63f43d64429dd397786fd9aa77b8eedd66671 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Fri, 14 May 2021 14:42:54 -0700 Subject: [PATCH 157/224] Ticket creation works, bug with IBSegueAction in homepage --- Sluggo.xcodeproj/project.pbxproj | 4 - Sluggo/Storyboards/TicketDetail.storyboard | 184 +++---------- .../HomeTableViewController.swift | 14 +- .../TicketDetailTableViewController.swift | 34 ++- .../TicketDetailViewController.swift | 259 ------------------ .../TicketListController.swift | 16 +- 6 files changed, 73 insertions(+), 438 deletions(-) delete mode 100644 Sluggo/View Controllers/TicketDetailViewController.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index e014fc6..b7c22ad 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -22,7 +22,6 @@ 3A150A6726365A9A0052934B /* StatusRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A6626365A9A0052934B /* StatusRecord.swift */; }; 3A150A6C26365B670052934B /* TagRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A6B26365B670052934B /* TagRecord.swift */; }; 3A150A77263CB5F70052934B /* TicketDetail.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3A150A76263CB5F70052934B /* TicketDetail.storyboard */; }; - 3A150A7F263CBA860052934B /* TicketDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A7E263CBA860052934B /* TicketDetailViewController.swift */; }; 5A00A0142639EDEA0085C8C6 /* URLRequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */; }; 5A00A045263A34CC0085C8C6 /* TicketTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A00A044263A34CC0085C8C6 /* TicketTableViewCell.swift */; }; 5A25CFA2264B8C8100852035 /* TagTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFA1264B8C8100852035 /* TagTests.swift */; }; @@ -103,7 +102,6 @@ 3A150A6626365A9A0052934B /* StatusRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusRecord.swift; sourceTree = ""; }; 3A150A6B26365B670052934B /* TagRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagRecord.swift; sourceTree = ""; }; 3A150A76263CB5F70052934B /* TicketDetail.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TicketDetail.storyboard; sourceTree = ""; }; - 3A150A7E263CBA860052934B /* TicketDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketDetailViewController.swift; sourceTree = ""; }; 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLRequestBuilder.swift; sourceTree = ""; }; 5A00A044263A34CC0085C8C6 /* TicketTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketTableViewCell.swift; sourceTree = ""; }; 5A25CFA1264B8C8100852035 /* TagTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagTests.swift; sourceTree = ""; }; @@ -348,7 +346,6 @@ 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */, 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */, 5A9FF88D26386D4700378550 /* TicketListController.swift */, - 3A150A7E263CBA860052934B /* TicketDetailViewController.swift */, 5A9C4C1B2640BC4E00B270AB /* TeamTableViewController.swift */, 5A9C4C202641113D00B270AB /* TeamSelectorContainerViewController.swift */, 7D23834A264119050091C7E8 /* HomeTableViewController.swift */, @@ -525,7 +522,6 @@ 5AB1C8FC262FF1690010F85E /* TicketRecord.swift in Sources */, 5A25CFDF264BCF2C00852035 /* TicketFilterParameters.swift in Sources */, 5A25CFE7264BD73D00852035 /* TicketFilterTableViewCell.swift in Sources */, - 3A150A7F263CBA860052934B /* TicketDetailViewController.swift in Sources */, 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */, 5A00A0142639EDEA0085C8C6 /* URLRequestBuilder.swift in Sources */, 5A25CFC2264B9AFF00852035 /* TeamPaginatedListable.swift in Sources */, diff --git a/Sluggo/Storyboards/TicketDetail.storyboard b/Sluggo/Storyboards/TicketDetail.storyboard index 0f8b3dd..0337519 100644 --- a/Sluggo/Storyboards/TicketDetail.storyboard +++ b/Sluggo/Storyboards/TicketDetail.storyboard @@ -1,155 +1,13 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -216,20 +74,29 @@ - + + + + + @@ -270,24 +137,45 @@ - - + + - + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index 6953c92..ca10520 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -198,13 +198,15 @@ class HomeTableViewController: UITableViewController { let selectedPath = tableView.indexPathForSelectedRow switch selectedPath!.section { case HomepageCategories.assigned.rawValue: - return TicketDetailTableViewController(coder: coder, - identity: self.identity, - ticket: assignedTickets[selectedPath!.row]) + let view = TicketDetailTableViewController(coder: coder) + view!.identity = self.identity + view!.ticket = assignedTickets[selectedPath!.row] + return view case HomepageCategories.pinned.rawValue: - return TicketDetailTableViewController(coder: coder, - identity: self.identity, - ticket: pinnedTickets[selectedPath!.row].ticket) + let view = TicketDetailTableViewController(coder: coder) + view!.identity = self.identity + view!.ticket = pinnedTickets[selectedPath!.row].ticket + return view default: fatalError("Nothing should be selectable from unimplemented categories!") } diff --git a/Sluggo/View Controllers/TicketDetailTableViewController.swift b/Sluggo/View Controllers/TicketDetailTableViewController.swift index 78dc84c..d532bc4 100644 --- a/Sluggo/View Controllers/TicketDetailTableViewController.swift +++ b/Sluggo/View Controllers/TicketDetailTableViewController.swift @@ -15,9 +15,12 @@ class TicketDetailTableViewController: UITableViewController { @IBOutlet var assignedField: UITextField! @IBOutlet var dueDatePicker: UIDatePicker! @IBOutlet var dueDateSwitch: UISwitch! - + @IBOutlet var dueDateLabel: UILabel! + @IBOutlet var navBar: UINavigationItem! + @IBOutlet var rightButton: UIBarButtonItem! + // MARK: Variables - var identity: AppIdentity + var identity: AppIdentity! var ticket: TicketRecord? var pickerView: UIPickerView = UIPickerView() var editingTicket = false @@ -25,16 +28,6 @@ class TicketDetailTableViewController: UITableViewController { var teamMembers: [MemberRecord?] = [nil] var currentMember: MemberRecord? - init? (coder: NSCoder, identity: AppIdentity, ticket: TicketRecord?) { - self.identity = identity - self.ticket = ticket - super.init(coder: coder) - } - - required init? (coder: NSCoder) { - fatalError("must be initialized with identity") - } - override func viewDidLoad() { super.viewDidLoad() @@ -60,12 +53,18 @@ class TicketDetailTableViewController: UITableViewController { ticketTitle.text = self.ticket?.title ?? "" ticketDescription.text = ticket?.description ?? "" assignedField.text = ticket?.assigned_user?.owner.username ?? "No Assigned User" + currentMember = ticket?.assigned_user ?? nil dueDatePicker.date = ticket?.due_date ?? Date() + dueDatePicker.isHidden = (ticket?.due_date == nil) + dueDateLabel.isHidden = (ticket?.due_date != nil) dueDateSwitch.isEnabled = (ticket?.due_date != nil) dueDatePicker.isEnabled = dueDateSwitch.isEnabled assignedField.isEnabled = true - setEditMode(false) + navBar.title = self.ticket != nil ? "Selected Ticket" : "Create a Ticket" + setEditMode(self.ticket == nil) createUserPicker() + + rightButton.title = (self.ticket != nil) ? "Edit" : "Done" } func setEditMode(_ editing: Bool) { @@ -73,10 +72,14 @@ class TicketDetailTableViewController: UITableViewController { ticketTitle.isUserInteractionEnabled = editing ticketDescription.isUserInteractionEnabled = editing dueDatePicker.isEnabled = true + dueDateSwitch.isEnabled = true + dueDateSwitch.isOn = (ticket?.due_date != nil) dueDatePicker.isUserInteractionEnabled = editing dueDateSwitch.isUserInteractionEnabled = editing assignedField.isUserInteractionEnabled = editing dueDateSwitch.isHidden = !editing + dueDatePicker.isHidden = (ticket?.due_date == nil && !editing) + dueDateLabel.isHidden = (ticket?.due_date != nil || editing) } func doSave() { @@ -125,9 +128,12 @@ class TicketDetailTableViewController: UITableViewController { if barButton.title == "Edit" { setEditMode(true) barButton.title = "Save" - } else { + } else if barButton.title == "Save" { doSave() barButton.title = "Edit" + } else { + editingTicket = false + doSave() } } } diff --git a/Sluggo/View Controllers/TicketDetailViewController.swift b/Sluggo/View Controllers/TicketDetailViewController.swift deleted file mode 100644 index 7d9edce..0000000 --- a/Sluggo/View Controllers/TicketDetailViewController.swift +++ /dev/null @@ -1,259 +0,0 @@ -// -// TicketDetailViewController.swift -// Sluggo -// -// Created by Stephan Martin on 4/30/21. -// - -import UIKit - -class TicketDetailViewController: UIViewController, UITextViewDelegate, UIPickerViewDelegate, UIPickerViewDataSource { - - @IBOutlet weak var ticketTitle: UITextField! - @IBOutlet weak var ticketDescription: UITextView! - @IBOutlet weak var navigationItemDisplay: UINavigationItem! - @IBOutlet weak var assignedUserTextField: UITextField! - @IBOutlet weak var dateTimePicker: UIDatePicker! - @IBOutlet weak var navBar: UINavigationBar! - @IBOutlet weak var ticketTitleTopConstraint: NSLayoutConstraint! - @IBOutlet weak var dueDateSwitch: UISwitch! - @IBOutlet weak var dueDateLabel: UILabel! - - var identity: AppIdentity - var ticket: TicketRecord? - var pickerView: UIPickerView = UIPickerView() - var editingTicket = false - - var teamMembers: [MemberRecord?] = [nil] - var currentMember: MemberRecord? - - init? (coder: NSCoder, identity: AppIdentity, ticket: TicketRecord?) { - self.identity = identity - self.ticket = ticket - super.init(coder: coder) - } - - required init? (coder: NSCoder) { - fatalError("must be initialized with identity") - } - - // swiftlint:disable:next function_body_length - override func viewDidLoad() { - super.viewDidLoad() - dateTimePicker.isEnabled = dueDateSwitch.isOn - - let memberManager = MemberManager(identity: self.identity) - memberManager.listFromTeams(page: 1) { result in - self.processResult(result: result, onSuccess: { record in - self.teamMembers = [nil] - for user in record.results { - self.teamMembers.append(user) - } - }) - } - - pickerView.dataSource = self - pickerView.delegate = self - createUserPicker() - - ticketDescription.delegate = self - if let passedTicket = ticket { - - ticketTitle.text = passedTicket.title - - if let description = passedTicket.description { - if description.isEmpty { - ticketDescription.text = "Description of ticket" // Placeholder text - ticketDescription.textColor = .lightGray - } else { - ticketDescription.text = description - } - } else { - ticketDescription.text = "Description of ticket" // Placeholder text - ticketDescription.textColor = .lightGray - } - - if let assignedName = passedTicket.assigned_user?.owner.username { - assignedUserTextField.text = assignedName - } - - if let date = passedTicket.due_date { - dateTimePicker.date = date - dateTimePicker.isEnabled = true - } else { - dateTimePicker.isHidden = true - dueDateLabel.isHidden = true - } - - navBar.isHidden = true - ticketTitleTopConstraint.constant = 0 - ticketTitle.isUserInteractionEnabled = false - ticketDescription.isUserInteractionEnabled = false - dateTimePicker.isUserInteractionEnabled = false - assignedUserTextField.isUserInteractionEnabled = false - dueDateSwitch.isHidden = true - navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, - target: self, - action: #selector(setToEditMode)) - } else { - ticketDescription.text = "Description of ticket" // Placeholder text - ticketDescription.textColor = .lightGray - navigationItemDisplay.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, - target: self, - action: #selector(cancelMode)) - navigationItemDisplay.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, - target: self, - action: #selector(submitTicketMode)) - } - } - - @IBAction func dueDateSwitch(_ sender: UISwitch) { - dateTimePicker.isEnabled = sender.isOn - } - - @objc func cancelMode() { - self.dismiss(animated: true, completion: nil) - } - - @objc func submitTicketMode() { - let title = ticketTitle.text ?? "Default title (This is an error)" - var description: String? = "" - if ticketDescription.textColor != .lightGray { - description = ticketDescription.text - } - - let date = dueDateSwitch.isOn ? dateTimePicker.date : nil - let member = currentMember?.id - - if editingTicket { // Edit Ticket - - ticket!.title = title - ticket!.description = description - ticket!.due_date = date - ticket!.assigned_user = currentMember - - let manager = TicketManager(identity) - manager.updateTicket(ticket: ticket!) { result in - switch result { - case .success(_): - DispatchQueue.main.async { - self.noEditingMode() - NotificationCenter.default.post(name: .refreshTrigger, object: self) - - } - case .failure(let error): - self.presentErrorFromMainThread(error: error) - } - } - } else { // Create Ticket - let ticket = WriteTicketRecord(tag_list: [], - assigned_user: member, - status: nil, - title: title, - description: description, - due_date: date) - let manager = TicketManager(identity) - manager.makeTicket(ticket: ticket) { result in - switch result { - case .success(_): - DispatchQueue.main.async { - self.dismiss(animated: true, completion: nil) - NotificationCenter.default.post(name: .refreshTrigger, object: self) - } - case .failure(let error): - self.presentErrorFromMainThread(error: error) - } - } - } - } - - // MARK: Placeholder text for description - // Whoever decided to code UITextView deserves to be beaten - func textViewDidBeginEditing (_ textView: UITextView) { - if ticketDescription.textColor == .lightGray { - ticketDescription.text = nil - ticketDescription.textColor = .black - } - } - - func textViewDidEndEditing (_ textView: UITextView) { - if ticketDescription.text.isEmpty || ticketDescription.text == "" { - ticketDescription.textColor = .lightGray - ticketDescription.text = "Description of Ticket" - } - } - - // We understand that these two are bad, but for now, passing parameters to selectors is not very fun. - @objc func setToEditMode() { - editingTicket = true - navigationItem.rightBarButtonItem = nil - navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, - target: self, - action: #selector(submitTicketMode)) - ticketTitle.isUserInteractionEnabled = true - ticketDescription.isUserInteractionEnabled = true - dateTimePicker.isUserInteractionEnabled = true - assignedUserTextField.isUserInteractionEnabled = true - dueDateSwitch.isHidden = false - dueDateSwitch.isOn = dateTimePicker.isEnabled - dateTimePicker.isHidden = false - dueDateLabel.isHidden = false - } - - func noEditingMode() { - editingTicket = false - navigationItem.rightBarButtonItem = nil - navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, - target: self, - action: #selector(setToEditMode)) - ticketTitle.isUserInteractionEnabled = false - ticketDescription.isUserInteractionEnabled = false - dateTimePicker.isUserInteractionEnabled = false - assignedUserTextField.isUserInteractionEnabled = false - dueDateSwitch.isHidden = true - dateTimePicker.isHidden = !dateTimePicker.isEnabled - dueDateLabel.isHidden = !dateTimePicker.isEnabled - } - - func createUserPicker() { - // create toolbar - let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 35)) - toolbar.sizeToFit() - toolbar.translatesAutoresizingMaskIntoConstraints = false - - // create bar button - let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(userPicked)) - toolbar.setItems([doneButton], animated: true) - assignedUserTextField.inputAccessoryView = toolbar - - assignedUserTextField.inputView = pickerView - } - - @objc func userPicked() { - assignedUserTextField.text = currentMember?.owner.username ?? "No Assigned User" - self.view.endEditing(true) - - } - - @IBSegueAction func segueToDetail(_ coder: NSCoder) -> TicketDetailTableViewController? { - return TicketDetailTableViewController(coder: coder, identity: identity, ticket: ticket) - } - - // MARK: UIPickerView manager - func numberOfComponents(in pickerView: UIPickerView) -> Int { - return 1 - } - - func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { - return teamMembers.count - } - - func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - return teamMembers[row]?.owner.username ?? "No Assigned User" - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - currentMember = teamMembers[row] - } - -} diff --git a/Sluggo/View Controllers/TicketListController.swift b/Sluggo/View Controllers/TicketListController.swift index f1b78b8..9eaec8a 100644 --- a/Sluggo/View Controllers/TicketListController.swift +++ b/Sluggo/View Controllers/TicketListController.swift @@ -73,22 +73,24 @@ class TicketListController: UITableViewController { let identity = self.identity let detailStoryboard = UIStoryboard(name: "TicketDetail", bundle: nil) if let view = detailStoryboard.instantiateViewController(identifier: "TicketDetail", creator: { coder in - return TicketDetailTableViewController(coder: coder, - identity: identity, ticket: self.tickets[indexPath.row]) + return TicketDetailTableViewController(coder: coder) }) as TicketDetailTableViewController? { + view.identity = identity + view.ticket = self.tickets[indexPath.row] navigationController?.pushViewController(view, animated: true) } } // MARK: menu item delegates func connectPopUp() { - let identity = self.identity let detailStoryboard = UIStoryboard(name: "TicketDetail", bundle: nil) - if let view = detailStoryboard.instantiateViewController(identifier: "TicketDetail", creator: { coder in - return TicketDetailTableViewController(coder: coder, identity: identity, ticket: nil) - }) as TicketDetailTableViewController? { - self.present(view, animated: true, completion: nil) + let view = detailStoryboard.instantiateViewController(identifier: "TicketDetailModal") + if let child = view.children[0] as? TicketDetailTableViewController { + child.identity = self.identity + child.ticket = nil } + + self.present(view, animated: true, completion: nil) } func launchFilterPopup() { From c965fc32e228934db5cdcee4004c8bf47f0441f7 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Fri, 14 May 2021 15:27:25 -0700 Subject: [PATCH 158/224] Statuses are added, right now differentiated with tags --- Sluggo/Storyboards/TicketDetail.storyboard | 41 +++++++++++- .../TicketDetailTableViewController.swift | 63 +++++++++++++++++-- 2 files changed, 97 insertions(+), 7 deletions(-) diff --git a/Sluggo/Storyboards/TicketDetail.storyboard b/Sluggo/Storyboards/TicketDetail.storyboard index 0337519..cfdf949 100644 --- a/Sluggo/Storyboards/TicketDetail.storyboard +++ b/Sluggo/Storyboards/TicketDetail.storyboard @@ -65,10 +65,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -106,7 +142,7 @@ - + @@ -152,6 +188,7 @@ + diff --git a/Sluggo/View Controllers/TicketDetailTableViewController.swift b/Sluggo/View Controllers/TicketDetailTableViewController.swift index d532bc4..84a2b10 100644 --- a/Sluggo/View Controllers/TicketDetailTableViewController.swift +++ b/Sluggo/View Controllers/TicketDetailTableViewController.swift @@ -13,6 +13,7 @@ class TicketDetailTableViewController: UITableViewController { @IBOutlet var ticketTitle: UITextField! @IBOutlet var ticketDescription: UITextView! @IBOutlet var assignedField: UITextField! + @IBOutlet var statusField: UITextField! @IBOutlet var dueDatePicker: UIDatePicker! @IBOutlet var dueDateSwitch: UISwitch! @IBOutlet var dueDateLabel: UILabel! @@ -23,10 +24,13 @@ class TicketDetailTableViewController: UITableViewController { var identity: AppIdentity! var ticket: TicketRecord? var pickerView: UIPickerView = UIPickerView() + var statusPicker: UIPickerView = UIPickerView() var editingTicket = false var teamMembers: [MemberRecord?] = [nil] + var statuses: [StatusRecord?] = [nil] var currentMember: MemberRecord? + var currentStatus: StatusRecord? override func viewDidLoad() { super.viewDidLoad() @@ -42,8 +46,25 @@ class TicketDetailTableViewController: UITableViewController { } }) } + + let statusManager = StatusManager(identity: self.identity) + statusManager.listFromTeams(page: 1) {result in + self.processResult(result: result, onSuccess: { record in + self.statuses = [nil] + for status in record.results { + self.statuses.append(status) + } + }) + } + pickerView.dataSource = self pickerView.delegate = self + + statusPicker.dataSource = self + statusPicker.delegate = self + + pickerView.tag = 1 + statusPicker.tag = 2 updateUI() } @@ -53,6 +74,7 @@ class TicketDetailTableViewController: UITableViewController { ticketTitle.text = self.ticket?.title ?? "" ticketDescription.text = ticket?.description ?? "" assignedField.text = ticket?.assigned_user?.owner.username ?? "No Assigned User" + statusField.text = ticket?.status?.title ?? "No Status Selected" currentMember = ticket?.assigned_user ?? nil dueDatePicker.date = ticket?.due_date ?? Date() dueDatePicker.isHidden = (ticket?.due_date == nil) @@ -63,7 +85,7 @@ class TicketDetailTableViewController: UITableViewController { navBar.title = self.ticket != nil ? "Selected Ticket" : "Create a Ticket" setEditMode(self.ticket == nil) createUserPicker() - + createStatusPicker() rightButton.title = (self.ticket != nil) ? "Edit" : "Done" } @@ -77,6 +99,7 @@ class TicketDetailTableViewController: UITableViewController { dueDatePicker.isUserInteractionEnabled = editing dueDateSwitch.isUserInteractionEnabled = editing assignedField.isUserInteractionEnabled = editing + statusField.isUserInteractionEnabled = editing dueDateSwitch.isHidden = !editing dueDatePicker.isHidden = (ticket?.due_date == nil && !editing) dueDateLabel.isHidden = (ticket?.due_date != nil || editing) @@ -87,12 +110,14 @@ class TicketDetailTableViewController: UITableViewController { let description = ticketDescription.text let date = dueDateSwitch.isOn ? dueDatePicker.date : nil let member = currentMember?.id + let status = currentStatus?.id if editingTicket { ticket!.title = title ticket!.description = description ticket!.due_date = date ticket!.assigned_user = currentMember + ticket!.status = currentStatus let manager = TicketManager(identity) @@ -107,7 +132,7 @@ class TicketDetailTableViewController: UITableViewController { } else { let ticket = WriteTicketRecord(tag_list: [], assigned_user: member, - status: nil, + status: status, title: title, description: description, due_date: date) @@ -158,23 +183,51 @@ extension TicketDetailTableViewController: UIPickerViewDelegate, UIPickerViewDat assignedField.inputView = pickerView } + + func createStatusPicker() { + let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 35)) + toolbar.sizeToFit() + toolbar.translatesAutoresizingMaskIntoConstraints = false + + // create bar button + let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(statusPicked)) + toolbar.setItems([doneButton], animated: true) + statusField.inputAccessoryView = toolbar + + statusField.inputView = statusPicker + } @objc func userPicked() { assignedField.text = currentMember?.owner.username ?? "No Assigned User" self.view.endEditing(true) } + + @objc func statusPicked() { + statusField.text = currentStatus?.title ?? "No Status Selected" + self.view.endEditing(true) + } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { - return teamMembers.count + return pickerView.tag == 1 ? teamMembers.count : statuses.count } func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - return teamMembers[row]?.owner.username ?? "No Assigned User" + if(pickerView.tag == 1){ + return teamMembers[row]?.owner.username ?? "No Assigned User" + } + else{ + return statuses[row]?.title ?? "No Status Selected" + } } func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - currentMember = teamMembers[row] + if(pickerView.tag == 1){ + currentMember = teamMembers[row] + } + else{ + currentStatus = statuses[row] + } } } From 4551df2e8224f65b7685a54ee23843d9d11472cd Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Fri, 14 May 2021 15:37:18 -0700 Subject: [PATCH 159/224] SwiftLint changes --- Sluggo/Models/Managers/MemberManager.swift | 1 + .../TicketDetailTableViewController.swift | 26 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/Sluggo/Models/Managers/MemberManager.swift b/Sluggo/Models/Managers/MemberManager.swift index 98112e3..049b2b2 100644 --- a/Sluggo/Models/Managers/MemberManager.swift +++ b/Sluggo/Models/Managers/MemberManager.swift @@ -17,6 +17,7 @@ class MemberManager: TeamPaginatedListable { self.identity = identity } + // swiftlint:disable:next identifier_name private func makeDetailUrl(id: String) -> URL { let urlString = identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + MemberManager.urlBase + "\(id)/" diff --git a/Sluggo/View Controllers/TicketDetailTableViewController.swift b/Sluggo/View Controllers/TicketDetailTableViewController.swift index 84a2b10..68672f5 100644 --- a/Sluggo/View Controllers/TicketDetailTableViewController.swift +++ b/Sluggo/View Controllers/TicketDetailTableViewController.swift @@ -19,7 +19,7 @@ class TicketDetailTableViewController: UITableViewController { @IBOutlet var dueDateLabel: UILabel! @IBOutlet var navBar: UINavigationItem! @IBOutlet var rightButton: UIBarButtonItem! - + // MARK: Variables var identity: AppIdentity! var ticket: TicketRecord? @@ -46,7 +46,7 @@ class TicketDetailTableViewController: UITableViewController { } }) } - + let statusManager = StatusManager(identity: self.identity) statusManager.listFromTeams(page: 1) {result in self.processResult(result: result, onSuccess: { record in @@ -56,13 +56,13 @@ class TicketDetailTableViewController: UITableViewController { } }) } - + pickerView.dataSource = self pickerView.delegate = self - + statusPicker.dataSource = self statusPicker.delegate = self - + pickerView.tag = 1 statusPicker.tag = 2 updateUI() @@ -183,7 +183,7 @@ extension TicketDetailTableViewController: UIPickerViewDelegate, UIPickerViewDat assignedField.inputView = pickerView } - + func createStatusPicker() { let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 35)) toolbar.sizeToFit() @@ -193,7 +193,7 @@ extension TicketDetailTableViewController: UIPickerViewDelegate, UIPickerViewDat let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(statusPicked)) toolbar.setItems([doneButton], animated: true) statusField.inputAccessoryView = toolbar - + statusField.inputView = statusPicker } @@ -202,7 +202,7 @@ extension TicketDetailTableViewController: UIPickerViewDelegate, UIPickerViewDat self.view.endEditing(true) } - + @objc func statusPicked() { statusField.text = currentStatus?.title ?? "No Status Selected" self.view.endEditing(true) @@ -213,19 +213,17 @@ extension TicketDetailTableViewController: UIPickerViewDelegate, UIPickerViewDat } func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - if(pickerView.tag == 1){ + if pickerView.tag == 1 { return teamMembers[row]?.owner.username ?? "No Assigned User" - } - else{ + } else { return statuses[row]?.title ?? "No Status Selected" } } func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - if(pickerView.tag == 1){ + if pickerView.tag == 1 { currentMember = teamMembers[row] - } - else{ + } else { currentStatus = statuses[row] } } From 7f92407e8b3afc655b3de6167fde4c23ad802158 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Fri, 14 May 2021 16:03:21 -0700 Subject: [PATCH 160/224] TicketDetail Updates done --- Sluggo/Storyboards/Home.storyboard | 20 ++++++++++---------- Sluggo/Storyboards/TicketDetail.storyboard | 21 +++++---------------- 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/Sluggo/Storyboards/Home.storyboard b/Sluggo/Storyboards/Home.storyboard index c3d14fd..7510962 100644 --- a/Sluggo/Storyboards/Home.storyboard +++ b/Sluggo/Storyboards/Home.storyboard @@ -1,8 +1,8 @@ - + - + @@ -41,7 +41,7 @@ - + @@ -62,29 +62,29 @@ - + - + - + - + @@ -153,7 +153,7 @@ - + diff --git a/Sluggo/Storyboards/TicketDetail.storyboard b/Sluggo/Storyboards/TicketDetail.storyboard index cfdf949..243188b 100644 --- a/Sluggo/Storyboards/TicketDetail.storyboard +++ b/Sluggo/Storyboards/TicketDetail.storyboard @@ -67,20 +67,13 @@ - - + + - + - - - - - - - @@ -89,12 +82,8 @@ - - - - @@ -104,7 +93,7 @@ - + @@ -142,7 +131,7 @@ - + From 3dfc68feb8bcbfed388222df19cc1f4631fbf46f Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Fri, 14 May 2021 16:51:45 -0700 Subject: [PATCH 161/224] Remove commented section no longer used --- .../MemberListViewController.swift | 34 ------------------- 1 file changed, 34 deletions(-) diff --git a/Sluggo/View Controllers/MemberListViewController.swift b/Sluggo/View Controllers/MemberListViewController.swift index 96c85c7..142b873 100644 --- a/Sluggo/View Controllers/MemberListViewController.swift +++ b/Sluggo/View Controllers/MemberListViewController.swift @@ -41,8 +41,6 @@ class MemberListViewController: UITableViewController { let member = self.members[indexPath.row] cell.textLabel?.text = member.owner.username - //cell.accessoryType = (member.owner.id == self.identity.authenticatedUser!.pk) ? .checkmark : .none - return cell } @@ -73,36 +71,4 @@ class MemberListViewController: UITableViewController { } } -// private func loadData(page: Int) { -// let memberManager = MemberManager(identity: identity) -// -// memberManager.listFromTeams() { result in -// self.processResult(result: result, onSuccess: { record in -// self.maxNumber = record.count -// -// var membersCopy = Array(self.members) -// let pageOffset = (page - 1) * self.identity.pageSize -// -// if (pageOffset < self.members.count) { -// membersCopy.removeSubrange(pageOffset...self.members.count-1) -// } -// -// for entry in record.results { -// membersCopy.append(entry) -// } -// -// DispatchQueue.main.async { -// self.refreshControl?.endRefreshing() -// self.members = membersCopy -// self.tableView.reloadData() -// self.isFetching = false -// } -// }, onError: { error in -// DispatchQueue.main.async { -// let alert = UIAlertController.errorController(error: error) -// self.present(alert, animated: true, completion: nil) -// } -// }, after: nil) -// } -// } } From 2d02aae63d3423fa14f0317ab81a217d2bc75412 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Fri, 14 May 2021 17:09:49 -0700 Subject: [PATCH 162/224] Left align due date text --- Sluggo/Storyboards/TicketDetail.storyboard | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Sluggo/Storyboards/TicketDetail.storyboard b/Sluggo/Storyboards/TicketDetail.storyboard index 243188b..d188f26 100644 --- a/Sluggo/Storyboards/TicketDetail.storyboard +++ b/Sluggo/Storyboards/TicketDetail.storyboard @@ -106,21 +106,22 @@ - + + From 72dcf3c84a2d47e74d3307cc284eb237f6281731 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Fri, 14 May 2021 17:36:58 -0700 Subject: [PATCH 163/224] SwiftLint adjustments for Member VC --- .../MemberListViewController.swift | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/Sluggo/View Controllers/MemberListViewController.swift b/Sluggo/View Controllers/MemberListViewController.swift index 142b873..9e1552c 100644 --- a/Sluggo/View Controllers/MemberListViewController.swift +++ b/Sluggo/View Controllers/MemberListViewController.swift @@ -12,48 +12,47 @@ class MemberListViewController: UITableViewController { private var members: [MemberRecord] = [] private var isFetching = false private var semaphore = DispatchSemaphore(value: 1) - + required init? (coder: NSCoder, identity: AppIdentity) { self.identity = identity super.init(coder: coder) } - + required init?(coder: NSCoder) { super.init(coder: coder) } - + override func viewDidLoad() { self.configureRefreshControl() self.handleRefreshAction() } - + func configureRefreshControl() { refreshControl = UIRefreshControl() refreshControl?.addTarget(self, action: #selector(handleRefreshAction), for: .valueChanged) } - + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return members.count } - + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "MemberCell", for: indexPath) as UITableViewCell let member = self.members[indexPath.row] cell.textLabel?.text = member.owner.username - + return cell } - - + @objc func handleRefreshAction() { DispatchQueue.global(qos: .userInitiated).async { self.semaphore.wait() - + let onSuccess = { (members: [MemberRecord]) -> Void in self.members = members } - + let after = { () -> Void in DispatchQueue.main.async { self.refreshControl?.endRefreshing() @@ -61,7 +60,7 @@ class MemberListViewController: UITableViewController { } self.semaphore.signal() } - + let memberManager = MemberManager(identity: self.identity) unwindPagination(manager: memberManager, startingPage: 1, @@ -70,5 +69,5 @@ class MemberListViewController: UITableViewController { after: after) } } - + } From fc0983aa6efc03757c4fd94bdfbd42f8e06ddb0b Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Fri, 14 May 2021 17:40:07 -0700 Subject: [PATCH 164/224] Title fixed for TabView --- Sluggo/Storyboards/Home.storyboard | 18 +++++++++--------- Sluggo/Storyboards/Ticket.storyboard | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Sluggo/Storyboards/Home.storyboard b/Sluggo/Storyboards/Home.storyboard index 7510962..dc77a30 100644 --- a/Sluggo/Storyboards/Home.storyboard +++ b/Sluggo/Storyboards/Home.storyboard @@ -7,11 +7,11 @@ - + - + @@ -62,29 +62,29 @@ - + - + - + - + @@ -166,7 +166,7 @@ - + diff --git a/Sluggo/Storyboards/Ticket.storyboard b/Sluggo/Storyboards/Ticket.storyboard index 44b8217..cfca5bb 100644 --- a/Sluggo/Storyboards/Ticket.storyboard +++ b/Sluggo/Storyboards/Ticket.storyboard @@ -127,11 +127,11 @@ - + - + From fcc27ccf9887b98f6590220e80d53283519236d0 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Fri, 14 May 2021 19:25:50 -0700 Subject: [PATCH 165/224] Basic Instance URL has been implemented --- Sluggo/Storyboards/Main.storyboard | 78 +++++++++---------- .../LoginViewController.swift | 5 +- 2 files changed, 41 insertions(+), 42 deletions(-) diff --git a/Sluggo/Storyboards/Main.storyboard b/Sluggo/Storyboards/Main.storyboard index 982d8ae..6fe3c06 100644 --- a/Sluggo/Storyboards/Main.storyboard +++ b/Sluggo/Storyboards/Main.storyboard @@ -1,8 +1,8 @@ - + - + @@ -41,23 +41,16 @@ + + + + + + - - - - - - - - - - - - - @@ -69,21 +62,6 @@ - - + - - + + + + + + - - - - - - @@ -174,7 +170,7 @@ - + @@ -220,7 +216,7 @@ - + @@ -124,7 +125,17 @@ - + + + + + + + + + + + From cd3b66e46265c5918a28916dc424f41a3fdd571e Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Sat, 15 May 2021 01:07:03 -0700 Subject: [PATCH 167/224] Added injection for Admin Table View Controller --- Sluggo.xcodeproj/project.pbxproj | 4 + Sluggo/Storyboards/Admin.storyboard | 48 +++++---- .../AdminTableViewController.swift | 101 ++++++++++++++++++ .../View Controllers/RootViewController.swift | 4 + 4 files changed, 139 insertions(+), 18 deletions(-) create mode 100644 Sluggo/View Controllers/AdminTableViewController.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 9cee53d..3174958 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -53,6 +53,7 @@ 5ACC0F012630CA1400DCF83E /* ErrorMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ACC0F002630CA1400DCF83E /* ErrorMessage.swift */; }; 5ACC0F062630CA2700DCF83E /* Exceptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ACC0F052630CA2700DCF83E /* Exceptions.swift */; }; 7D01B55C264FAAEE0009FA97 /* Admin.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7D01B55B264FAAEE0009FA97 /* Admin.storyboard */; }; + 7D01B55E264FADC40009FA97 /* AdminTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D01B55D264FADC40009FA97 /* AdminTableViewController.swift */; }; 7D1354FC262D026E00D38EE0 /* UserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1354FB262D026E00D38EE0 /* UserTests.swift */; }; 7D1DA6AE262B82C200E7C12D /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */; }; 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */; }; @@ -135,6 +136,7 @@ 5ACC0F002630CA1400DCF83E /* ErrorMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMessage.swift; sourceTree = ""; }; 5ACC0F052630CA2700DCF83E /* Exceptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Exceptions.swift; sourceTree = ""; }; 7D01B55B264FAAEE0009FA97 /* Admin.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Admin.storyboard; sourceTree = ""; }; + 7D01B55D264FADC40009FA97 /* AdminTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminTableViewController.swift; sourceTree = ""; }; 7D1354FB262D026E00D38EE0 /* UserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTests.swift; sourceTree = ""; }; 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RootViewController.swift; path = "Sluggo/View Controllers/RootViewController.swift"; sourceTree = SOURCE_ROOT; }; 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarData.swift; sourceTree = ""; }; @@ -360,6 +362,7 @@ 5A25CFAB264B966300852035 /* TicketFilterTableViewController.swift */, 2FF89023264BBAC600D50B69 /* TicketDetailTableViewController.swift */, 05DE0CD6264F11E400D69C61 /* MemberListViewController.swift */, + 7D01B55D264FADC40009FA97 /* AdminTableViewController.swift */, ); path = "View Controllers"; sourceTree = ""; @@ -553,6 +556,7 @@ 052258702636100B00B49CE4 /* LaunchViewController.swift in Sources */, 3A150A6726365A9A0052934B /* StatusRecord.swift in Sources */, 5A25CFF1264BE28100852035 /* HasTitle.swift in Sources */, + 7D01B55E264FADC40009FA97 /* AdminTableViewController.swift in Sources */, 7D53BE6926433D6600400D0E /* PinnedTicketManager.swift in Sources */, 5ACC0F062630CA2700DCF83E /* Exceptions.swift in Sources */, 05DE0CD7264F11E400D69C61 /* MemberListViewController.swift in Sources */, diff --git a/Sluggo/Storyboards/Admin.storyboard b/Sluggo/Storyboards/Admin.storyboard index 594961d..12c54e0 100644 --- a/Sluggo/Storyboards/Admin.storyboard +++ b/Sluggo/Storyboards/Admin.storyboard @@ -3,27 +3,10 @@ - - - - - - - - - - - - - - - - - @@ -37,13 +20,42 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sluggo/View Controllers/AdminTableViewController.swift b/Sluggo/View Controllers/AdminTableViewController.swift new file mode 100644 index 0000000..1e25ff2 --- /dev/null +++ b/Sluggo/View Controllers/AdminTableViewController.swift @@ -0,0 +1,101 @@ +// +// AdminTableViewController.swift +// Sluggo +// +// Created by Isaac Trimble-Pederson on 5/15/21. +// + +import UIKit + +class AdminTableViewController: UITableViewController { + + var identity: AppIdentity! + + required init?(coder: NSCoder) { + fatalError("Coder init called directly on AdminTableViewController. Identity not injected.") + } + + init?(coder: NSCoder, identity: AppIdentity) { + super.init(coder: coder) + + self.identity = identity + } + + override func viewDidLoad() { + super.viewDidLoad() + + // Uncomment the following line to preserve selection between presentations + // self.clearsSelectionOnViewWillAppear = false + + // Uncomment the following line to display an Edit button in the navigation bar for this view controller. + // self.navigationItem.rightBarButtonItem = self.editButtonItem + } + + // MARK: - Table view data source + + override func numberOfSections(in tableView: UITableView) -> Int { + // #warning Incomplete implementation, return the number of sections + return 0 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + // #warning Incomplete implementation, return the number of rows + return 0 + } + + /* + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) + + // Configure the cell... + + return cell + } + */ + + /* + // Override to support conditional editing of the table view. + override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { + // Return false if you do not want the specified item to be editable. + return true + } + */ + + /* + // Override to support editing the table view. + override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { + if editingStyle == .delete { + // Delete the row from the data source + tableView.deleteRows(at: [indexPath], with: .fade) + } else if editingStyle == .insert { + // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view + } + } + */ + + /* + // Override to support rearranging the table view. + override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) { + + } + */ + + /* + // Override to support conditional rearranging of the table view. + override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { + // Return false if you do not want the item to be re-orderable. + return true + } + */ + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + +} diff --git a/Sluggo/View Controllers/RootViewController.swift b/Sluggo/View Controllers/RootViewController.swift index 3439aa9..d7de704 100644 --- a/Sluggo/View Controllers/RootViewController.swift +++ b/Sluggo/View Controllers/RootViewController.swift @@ -64,6 +64,10 @@ class RootViewController: UIViewController { return MemberListViewController(coder: coder, identity: identity) } + @IBSegueAction func createAdmin(_ coder: NSCoder) -> AdminTableViewController? { + return AdminTableViewController(coder: coder, identity: identity) + } + @IBAction func receivedGesture() { NotificationCenter.default.post(name: .onSidebarTrigger, object: self, userInfo: [Sidebar.USER_INFO_KEY: SidebarStatus.open]) From efe1525fb187d469bb636a76367db642684de025 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Sat, 15 May 2021 22:44:20 -0700 Subject: [PATCH 168/224] Conditional attachment of the Admin page The AdminTableViewController will not be created or attached to the tab bar unless the member is an admin. --- Sluggo/Storyboards/Admin.storyboard | 24 +------- Sluggo/Storyboards/Base.lproj/Root.storyboard | 13 +---- .../AdminTableViewController.swift | 10 ++-- .../View Controllers/RootViewController.swift | 58 +++++++++++++++++-- 4 files changed, 62 insertions(+), 43 deletions(-) diff --git a/Sluggo/Storyboards/Admin.storyboard b/Sluggo/Storyboards/Admin.storyboard index 12c54e0..5c1b538 100644 --- a/Sluggo/Storyboards/Admin.storyboard +++ b/Sluggo/Storyboards/Admin.storyboard @@ -1,5 +1,5 @@ - + @@ -7,26 +7,6 @@ - - - - - - - - - - - - - - - - - - - - @@ -51,6 +31,7 @@ + @@ -58,7 +39,6 @@ - diff --git a/Sluggo/Storyboards/Base.lproj/Root.storyboard b/Sluggo/Storyboards/Base.lproj/Root.storyboard index 5d1ffcc..2ffb87c 100644 --- a/Sluggo/Storyboards/Base.lproj/Root.storyboard +++ b/Sluggo/Storyboards/Base.lproj/Root.storyboard @@ -27,7 +27,7 @@ - + @@ -89,7 +89,6 @@ - @@ -127,16 +126,6 @@ - - - - - - - - - - diff --git a/Sluggo/View Controllers/AdminTableViewController.swift b/Sluggo/View Controllers/AdminTableViewController.swift index 1e25ff2..c221fea 100644 --- a/Sluggo/View Controllers/AdminTableViewController.swift +++ b/Sluggo/View Controllers/AdminTableViewController.swift @@ -8,16 +8,16 @@ import UIKit class AdminTableViewController: UITableViewController { - + var identity: AppIdentity! - + required init?(coder: NSCoder) { - fatalError("Coder init called directly on AdminTableViewController. Identity not injected.") + super.init(coder: coder) } - + init?(coder: NSCoder, identity: AppIdentity) { super.init(coder: coder) - + self.identity = identity } diff --git a/Sluggo/View Controllers/RootViewController.swift b/Sluggo/View Controllers/RootViewController.swift index d7de704..5bffc28 100644 --- a/Sluggo/View Controllers/RootViewController.swift +++ b/Sluggo/View Controllers/RootViewController.swift @@ -14,6 +14,8 @@ class RootViewController: UIViewController { @IBOutlet var swipeRight: UISwipeGestureRecognizer! @IBOutlet var swipeLeft: UISwipeGestureRecognizer! + var mainTabBarController: UITabBarController? + init? (coder: NSCoder, identity: AppIdentity) { self.identity = identity super.init(coder: coder) @@ -43,6 +45,58 @@ class RootViewController: UIViewController { name: .onSidebarTrigger, object: nil) // Do any additional setup after loading the view. + + // MARK: Attach admin if relevant + getMember(completionHandler: attachAdminIfMemberAdmin) + } + + func getMember(completionHandler: @escaping ((MemberRecord) -> Void)) { + let memberManager = MemberManager(identity: identity) + memberManager.getMemberRecord(user: identity.authenticatedUser!, identity: identity) { result in + switch result { + case .success(let member): + completionHandler(member) + case .failure: + // Silently fails, for now + // TODO: is there a better approach? + print("Getting the member failed.") + } + } + } + + func attachAdminIfMemberAdmin(member: MemberRecord) { + if member.role == "AD" { + // Attach admin VC + DispatchQueue.main.async { + + if let adminVC = UIStoryboard(name: "Admin", + bundle: Bundle.main) + .instantiateInitialViewController() as? AdminTableViewController { + adminVC.identity = self.identity // Does this create a race condition? + + // Wrap this in a navigation controller + let navVC = UINavigationController(rootViewController: adminVC) + + // Create bar button item + let adminBarButtonImage = UIImage(systemName: "shield") + let barItem = UITabBarItem(title: "Admin", image: adminBarButtonImage, selectedImage: nil) + navVC.tabBarItem = barItem + + // Attach VC + self.mainTabBarController?.viewControllers?.append(navVC) + } + } + } + } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + switch segue.identifier { + case "tabControllerEmbed": + mainTabBarController = segue.destination as? UITabBarController + default: + break + } + super.prepare(for: segue, sender: sender) } // MARK: - Navigation @@ -64,10 +118,6 @@ class RootViewController: UIViewController { return MemberListViewController(coder: coder, identity: identity) } - @IBSegueAction func createAdmin(_ coder: NSCoder) -> AdminTableViewController? { - return AdminTableViewController(coder: coder, identity: identity) - } - @IBAction func receivedGesture() { NotificationCenter.default.post(name: .onSidebarTrigger, object: self, userInfo: [Sidebar.USER_INFO_KEY: SidebarStatus.open]) From 8d79d602db40c0e34c04faa4b0dae77071a20ceb Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Sat, 15 May 2021 23:07:36 -0700 Subject: [PATCH 169/224] Add support for attaching/detaching updating when switching teams --- .../View Controllers/RootViewController.swift | 26 ++++++++++++++++--- .../TeamSelectorContainerViewController.swift | 2 ++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/Sluggo/View Controllers/RootViewController.swift b/Sluggo/View Controllers/RootViewController.swift index 5bffc28..6c40306 100644 --- a/Sluggo/View Controllers/RootViewController.swift +++ b/Sluggo/View Controllers/RootViewController.swift @@ -15,6 +15,7 @@ class RootViewController: UIViewController { @IBOutlet var swipeLeft: UISwipeGestureRecognizer! var mainTabBarController: UITabBarController? + var adminController: UIViewController? init? (coder: NSCoder, identity: AppIdentity) { self.identity = identity @@ -43,11 +44,20 @@ class RootViewController: UIViewController { NotificationCenter.default.addObserver(self, selector: #selector(onSidebarNotificationRecieved), name: .onSidebarTrigger, object: nil) + + NotificationCenter.default.addObserver(self, + selector: #selector(updateAdminAttachment), + name: Constants.Signals.TEAM_CHANGE_NOTIFICATION, + object: nil) // Do any additional setup after loading the view. // MARK: Attach admin if relevant - getMember(completionHandler: attachAdminIfMemberAdmin) + updateAdminAttachment() + } + + @objc func updateAdminAttachment() { + getMember(completionHandler: updateAdminAttachmentForMemberRole) } func getMember(completionHandler: @escaping ((MemberRecord) -> Void)) { @@ -64,11 +74,11 @@ class RootViewController: UIViewController { } } - func attachAdminIfMemberAdmin(member: MemberRecord) { + func updateAdminAttachmentForMemberRole(member: MemberRecord) { if member.role == "AD" { // Attach admin VC DispatchQueue.main.async { - + if(self.adminController != nil) { return } if let adminVC = UIStoryboard(name: "Admin", bundle: Bundle.main) .instantiateInitialViewController() as? AdminTableViewController { @@ -84,8 +94,18 @@ class RootViewController: UIViewController { // Attach VC self.mainTabBarController?.viewControllers?.append(navVC) + + // Track it here for removal + self.adminController = navVC } } + } else { + // Filter the admin controller out of the tab bar view controllers + // This *should* remove the view controller which will then deallocate automatically + DispatchQueue.main.async { + self.mainTabBarController?.viewControllers = self.mainTabBarController?.viewControllers?.filter{ $0 != self.adminController } + self.adminController = nil + } } } diff --git a/Sluggo/View Controllers/TeamSelectorContainerViewController.swift b/Sluggo/View Controllers/TeamSelectorContainerViewController.swift index d625167..342eab1 100644 --- a/Sluggo/View Controllers/TeamSelectorContainerViewController.swift +++ b/Sluggo/View Controllers/TeamSelectorContainerViewController.swift @@ -7,6 +7,8 @@ import UIKit + + class TeamSelectorContainerViewController: UIViewController { private var identity: AppIdentity private var completion: (() -> Void)? From 63104680fe671b20ab0dd232eda01460731697ef Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Sat, 15 May 2021 23:18:00 -0700 Subject: [PATCH 170/224] Skeleton for User Roles List Making use of a static table view controller for the Admin page since this is just going to link to other pages. This *could* be made dynamic, but I don't think it's worth the effort. --- Sluggo.xcodeproj/project.pbxproj | 14 +++- Sluggo/Storyboards/Admin/Admin.storyboard | 79 +++++++++++++++++++ .../UserRolesList.storyboard} | 24 +++--- .../AdminTableViewController.swift | 78 ------------------ 4 files changed, 104 insertions(+), 91 deletions(-) create mode 100644 Sluggo/Storyboards/Admin/Admin.storyboard rename Sluggo/Storyboards/{Admin.storyboard => Admin/UserRolesList.storyboard} (81%) diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 3174958..94c9764 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -59,6 +59,7 @@ 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */; }; 7D23834B264119050091C7E8 /* HomeTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D23834A264119050091C7E8 /* HomeTableViewController.swift */; }; 7D53BE6926433D6600400D0E /* PinnedTicketManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D53BE6826433D6600400D0E /* PinnedTicketManager.swift */; }; + 7D679F822650EF98000429E8 /* UserRolesList.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7D679F812650EF98000429E8 /* UserRolesList.storyboard */; }; 7D778C06264A374C00D4061A /* ViewControllerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D778C05264A374C00D4061A /* ViewControllerExtensions.swift */; }; 7D778C0A264A6A0F00D4061A /* TicketManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D778C09264A6A0F00D4061A /* TicketManagerTests.swift */; }; 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */; }; @@ -142,6 +143,7 @@ 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarData.swift; sourceTree = ""; }; 7D23834A264119050091C7E8 /* HomeTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HomeTableViewController.swift; path = "Sluggo/View Controllers/HomeTableViewController.swift"; sourceTree = SOURCE_ROOT; }; 7D53BE6826433D6600400D0E /* PinnedTicketManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedTicketManager.swift; sourceTree = ""; }; + 7D679F812650EF98000429E8 /* UserRolesList.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = UserRolesList.storyboard; sourceTree = ""; }; 7D778C05264A374C00D4061A /* ViewControllerExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerExtensions.swift; sourceTree = ""; }; 7D778C09264A6A0F00D4061A /* TicketManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketManagerTests.swift; sourceTree = ""; }; 7D9963A2261EF3A4002338E7 /* Sluggo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sluggo.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -265,6 +267,15 @@ path = Managers; sourceTree = ""; }; + 7D679F802650EF83000429E8 /* Admin */ = { + isa = PBXGroup; + children = ( + 7D01B55B264FAAEE0009FA97 /* Admin.storyboard */, + 7D679F812650EF98000429E8 /* UserRolesList.storyboard */, + ); + path = Admin; + sourceTree = ""; + }; 7D778C04264A374200D4061A /* Extensions */ = { isa = PBXGroup; children = ( @@ -334,6 +345,7 @@ 7DC2A21526293F1E0002CEE6 /* Storyboards */ = { isa = PBXGroup; children = ( + 7D679F802650EF83000429E8 /* Admin */, 5A79FFE0262E562A00D04320 /* Main.storyboard */, 7D9963AB261EF3A4002338E7 /* Root.storyboard */, 7DC2A21626293F360002CEE6 /* Home.storyboard */, @@ -342,7 +354,6 @@ 7DEA17842639E7B5004E5A71 /* LaunchScreen.storyboard */, 3A150A76263CB5F70052934B /* TicketDetail.storyboard */, 05588A11264DB61E000D8674 /* TeamMembers.storyboard */, - 7D01B55B264FAAEE0009FA97 /* Admin.storyboard */, ); path = Storyboards; sourceTree = ""; @@ -484,6 +495,7 @@ 7D9963AD261EF3A4002338E7 /* Root.storyboard in Resources */, 7DEA17852639E7B5004E5A71 /* LaunchScreen.storyboard in Resources */, 7D01B55C264FAAEE0009FA97 /* Admin.storyboard in Resources */, + 7D679F822650EF98000429E8 /* UserRolesList.storyboard in Resources */, 3A150A77263CB5F70052934B /* TicketDetail.storyboard in Resources */, 7DC2A21726293F360002CEE6 /* Home.storyboard in Resources */, 7DC2A21C262943A70002CEE6 /* Sidebar.storyboard in Resources */, diff --git a/Sluggo/Storyboards/Admin/Admin.storyboard b/Sluggo/Storyboards/Admin/Admin.storyboard new file mode 100644 index 0000000..de824c3 --- /dev/null +++ b/Sluggo/Storyboards/Admin/Admin.storyboard @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sluggo/Storyboards/Admin.storyboard b/Sluggo/Storyboards/Admin/UserRolesList.storyboard similarity index 81% rename from Sluggo/Storyboards/Admin.storyboard rename to Sluggo/Storyboards/Admin/UserRolesList.storyboard index 5c1b538..510a6eb 100644 --- a/Sluggo/Storyboards/Admin.storyboard +++ b/Sluggo/Storyboards/Admin/UserRolesList.storyboard @@ -1,5 +1,5 @@ - + @@ -7,35 +7,35 @@ - - + + - - + + - + - + - - + + - + - + - + diff --git a/Sluggo/View Controllers/AdminTableViewController.swift b/Sluggo/View Controllers/AdminTableViewController.swift index c221fea..6ac76ac 100644 --- a/Sluggo/View Controllers/AdminTableViewController.swift +++ b/Sluggo/View Controllers/AdminTableViewController.swift @@ -20,82 +20,4 @@ class AdminTableViewController: UITableViewController { self.identity = identity } - - override func viewDidLoad() { - super.viewDidLoad() - - // Uncomment the following line to preserve selection between presentations - // self.clearsSelectionOnViewWillAppear = false - - // Uncomment the following line to display an Edit button in the navigation bar for this view controller. - // self.navigationItem.rightBarButtonItem = self.editButtonItem - } - - // MARK: - Table view data source - - override func numberOfSections(in tableView: UITableView) -> Int { - // #warning Incomplete implementation, return the number of sections - return 0 - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - // #warning Incomplete implementation, return the number of rows - return 0 - } - - /* - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) - - // Configure the cell... - - return cell - } - */ - - /* - // Override to support conditional editing of the table view. - override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { - // Return false if you do not want the specified item to be editable. - return true - } - */ - - /* - // Override to support editing the table view. - override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { - if editingStyle == .delete { - // Delete the row from the data source - tableView.deleteRows(at: [indexPath], with: .fade) - } else if editingStyle == .insert { - // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view - } - } - */ - - /* - // Override to support rearranging the table view. - override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) { - - } - */ - - /* - // Override to support conditional rearranging of the table view. - override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { - // Return false if you do not want the item to be re-orderable. - return true - } - */ - - /* - // MARK: - Navigation - - // In a storyboard-based application, you will often want to do a little preparation before navigation - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - // Get the new view controller using segue.destination. - // Pass the selected object to the new view controller. - } - */ - } From d2c2482d673fb6eb4bb49f82b8fa3feb08986f3f Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Sun, 16 May 2021 01:04:35 -0700 Subject: [PATCH 171/224] Added user roles and injectable view controller to show --- Sluggo.xcodeproj/project.pbxproj | 4 -- Sluggo/Storyboards/Admin/Admin.storyboard | 6 +-- .../Admin/UserRolesList.storyboard | 46 ------------------- Sluggo/Storyboards/TeamMembers.storyboard | 26 +++++++++-- .../AdminTableViewController.swift | 10 ++++ .../MemberListViewController.swift | 17 +++++++ 6 files changed, 51 insertions(+), 58 deletions(-) delete mode 100644 Sluggo/Storyboards/Admin/UserRolesList.storyboard diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 94c9764..02b28f0 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -59,7 +59,6 @@ 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */; }; 7D23834B264119050091C7E8 /* HomeTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D23834A264119050091C7E8 /* HomeTableViewController.swift */; }; 7D53BE6926433D6600400D0E /* PinnedTicketManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D53BE6826433D6600400D0E /* PinnedTicketManager.swift */; }; - 7D679F822650EF98000429E8 /* UserRolesList.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7D679F812650EF98000429E8 /* UserRolesList.storyboard */; }; 7D778C06264A374C00D4061A /* ViewControllerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D778C05264A374C00D4061A /* ViewControllerExtensions.swift */; }; 7D778C0A264A6A0F00D4061A /* TicketManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D778C09264A6A0F00D4061A /* TicketManagerTests.swift */; }; 7D9963A6261EF3A4002338E7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9963A5261EF3A4002338E7 /* AppDelegate.swift */; }; @@ -143,7 +142,6 @@ 7D1DA6B6262B866D00E7C12D /* SidebarData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarData.swift; sourceTree = ""; }; 7D23834A264119050091C7E8 /* HomeTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HomeTableViewController.swift; path = "Sluggo/View Controllers/HomeTableViewController.swift"; sourceTree = SOURCE_ROOT; }; 7D53BE6826433D6600400D0E /* PinnedTicketManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedTicketManager.swift; sourceTree = ""; }; - 7D679F812650EF98000429E8 /* UserRolesList.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = UserRolesList.storyboard; sourceTree = ""; }; 7D778C05264A374C00D4061A /* ViewControllerExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerExtensions.swift; sourceTree = ""; }; 7D778C09264A6A0F00D4061A /* TicketManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketManagerTests.swift; sourceTree = ""; }; 7D9963A2261EF3A4002338E7 /* Sluggo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sluggo.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -271,7 +269,6 @@ isa = PBXGroup; children = ( 7D01B55B264FAAEE0009FA97 /* Admin.storyboard */, - 7D679F812650EF98000429E8 /* UserRolesList.storyboard */, ); path = Admin; sourceTree = ""; @@ -495,7 +492,6 @@ 7D9963AD261EF3A4002338E7 /* Root.storyboard in Resources */, 7DEA17852639E7B5004E5A71 /* LaunchScreen.storyboard in Resources */, 7D01B55C264FAAEE0009FA97 /* Admin.storyboard in Resources */, - 7D679F822650EF98000429E8 /* UserRolesList.storyboard in Resources */, 3A150A77263CB5F70052934B /* TicketDetail.storyboard in Resources */, 7DC2A21726293F360002CEE6 /* Home.storyboard in Resources */, 7DC2A21C262943A70002CEE6 /* Sidebar.storyboard in Resources */, diff --git a/Sluggo/Storyboards/Admin/Admin.storyboard b/Sluggo/Storyboards/Admin/Admin.storyboard index de824c3..002ffbf 100644 --- a/Sluggo/Storyboards/Admin/Admin.storyboard +++ b/Sluggo/Storyboards/Admin/Admin.storyboard @@ -42,7 +42,7 @@ - + @@ -60,10 +60,10 @@ - + - + diff --git a/Sluggo/Storyboards/Admin/UserRolesList.storyboard b/Sluggo/Storyboards/Admin/UserRolesList.storyboard deleted file mode 100644 index 510a6eb..0000000 --- a/Sluggo/Storyboards/Admin/UserRolesList.storyboard +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Sluggo/Storyboards/TeamMembers.storyboard b/Sluggo/Storyboards/TeamMembers.storyboard index f71e4fc..67a66ac 100644 --- a/Sluggo/Storyboards/TeamMembers.storyboard +++ b/Sluggo/Storyboards/TeamMembers.storyboard @@ -1,8 +1,8 @@ - + - + @@ -10,18 +10,34 @@ - + - - + + + + + + diff --git a/Sluggo/View Controllers/AdminTableViewController.swift b/Sluggo/View Controllers/AdminTableViewController.swift index 6ac76ac..221ad22 100644 --- a/Sluggo/View Controllers/AdminTableViewController.swift +++ b/Sluggo/View Controllers/AdminTableViewController.swift @@ -20,4 +20,14 @@ class AdminTableViewController: UITableViewController { self.identity = identity } + + @IBSegueAction func createUsersList(_ coder: NSCoder) -> MemberListViewController? { + let memberVC = MemberListViewController(coder: coder, identity: identity) + memberVC?.showsRoles = true + memberVC?.generateSegueableController = { identity in + return nil + } + + return memberVC + } } diff --git a/Sluggo/View Controllers/MemberListViewController.swift b/Sluggo/View Controllers/MemberListViewController.swift index 9e1552c..3b262da 100644 --- a/Sluggo/View Controllers/MemberListViewController.swift +++ b/Sluggo/View Controllers/MemberListViewController.swift @@ -8,6 +8,8 @@ import UIKit class MemberListViewController: UITableViewController { var identity: AppIdentity! + var showsRoles: Bool = false + var generateSegueableController: ((AppIdentity) -> UIViewController?)? private var maxNumber: Int = 0 private var members: [MemberRecord] = [] private var isFetching = false @@ -25,6 +27,8 @@ class MemberListViewController: UITableViewController { override func viewDidLoad() { self.configureRefreshControl() self.handleRefreshAction() + + self.tableView.allowsSelection = (generateSegueableController != nil) } func configureRefreshControl() { @@ -40,9 +44,22 @@ class MemberListViewController: UITableViewController { let cell = tableView.dequeueReusableCell(withIdentifier: "MemberCell", for: indexPath) as UITableViewCell let member = self.members[indexPath.row] cell.textLabel?.text = member.owner.username + cell.detailTextLabel?.text = self.showsRoles ? member.role : "" return cell } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let generateController = self.generateSegueableController else { return } + + guard let viewController = generateController(identity) else { return } + + if let navController = self.navigationController { + navController.show(viewController, sender: self) + } else { + self.present(viewController, animated: true, completion: nil) + } + } @objc func handleRefreshAction() { From 9aa3d5c14077a0f1647b10b3ef003d7e19e26e6d Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Sun, 16 May 2021 01:08:05 -0700 Subject: [PATCH 172/224] Make member details injectable, for greater reusability --- Sluggo/View Controllers/AdminTableViewController.swift | 4 +++- Sluggo/View Controllers/MemberListViewController.swift | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Sluggo/View Controllers/AdminTableViewController.swift b/Sluggo/View Controllers/AdminTableViewController.swift index 221ad22..2d7e6df 100644 --- a/Sluggo/View Controllers/AdminTableViewController.swift +++ b/Sluggo/View Controllers/AdminTableViewController.swift @@ -23,10 +23,12 @@ class AdminTableViewController: UITableViewController { @IBSegueAction func createUsersList(_ coder: NSCoder) -> MemberListViewController? { let memberVC = MemberListViewController(coder: coder, identity: identity) - memberVC?.showsRoles = true memberVC?.generateSegueableController = { identity in return nil } + memberVC?.generateMemberDetail = { memberRecord in + return memberRecord.role + } return memberVC } diff --git a/Sluggo/View Controllers/MemberListViewController.swift b/Sluggo/View Controllers/MemberListViewController.swift index 3b262da..7939b94 100644 --- a/Sluggo/View Controllers/MemberListViewController.swift +++ b/Sluggo/View Controllers/MemberListViewController.swift @@ -8,8 +8,8 @@ import UIKit class MemberListViewController: UITableViewController { var identity: AppIdentity! - var showsRoles: Bool = false var generateSegueableController: ((AppIdentity) -> UIViewController?)? + var generateMemberDetail: ((MemberRecord) -> String?)? private var maxNumber: Int = 0 private var members: [MemberRecord] = [] private var isFetching = false @@ -44,7 +44,7 @@ class MemberListViewController: UITableViewController { let cell = tableView.dequeueReusableCell(withIdentifier: "MemberCell", for: indexPath) as UITableViewCell let member = self.members[indexPath.row] cell.textLabel?.text = member.owner.username - cell.detailTextLabel?.text = self.showsRoles ? member.role : "" + cell.detailTextLabel?.text = generateMemberDetail?(member) ?? "" return cell } From 1f0dc2a5d1a44e4e810fe59ba4dba777d0bff664 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Sun, 16 May 2021 01:09:27 -0700 Subject: [PATCH 173/224] Formatting fixes --- .../View Controllers/AdminTableViewController.swift | 2 +- .../View Controllers/MemberListViewController.swift | 8 ++++---- Sluggo/View Controllers/RootViewController.swift | 11 ++++++----- .../TeamSelectorContainerViewController.swift | 2 -- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/Sluggo/View Controllers/AdminTableViewController.swift b/Sluggo/View Controllers/AdminTableViewController.swift index 2d7e6df..831ce76 100644 --- a/Sluggo/View Controllers/AdminTableViewController.swift +++ b/Sluggo/View Controllers/AdminTableViewController.swift @@ -23,7 +23,7 @@ class AdminTableViewController: UITableViewController { @IBSegueAction func createUsersList(_ coder: NSCoder) -> MemberListViewController? { let memberVC = MemberListViewController(coder: coder, identity: identity) - memberVC?.generateSegueableController = { identity in + memberVC?.generateSegueableController = { _ in return nil } memberVC?.generateMemberDetail = { memberRecord in diff --git a/Sluggo/View Controllers/MemberListViewController.swift b/Sluggo/View Controllers/MemberListViewController.swift index 7939b94..ad21d49 100644 --- a/Sluggo/View Controllers/MemberListViewController.swift +++ b/Sluggo/View Controllers/MemberListViewController.swift @@ -27,7 +27,7 @@ class MemberListViewController: UITableViewController { override func viewDidLoad() { self.configureRefreshControl() self.handleRefreshAction() - + self.tableView.allowsSelection = (generateSegueableController != nil) } @@ -48,12 +48,12 @@ class MemberListViewController: UITableViewController { return cell } - + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let generateController = self.generateSegueableController else { return } - + guard let viewController = generateController(identity) else { return } - + if let navController = self.navigationController { navController.show(viewController, sender: self) } else { diff --git a/Sluggo/View Controllers/RootViewController.swift b/Sluggo/View Controllers/RootViewController.swift index 6c40306..56aaf7a 100644 --- a/Sluggo/View Controllers/RootViewController.swift +++ b/Sluggo/View Controllers/RootViewController.swift @@ -44,7 +44,7 @@ class RootViewController: UIViewController { NotificationCenter.default.addObserver(self, selector: #selector(onSidebarNotificationRecieved), name: .onSidebarTrigger, object: nil) - + NotificationCenter.default.addObserver(self, selector: #selector(updateAdminAttachment), name: Constants.Signals.TEAM_CHANGE_NOTIFICATION, @@ -55,7 +55,7 @@ class RootViewController: UIViewController { // MARK: Attach admin if relevant updateAdminAttachment() } - + @objc func updateAdminAttachment() { getMember(completionHandler: updateAdminAttachmentForMemberRole) } @@ -78,7 +78,7 @@ class RootViewController: UIViewController { if member.role == "AD" { // Attach admin VC DispatchQueue.main.async { - if(self.adminController != nil) { return } + if self.adminController != nil { return } if let adminVC = UIStoryboard(name: "Admin", bundle: Bundle.main) .instantiateInitialViewController() as? AdminTableViewController { @@ -94,7 +94,7 @@ class RootViewController: UIViewController { // Attach VC self.mainTabBarController?.viewControllers?.append(navVC) - + // Track it here for removal self.adminController = navVC } @@ -103,7 +103,8 @@ class RootViewController: UIViewController { // Filter the admin controller out of the tab bar view controllers // This *should* remove the view controller which will then deallocate automatically DispatchQueue.main.async { - self.mainTabBarController?.viewControllers = self.mainTabBarController?.viewControllers?.filter{ $0 != self.adminController } + self.mainTabBarController?.viewControllers = + self.mainTabBarController?.viewControllers?.filter { $0 != self.adminController } self.adminController = nil } } diff --git a/Sluggo/View Controllers/TeamSelectorContainerViewController.swift b/Sluggo/View Controllers/TeamSelectorContainerViewController.swift index 342eab1..d625167 100644 --- a/Sluggo/View Controllers/TeamSelectorContainerViewController.swift +++ b/Sluggo/View Controllers/TeamSelectorContainerViewController.swift @@ -7,8 +7,6 @@ import UIKit - - class TeamSelectorContainerViewController: UIViewController { private var identity: AppIdentity private var completion: (() -> Void)? From be63cecb66bb5d51a6ae5f6a59592ab56bca7e6c Mon Sep 17 00:00:00 2001 From: Stephan Martin Date: Sun, 16 May 2021 18:14:39 -0700 Subject: [PATCH 174/224] started work on tags --- Sluggo.xcodeproj/project.pbxproj | 11 +- .../Components/TicketTagTableViewCell.swift | 23 ++++ Sluggo/Storyboards/TicketDetail.storyboard | 101 +++++++++++++++++- .../TicketDetailTableViewController.swift | 48 +++++++++ .../TicketTabTableViewController.swift | 66 ++++++++++++ 5 files changed, 244 insertions(+), 5 deletions(-) create mode 100644 Sluggo/Components/TicketTagTableViewCell.swift create mode 100644 Sluggo/View Controllers/TicketTabTableViewController.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index f8cafcf..cfae0f0 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -24,6 +24,8 @@ 3A150A6726365A9A0052934B /* StatusRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A6626365A9A0052934B /* StatusRecord.swift */; }; 3A150A6C26365B670052934B /* TagRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A150A6B26365B670052934B /* TagRecord.swift */; }; 3A150A77263CB5F70052934B /* TicketDetail.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3A150A76263CB5F70052934B /* TicketDetail.storyboard */; }; + 3A90FBB12651CEED00BE4F5D /* TicketTagTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A90FBB02651CEED00BE4F5D /* TicketTagTableViewCell.swift */; }; + 3A90FBB32651D76900BE4F5D /* TicketTabTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A90FBB22651D76900BE4F5D /* TicketTabTableViewController.swift */; }; 5A00A0142639EDEA0085C8C6 /* URLRequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */; }; 5A00A045263A34CC0085C8C6 /* TicketTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A00A044263A34CC0085C8C6 /* TicketTableViewCell.swift */; }; 5A25CFA2264B8C8100852035 /* TagTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFA1264B8C8100852035 /* TagTests.swift */; }; @@ -106,6 +108,8 @@ 3A150A6626365A9A0052934B /* StatusRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusRecord.swift; sourceTree = ""; }; 3A150A6B26365B670052934B /* TagRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagRecord.swift; sourceTree = ""; }; 3A150A76263CB5F70052934B /* TicketDetail.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TicketDetail.storyboard; sourceTree = ""; }; + 3A90FBB02651CEED00BE4F5D /* TicketTagTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketTagTableViewCell.swift; sourceTree = ""; }; + 3A90FBB22651D76900BE4F5D /* TicketTabTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketTabTableViewController.swift; sourceTree = ""; }; 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLRequestBuilder.swift; sourceTree = ""; }; 5A00A044263A34CC0085C8C6 /* TicketTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketTableViewCell.swift; sourceTree = ""; }; 5A25CFA1264B8C8100852035 /* TagTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagTests.swift; sourceTree = ""; }; @@ -208,6 +212,7 @@ 5A00A044263A34CC0085C8C6 /* TicketTableViewCell.swift */, 5AC6458F26412F0C00F46137 /* TeamTableViewCell.swift */, 5A25CFE6264BD73D00852035 /* TicketFilterTableViewCell.swift */, + 3A90FBB02651CEED00BE4F5D /* TicketTagTableViewCell.swift */, ); path = Components; sourceTree = ""; @@ -349,16 +354,14 @@ 0522586F2636100B00B49CE4 /* LaunchViewController.swift */, 5A79FFE5262E563F00D04320 /* LoginViewController.swift */, 7D1DA6AD262B82C200E7C12D /* RootViewController.swift */, - 5A9C4C202641113D00B270AB /* TeamSelectorContainerViewController.swift */, - 5A9C4C1B2640BC4E00B270AB /* TeamTableViewController.swift */, 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */, - 7D23834A264119050091C7E8 /* HomeTableViewController.swift */, 5A9FF88D26386D4700378550 /* TicketListController.swift */, 5A9C4C1B2640BC4E00B270AB /* TeamTableViewController.swift */, 5A9C4C202641113D00B270AB /* TeamSelectorContainerViewController.swift */, 7D23834A264119050091C7E8 /* HomeTableViewController.swift */, 5A25CFAB264B966300852035 /* TicketFilterTableViewController.swift */, 2FF89023264BBAC600D50B69 /* TicketDetailTableViewController.swift */, + 3A90FBB22651D76900BE4F5D /* TicketTabTableViewController.swift */, 05DE0CD6264F11E400D69C61 /* MemberListViewController.swift */, ); path = "View Controllers"; @@ -530,6 +533,7 @@ 5A9C4C212641113D00B270AB /* TeamSelectorContainerViewController.swift in Sources */, 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */, 5AB1C8FC262FF1690010F85E /* TicketRecord.swift in Sources */, + 3A90FBB32651D76900BE4F5D /* TicketTabTableViewController.swift in Sources */, 5A25CFDF264BCF2C00852035 /* TicketFilterParameters.swift in Sources */, 5A25CFE7264BD73D00852035 /* TicketFilterTableViewCell.swift in Sources */, 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */, @@ -551,6 +555,7 @@ 2F3AD7A32627C86A00B61A97 /* UserRecord.swift in Sources */, 052258702636100B00B49CE4 /* LaunchViewController.swift in Sources */, 3A150A6726365A9A0052934B /* StatusRecord.swift in Sources */, + 3A90FBB12651CEED00BE4F5D /* TicketTagTableViewCell.swift in Sources */, 5A25CFF1264BE28100852035 /* HasTitle.swift in Sources */, 7D53BE6926433D6600400D0E /* PinnedTicketManager.swift in Sources */, 5ACC0F062630CA2700DCF83E /* Exceptions.swift in Sources */, diff --git a/Sluggo/Components/TicketTagTableViewCell.swift b/Sluggo/Components/TicketTagTableViewCell.swift new file mode 100644 index 0000000..4aba084 --- /dev/null +++ b/Sluggo/Components/TicketTagTableViewCell.swift @@ -0,0 +1,23 @@ +// +// TicketTagTableViewCell.swift +// Sluggo +// +// Created by Stephan Martin on 5/16/21. +// + +import UIKit + +class TicketTagTableViewCell: UITableViewCell { + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization Code + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + super.accessoryType = selected ? .checkmark : .none + // configures the view for the selected state + } +} diff --git a/Sluggo/Storyboards/TicketDetail.storyboard b/Sluggo/Storyboards/TicketDetail.storyboard index d188f26..f484519 100644 --- a/Sluggo/Storyboards/TicketDetail.storyboard +++ b/Sluggo/Storyboards/TicketDetail.storyboard @@ -7,6 +7,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -90,10 +145,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -132,7 +227,7 @@ - + @@ -179,6 +274,7 @@ + @@ -207,6 +303,7 @@ + diff --git a/Sluggo/View Controllers/TicketDetailTableViewController.swift b/Sluggo/View Controllers/TicketDetailTableViewController.swift index 68672f5..24decd5 100644 --- a/Sluggo/View Controllers/TicketDetailTableViewController.swift +++ b/Sluggo/View Controllers/TicketDetailTableViewController.swift @@ -15,6 +15,7 @@ class TicketDetailTableViewController: UITableViewController { @IBOutlet var assignedField: UITextField! @IBOutlet var statusField: UITextField! @IBOutlet var dueDatePicker: UIDatePicker! + @IBOutlet weak var tagLabel: UILabel! @IBOutlet var dueDateSwitch: UISwitch! @IBOutlet var dueDateLabel: UILabel! @IBOutlet var navBar: UINavigationItem! @@ -31,6 +32,7 @@ class TicketDetailTableViewController: UITableViewController { var statuses: [StatusRecord?] = [nil] var currentMember: MemberRecord? var currentStatus: StatusRecord? + var selectedTags: [TagRecord?] = [nil] override func viewDidLoad() { super.viewDidLoad() @@ -57,6 +59,19 @@ class TicketDetailTableViewController: UITableViewController { }) } +// let tagManager = TagManager(identity: self.identity) +// tagManager.listFromTeams(page: 1) {result in +// self.processResult(result: result, onSuccess: { record in +// for tag in record.results { +// self.selectedTags.append(tag) +// } +// }) +// } + + self.selectedTags = ticket!.tag_list + + refreshTagLabel() + pickerView.dataSource = self pickerView.delegate = self @@ -69,6 +84,37 @@ class TicketDetailTableViewController: UITableViewController { } + func launchTagPopup() { + // launch the view controller holding the tag view + if let view = storyboard?.instantiateViewController(identifier: "TagNavigationController") { + if let child = view.children[0] as? TicketTabTableViewController { + child.identity = self.identity + child.selectedTags = self.selectedTags + child.completion = { newTags in + self.selectedTags = newTags + self.refreshTagLabel() + } + } + self.present(view, animated: true, completion: nil) + } + } + + @IBAction func addTagButtonHit(_ sender: Any) { + launchTagPopup() + } + + func refreshTagLabel() { + if selectedTags.count == 0 { + tagLabel.text = "No Tags Selected" + } else if selectedTags.count == 1 { + tagLabel.text = selectedTags[0]?.title + } else if selectedTags.count == 2 { + tagLabel.text = selectedTags[0]!.title + ", " + selectedTags[1]!.title + } else { + tagLabel.text = selectedTags[0]!.title + ", " + selectedTags[1]!.title + ", ..." + } + } + func updateUI() { ticketTitle.text = self.ticket?.title ?? "" @@ -223,8 +269,10 @@ extension TicketDetailTableViewController: UIPickerViewDelegate, UIPickerViewDat func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { if pickerView.tag == 1 { currentMember = teamMembers[row] + // assignedField.text = currentMember?.owner.username ?? "No Assigned User" } else { currentStatus = statuses[row] + // statusField.text = currentStatus?.title ?? "No Status Selected" } } diff --git a/Sluggo/View Controllers/TicketTabTableViewController.swift b/Sluggo/View Controllers/TicketTabTableViewController.swift new file mode 100644 index 0000000..fd97c18 --- /dev/null +++ b/Sluggo/View Controllers/TicketTabTableViewController.swift @@ -0,0 +1,66 @@ +// +// TicketTabTableViewController.swift +// Sluggo +// +// Created by Stephan Martin on 5/16/21. +// + +import UIKit + +class TicketTabTableViewController: UITableViewController { + + var identity: AppIdentity! + var completion: (([TagRecord?]) -> Void)? + var selectedTags: [TagRecord?] = [nil] + + var ticketTags: [TagRecord?] = [] + + override func viewDidLoad() { + super.viewDidLoad() + configureBarItems() + loadData() + } + + func configureBarItems() { + let doneAction = UIAction {_ in + self.dismiss(animated: true) { + self.completion?(self.selectedTags) + } + } + navigationItem.rightBarButtonItem = UIBarButtonItem(systemItem: .done, primaryAction: doneAction) + } + + func loadData() { + + let tagManager = TagManager(identity: self.identity) + tagManager.listFromTeams(page: 1) {result in + self.processResult(result: result, onSuccess: { record in + self.ticketTags = [] + for tag in record.results { + self.ticketTags.append(tag) + print(tag) + } + self.tableView.reloadData() + }) + } + } + + func preselectItems() { + + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + print("I am here. The count is: ") + print(ticketTags.count) + return ticketTags.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt + indexPath: IndexPath) -> UITableViewCell { + + let cell = tableView.dequeueReusableCell(withIdentifier: "TagEntry", for: indexPath) + let text = ticketTags[indexPath.row]!.getTitle() + cell.textLabel?.text = text + return cell + } +} From d5656efe1527cfd1b9d8af2958969c2f2347841d Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Sun, 16 May 2021 18:37:34 -0700 Subject: [PATCH 175/224] Finished some checks with instance URL --- Sluggo/Storyboards/Main.storyboard | 26 +++++++++---------- .../LoginViewController.swift | 16 +++++++++++- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/Sluggo/Storyboards/Main.storyboard b/Sluggo/Storyboards/Main.storyboard index 6fe3c06..71797f2 100644 --- a/Sluggo/Storyboards/Main.storyboard +++ b/Sluggo/Storyboards/Main.storyboard @@ -41,12 +41,6 @@ - - - - - - @@ -58,7 +52,7 @@ - + @@ -73,7 +67,7 @@ - + + + + + + + + + + + + @@ -268,6 +298,7 @@ + @@ -311,5 +342,8 @@ + + + diff --git a/Sluggo/View Controllers/AdminTableViewController.swift b/Sluggo/View Controllers/AdminTableViewController.swift index e7ea6be..b19e52d 100644 --- a/Sluggo/View Controllers/AdminTableViewController.swift +++ b/Sluggo/View Controllers/AdminTableViewController.swift @@ -23,14 +23,14 @@ class AdminTableViewController: UITableViewController { @IBSegueAction func createUsersList(_ coder: NSCoder) -> MemberListViewController? { let memberVC = MemberListViewController(coder: coder, identity: identity) - memberVC?.generateSegueableController = { oldIdentity, record in - if oldIdentity.authenticatedUser?.pk == record?.owner.id { + memberVC?.generateSegueableController = { memberIdentity, record in + if memberIdentity.authenticatedUser?.pk == record?.owner.id { return nil } if let view = self.storyboard?.instantiateViewController(identifier: "AdminRoleNavController") { if let child = view.children[0] as? AdminUserRolesTableViewController { child.member = record - child.identity = oldIdentity + child.identity = memberIdentity return child } } diff --git a/Sluggo/View Controllers/AdminUserRolesTableViewController.swift b/Sluggo/View Controllers/AdminUserRolesTableViewController.swift index 6e859ce..56fe9d8 100644 --- a/Sluggo/View Controllers/AdminUserRolesTableViewController.swift +++ b/Sluggo/View Controllers/AdminUserRolesTableViewController.swift @@ -7,6 +7,12 @@ import UIKit +enum MemberRoleCategories: String { + case admin = "AD" + case approved = "AP" + case invited = "IN"// Invited added for adding users to teams +} + class AdminUserRolesTableViewController: UITableViewController { var identity: AppIdentity! @@ -34,26 +40,26 @@ class AdminUserRolesTableViewController: UITableViewController { } func preSelect() { - if member?.role == "AD" { + if member?.role == MemberRoleCategories.admin.rawValue { let indexPath = IndexPath(row: 1, section: 0) tableView.selectRow(at: indexPath, animated: true, scrollPosition: .bottom) - role = "AD" - initialRole = "AD" + role = MemberRoleCategories.admin.rawValue + initialRole = MemberRoleCategories.admin.rawValue // print("Selected AD") } else { let indexPath = IndexPath(row: 0, section: 0) tableView.selectRow(at: indexPath, animated: true, scrollPosition: .bottom) - role = "AP" - initialRole = "AP" + role = MemberRoleCategories.approved.rawValue + initialRole = MemberRoleCategories.approved.rawValue // print("Selected AP") } } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if indexPath.row == 0 { - role = "AP" + role = MemberRoleCategories.admin.rawValue } else { - role = "AD" + role = MemberRoleCategories.approved.rawValue } } diff --git a/Sluggo/View Controllers/TicketDetailTableViewController.swift b/Sluggo/View Controllers/TicketDetailTableViewController.swift index 86d938b..06b4426 100644 --- a/Sluggo/View Controllers/TicketDetailTableViewController.swift +++ b/Sluggo/View Controllers/TicketDetailTableViewController.swift @@ -21,6 +21,7 @@ class TicketDetailTableViewController: UITableViewController { @IBOutlet var dueDateLabel: UILabel! @IBOutlet var navBar: UINavigationItem! @IBOutlet var rightButton: UIBarButtonItem! + @IBOutlet var deleteButton: UIButton! // MARK: Variables var identity: AppIdentity! @@ -132,7 +133,10 @@ class TicketDetailTableViewController: UITableViewController { dueDatePicker.isHidden = (ticket?.due_date == nil && !editing) dueDateLabel.isHidden = (ticket?.due_date != nil || editing) tagPlusButton.isHidden = !editing - if editing { ticketTitle.becomeFirstResponder() } + tableView.reloadData() + if editing { + ticketTitle.becomeFirstResponder() + } } func doSave() { @@ -194,6 +198,27 @@ class TicketDetailTableViewController: UITableViewController { } } + @IBAction func deleteButtonHit(_ sender: Any) { + let acon = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + acon.addAction(UIAlertAction(title: "Delete Ticket", style: .destructive, handler: doDeleteTicket)) + acon.addAction(UIAlertAction(title: "Cancel", style: .cancel)) + acon.popoverPresentationController?.barButtonItem = self.navigationItem.rightBarButtonItem + present(acon, animated: true) + } + + func doDeleteTicket(action: UIAlertAction) { + let manager = TicketManager(identity) + if let ticket = self.ticket { + manager.deleteTicket(ticket: ticket) { result in + self.processResult(result: result) { _ in + DispatchQueue.main.async { + NotificationCenter.default.post(name: .refreshTrigger, object: self) + self.dismiss(animated: true, completion: nil) + } + } + } + } + } } extension TicketDetailTableViewController: UIPickerViewDelegate, UIPickerViewDataSource { @@ -260,3 +285,15 @@ extension TicketDetailTableViewController: UIPickerViewDelegate, UIPickerViewDat } } + +extension TicketDetailTableViewController { + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + switch section { + case 6: + return (self.editingTicket && self.ticket != nil) ? 1 : 0 + default: + return 1 + } + } +} diff --git a/Sluggo/View Controllers/TicketTabTableViewController.swift b/Sluggo/View Controllers/TicketTabTableViewController.swift index 780e9b0..c3f5887 100644 --- a/Sluggo/View Controllers/TicketTabTableViewController.swift +++ b/Sluggo/View Controllers/TicketTabTableViewController.swift @@ -72,11 +72,10 @@ class TicketTabTableViewController: UITableViewController { } override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { - for (count, selected) in selectedTags.enumerated() { - if selected.object_uuid == ticketTags[indexPath.row].object_uuid { - selectedTags.remove(at: count) - break - } + for (count, selected) in selectedTags.enumerated() + where selected.object_uuid == ticketTags[indexPath.row].object_uuid { + selectedTags.remove(at: count) + break } } From 95baf02e501c13bd10e98d70db653d98bf5fff25 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Sat, 22 May 2021 00:03:28 -0700 Subject: [PATCH 184/224] remake initial VC. --- Sluggo/Storyboards/TicketDetail.storyboard | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sluggo/Storyboards/TicketDetail.storyboard b/Sluggo/Storyboards/TicketDetail.storyboard index 045bcb0..44f80ab 100644 --- a/Sluggo/Storyboards/TicketDetail.storyboard +++ b/Sluggo/Storyboards/TicketDetail.storyboard @@ -1,5 +1,5 @@ - + From c036c0addefc52591a7d62bacc943516c30b3a42 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Mon, 24 May 2021 19:18:33 -0700 Subject: [PATCH 185/224] Finished Delete implementation for tickets. --- Sluggo/Models/JsonLoader.swift | 28 +++++++++++++++++++ Sluggo/Models/Managers/TicketManager.swift | 4 +-- .../TicketDetailTableViewController.swift | 2 +- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/Sluggo/Models/JsonLoader.swift b/Sluggo/Models/JsonLoader.swift index 1339f8c..b165e0b 100644 --- a/Sluggo/Models/JsonLoader.swift +++ b/Sluggo/Models/JsonLoader.swift @@ -86,4 +86,32 @@ class JsonLoader { } }).resume() } + + static func executeEmptyRequest(request: URLRequest, + completionHandler: @escaping (Result) -> Void) { + + let session = URLSession.shared + session.dataTask(with: request, completionHandler: { data, response, error -> Void in + if error != nil { + completionHandler(.failure(Exception.runtimeError(message: "Server Error!"))) + return + } + // swiftlint:disable:next force_cast + let resp = response as! HTTPURLResponse + if resp.statusCode <= 299 && resp.statusCode >= 200 { + completionHandler(.success(())) + return + } else { + if let fetchedData = data { + let fetchedString = String(data: fetchedData, encoding: .utf8) ?? "A parsing error occurred" + let errorMessage = "HTTP Error \(resp.statusCode): \(fetchedString)" + completionHandler(.failure(RESTException.failedRequest(message: errorMessage))) + return + } + let errorMessage = "HTTP Error \(resp.statusCode): An unknown error occured." + completionHandler(.failure(RESTException.failedRequest(message: errorMessage))) + return + } + }).resume() + } } diff --git a/Sluggo/Models/Managers/TicketManager.swift b/Sluggo/Models/Managers/TicketManager.swift index 0baaff5..5db9b10 100644 --- a/Sluggo/Models/Managers/TicketManager.swift +++ b/Sluggo/Models/Managers/TicketManager.swift @@ -87,13 +87,13 @@ class TicketManager { JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } - public func deleteTicket(ticket: TicketRecord, completionHandler: @escaping(Result) -> Void) { + public func deleteTicket(ticket: TicketRecord, completionHandler: @escaping(Result) -> Void) { let requestBuilder = URLRequestBuilder(url: makeDetailUrl(ticket)) .setMethod(method: .DELETE) .setIdentity(identity: self.identity) - JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) + JsonLoader.executeEmptyRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } } diff --git a/Sluggo/View Controllers/TicketDetailTableViewController.swift b/Sluggo/View Controllers/TicketDetailTableViewController.swift index 06b4426..9661d6e 100644 --- a/Sluggo/View Controllers/TicketDetailTableViewController.swift +++ b/Sluggo/View Controllers/TicketDetailTableViewController.swift @@ -213,7 +213,7 @@ class TicketDetailTableViewController: UITableViewController { self.processResult(result: result) { _ in DispatchQueue.main.async { NotificationCenter.default.post(name: .refreshTrigger, object: self) - self.dismiss(animated: true, completion: nil) + self.navigationController?.popViewController(animated: false) } } } From b58b983d6398a5255277902dc5bd79f131e3c1b1 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Mon, 24 May 2021 20:13:09 -0700 Subject: [PATCH 186/224] minor change --- Sluggo/View Controllers/TicketDetailTableViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sluggo/View Controllers/TicketDetailTableViewController.swift b/Sluggo/View Controllers/TicketDetailTableViewController.swift index 9661d6e..02a37a1 100644 --- a/Sluggo/View Controllers/TicketDetailTableViewController.swift +++ b/Sluggo/View Controllers/TicketDetailTableViewController.swift @@ -212,6 +212,7 @@ class TicketDetailTableViewController: UITableViewController { manager.deleteTicket(ticket: ticket) { result in self.processResult(result: result) { _ in DispatchQueue.main.async { + self.setEditMode(false) NotificationCenter.default.post(name: .refreshTrigger, object: self) self.navigationController?.popViewController(animated: false) } From ab7cbb898c207fad463d4614e5ac667f170b7e1b Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Wed, 26 May 2021 16:51:17 -0700 Subject: [PATCH 187/224] Pinned tickets work. --- .../Models/Codables/PinnedTicketRecord.swift | 2 +- .../Models/Managers/PinnedTicketManager.swift | 37 ++++++++++++--- Sluggo/Storyboards/TicketDetail.storyboard | 30 ++++++++++++- .../TicketDetailTableViewController.swift | 45 +++++++++++++++++++ 4 files changed, 105 insertions(+), 9 deletions(-) diff --git a/Sluggo/Models/Codables/PinnedTicketRecord.swift b/Sluggo/Models/Codables/PinnedTicketRecord.swift index d94a27b..860629c 100644 --- a/Sluggo/Models/Codables/PinnedTicketRecord.swift +++ b/Sluggo/Models/Codables/PinnedTicketRecord.swift @@ -15,5 +15,5 @@ struct PinnedTicketRecord: Codable { } struct WritePinnedTicketRecord: Codable { - let ticketID: Int + let ticket: Int } diff --git a/Sluggo/Models/Managers/PinnedTicketManager.swift b/Sluggo/Models/Managers/PinnedTicketManager.swift index e06161b..b44cb52 100644 --- a/Sluggo/Models/Managers/PinnedTicketManager.swift +++ b/Sluggo/Models/Managers/PinnedTicketManager.swift @@ -6,6 +6,7 @@ // import Foundation +import CryptoKit class PinnedTicketManager { static let urlBase = "/pinned_tickets/" @@ -24,7 +25,7 @@ class PinnedTicketManager { } private func makeDetailURL(desiredID: String) -> URL { - return URL(string: makeListURL().absoluteString + "/\(desiredID)/")! + return URL(string: makeListURL().absoluteString + "\(desiredID)/")! } public func fetchPinnedTickets(completionHandler: @escaping(Result<[PinnedTicketRecord], Error>) -> Void) { @@ -35,11 +36,10 @@ class PinnedTicketManager { JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } - public func postPinnedTicket(member: MemberRecord, - ticket: TicketRecord, + public func postPinnedTicket(ticket: TicketRecord, completionHandler: @escaping(Result) -> Void) { - let writeRecord = WritePinnedTicketRecord(ticketID: ticket.id) + let writeRecord = WritePinnedTicketRecord(ticket: ticket.id) guard let body = JsonLoader.encode(object: writeRecord) else { let errorMessage = "Failed to serialize write pinned ticket record in PinnedTicketManager" @@ -55,14 +55,37 @@ class PinnedTicketManager { JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } - public func deletePinnedTicket(member: MemberRecord, - pinned: PinnedTicketRecord, - completionHandler: @escaping(Result) -> Void) { + public func deletePinnedTicket(pinned: PinnedTicketRecord, + completionHandler: @escaping(Result) -> Void) { let requestBuilder = URLRequestBuilder(url: makeDetailURL(desiredID: pinned.id)) .setMethod(method: .DELETE) .setIdentity(identity: identity) + JsonLoader.executeEmptyRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) + } + + public func fetchPinned(ticket: TicketRecord, + completionHandler: @escaping(Result) -> Void) { + + // MARK: MD5 hashing convenience + func MD5String(for str: String) -> String { + let digest = Insecure.MD5.hash(data: str.data(using: .utf8)!) + + return digest.map { + String(format: "%02hhx", $0) + }.joined() + } + + // Calculate the member PK + let memberHash = MD5String(for: String(member.id)) + let ticketHash = MD5String(for: String(ticket.id)) + + let pinnedID = memberHash.appending(ticketHash) + let requestBuilder = URLRequestBuilder(url: makeDetailURL(desiredID: pinnedID)) + .setMethod(method: .GET) + .setIdentity(identity: identity) + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } } diff --git a/Sluggo/Storyboards/TicketDetail.storyboard b/Sluggo/Storyboards/TicketDetail.storyboard index 44f80ab..09f8314 100644 --- a/Sluggo/Storyboards/TicketDetail.storyboard +++ b/Sluggo/Storyboards/TicketDetail.storyboard @@ -252,10 +252,37 @@ + + + + + + + + + + + + + + + + + + + + + - + @@ -303,6 +330,7 @@ + diff --git a/Sluggo/View Controllers/TicketDetailTableViewController.swift b/Sluggo/View Controllers/TicketDetailTableViewController.swift index 02a37a1..c584fe0 100644 --- a/Sluggo/View Controllers/TicketDetailTableViewController.swift +++ b/Sluggo/View Controllers/TicketDetailTableViewController.swift @@ -22,10 +22,13 @@ class TicketDetailTableViewController: UITableViewController { @IBOutlet var navBar: UINavigationItem! @IBOutlet var rightButton: UIBarButtonItem! @IBOutlet var deleteButton: UIButton! + @IBOutlet var pinTicketButton: UIButton! // MARK: Variables var identity: AppIdentity! + var member: MemberRecord! var ticket: TicketRecord? + var pinnedTicket: PinnedTicketRecord? var pickerView: UIPickerView = UIPickerView() var statusPicker: UIPickerView = UIPickerView() var editingTicket = false @@ -46,6 +49,20 @@ class TicketDetailTableViewController: UITableViewController { self.processResult(result: result, onSuccess: { record in self.teamMembers = [nil] self.teamMembers += record.results + self.member = self.teamMembers.filter { $0?.owner.id == self.identity.authenticatedUser?.pk }[0] + if let ticket = self.ticket { + let pinnedManager = PinnedTicketManager(identity: self.identity, member: self.member) + pinnedManager.fetchPinned(ticket: ticket) { result in + self.processResult(result: result, onSuccess: { record in + self.pinnedTicket = record + }, onError: { _ in + self.pinnedTicket = nil + }, after: { + let title = self.pinnedTicket != nil ? "Un-pin Ticket" : "Pin Ticket" + self.pinTicketButton.setTitle(title, for: .normal) + }) + } + } }) } @@ -220,6 +237,32 @@ class TicketDetailTableViewController: UITableViewController { } } } + @IBAction func pinTicketHit(_ sender: Any) { + + let manager = PinnedTicketManager(identity: identity, member: self.member) + if let pinned = pinnedTicket { + manager.deletePinnedTicket(pinned: pinned) { result in + self.processResult(result: result) { _ in + DispatchQueue.main.async { + NotificationCenter.default.post(name: .refreshTrigger, object: self) + self.pinTicketButton.setTitle("Pin Ticket", for: .normal) + self.pinnedTicket = nil + } + } + + } + } else { + manager.postPinnedTicket(ticket: self.ticket!) { result in + self.processResult(result: result) { record in + DispatchQueue.main.async { + NotificationCenter.default.post(name: .refreshTrigger, object: self) + self.pinTicketButton.setTitle("Un-pin Ticket", for: .normal) + self.pinnedTicket = record + } + } + } + } + } } extension TicketDetailTableViewController: UIPickerViewDelegate, UIPickerViewDataSource { @@ -292,6 +335,8 @@ extension TicketDetailTableViewController { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { switch section { case 6: + return self.ticket != nil ? 1 : 0 + case 7: return (self.editingTicket && self.ticket != nil) ? 1 : 0 default: return 1 From 9b32a91d8c9d00c62e763f364f0e7a48ebc47d5b Mon Sep 17 00:00:00 2001 From: Stephan Martin Date: Wed, 26 May 2021 22:42:01 -0700 Subject: [PATCH 188/224] Ticket tag creation is done, ticket tag deletion still needing to be done. --- Sluggo.xcodeproj/project.pbxproj | 8 + Sluggo/Models/Codables/TagRecord.swift | 4 + Sluggo/Models/Managers/TagManager.swift | 16 ++ Sluggo/Storyboards/Admin/Admin.storyboard | 37 +++++ Sluggo/Storyboards/Admin/AdminTag.storyboard | 139 ++++++++++++++++++ .../AdminTableViewController.swift | 4 + .../AdminTagViewController.swift | 115 +++++++++++++++ 7 files changed, 323 insertions(+) create mode 100644 Sluggo/Storyboards/Admin/AdminTag.storyboard create mode 100644 Sluggo/View Controllers/AdminTagViewController.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 56ecece..47e7ffb 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -27,6 +27,8 @@ 3A90FBB12651CEED00BE4F5D /* TicketTagTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A90FBB02651CEED00BE4F5D /* TicketTagTableViewCell.swift */; }; 3A90FBB32651D76900BE4F5D /* TicketTabTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A90FBB22651D76900BE4F5D /* TicketTabTableViewController.swift */; }; 3A90FBB526531AC600BE4F5D /* AdminUserRolesTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A90FBB426531AC600BE4F5D /* AdminUserRolesTableViewController.swift */; }; + 3A90FBB9265F4E7900BE4F5D /* AdminTag.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3A90FBB8265F4E7900BE4F5D /* AdminTag.storyboard */; }; + 3A90FBBB265F4FAB00BE4F5D /* AdminTagViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A90FBBA265F4FAB00BE4F5D /* AdminTagViewController.swift */; }; 5A00A0142639EDEA0085C8C6 /* URLRequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */; }; 5A00A045263A34CC0085C8C6 /* TicketTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A00A044263A34CC0085C8C6 /* TicketTableViewCell.swift */; }; 5A25CFA2264B8C8100852035 /* TagTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A25CFA1264B8C8100852035 /* TagTests.swift */; }; @@ -114,6 +116,8 @@ 3A90FBB02651CEED00BE4F5D /* TicketTagTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketTagTableViewCell.swift; sourceTree = ""; }; 3A90FBB22651D76900BE4F5D /* TicketTabTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketTabTableViewController.swift; sourceTree = ""; }; 3A90FBB426531AC600BE4F5D /* AdminUserRolesTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminUserRolesTableViewController.swift; sourceTree = ""; }; + 3A90FBB8265F4E7900BE4F5D /* AdminTag.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = AdminTag.storyboard; sourceTree = ""; }; + 3A90FBBA265F4FAB00BE4F5D /* AdminTagViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminTagViewController.swift; sourceTree = ""; }; 5A00A0132639EDEA0085C8C6 /* URLRequestBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLRequestBuilder.swift; sourceTree = ""; }; 5A00A044263A34CC0085C8C6 /* TicketTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketTableViewCell.swift; sourceTree = ""; }; 5A25CFA1264B8C8100852035 /* TagTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagTests.swift; sourceTree = ""; }; @@ -276,6 +280,7 @@ isa = PBXGroup; children = ( 7D01B55B264FAAEE0009FA97 /* Admin.storyboard */, + 3A90FBB8265F4E7900BE4F5D /* AdminTag.storyboard */, ); path = Admin; sourceTree = ""; @@ -380,6 +385,7 @@ 05DE0CD6264F11E400D69C61 /* MemberListViewController.swift */, 7D01B55D264FADC40009FA97 /* AdminTableViewController.swift */, 3A90FBB426531AC600BE4F5D /* AdminUserRolesTableViewController.swift */, + 3A90FBBA265F4FAB00BE4F5D /* AdminTagViewController.swift */, ); path = "View Controllers"; sourceTree = ""; @@ -495,6 +501,7 @@ buildActionMask = 2147483647; files = ( 7D9963B2261EF3A5002338E7 /* Assets.xcassets in Resources */, + 3A90FBB9265F4E7900BE4F5D /* AdminTag.storyboard in Resources */, 5A9FF88926386D1400378550 /* Ticket.storyboard in Resources */, 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */, 05588A12264DB61E000D8674 /* TeamMembers.storyboard in Resources */, @@ -565,6 +572,7 @@ 7DC54AE526437AB3000C8300 /* PinnedTicketRecord.swift in Sources */, 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */, 2FF89024264BBAC600D50B69 /* TicketDetailTableViewController.swift in Sources */, + 3A90FBBB265F4FAB00BE4F5D /* AdminTagViewController.swift in Sources */, 5AC6459026412F0C00F46137 /* TeamTableViewCell.swift in Sources */, 051AAB0B2630FD6500BCB9C7 /* Constants.swift in Sources */, 2F3F66E2262DD3BA0040404C /* JsonLoader.swift in Sources */, diff --git a/Sluggo/Models/Codables/TagRecord.swift b/Sluggo/Models/Codables/TagRecord.swift index 7072b11..c8a9d48 100644 --- a/Sluggo/Models/Codables/TagRecord.swift +++ b/Sluggo/Models/Codables/TagRecord.swift @@ -22,3 +22,7 @@ struct TagRecord: Codable, HasTitle { return title } } + +struct WriteTagRecord: Codable { + var title: String +} diff --git a/Sluggo/Models/Managers/TagManager.swift b/Sluggo/Models/Managers/TagManager.swift index 3e9074d..0ad2e08 100644 --- a/Sluggo/Models/Managers/TagManager.swift +++ b/Sluggo/Models/Managers/TagManager.swift @@ -36,4 +36,20 @@ class TagManager: TeamPaginatedListable { JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } + public func makeTag(tag: WriteTagRecord, + completionHandler: @escaping(Result) -> Void) { + guard let body = JsonLoader.encode(object: tag) else { + let errorMessage = "Failed to serialize tag JSON for makeTag in TagManager" + completionHandler(.failure(Exception.runtimeError(message: errorMessage))) + return + } + + let requestBuilder = URLRequestBuilder(url: makeListUrl(page: 1)) + .setMethod(method: .POST) + .setData(data: body) + .setIdentity(identity: self.identity) + + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) + } + } diff --git a/Sluggo/Storyboards/Admin/Admin.storyboard b/Sluggo/Storyboards/Admin/Admin.storyboard index 3418b0f..f524f6c 100644 --- a/Sluggo/Storyboards/Admin/Admin.storyboard +++ b/Sluggo/Storyboards/Admin/Admin.storyboard @@ -45,6 +45,33 @@ + + + + + + + + + + + + + + + @@ -136,6 +163,16 @@ + + + + + + + + + + diff --git a/Sluggo/Storyboards/Admin/AdminTag.storyboard b/Sluggo/Storyboards/Admin/AdminTag.storyboard new file mode 100644 index 0000000..2f9cd14 --- /dev/null +++ b/Sluggo/Storyboards/Admin/AdminTag.storyboard @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sluggo/View Controllers/AdminTableViewController.swift b/Sluggo/View Controllers/AdminTableViewController.swift index b19e52d..772d40c 100644 --- a/Sluggo/View Controllers/AdminTableViewController.swift +++ b/Sluggo/View Controllers/AdminTableViewController.swift @@ -42,4 +42,8 @@ class AdminTableViewController: UITableViewController { return memberVC } + @IBSegueAction func createTagsList(_ coder: NSCoder) -> AdminTagViewController? { + let view = AdminTagViewController(coder: coder, identity: identity) + return view + } } diff --git a/Sluggo/View Controllers/AdminTagViewController.swift b/Sluggo/View Controllers/AdminTagViewController.swift new file mode 100644 index 0000000..81b2bd6 --- /dev/null +++ b/Sluggo/View Controllers/AdminTagViewController.swift @@ -0,0 +1,115 @@ +// +// AdminTagViewController.swift +// Sluggo +// +// Created by Stephan Martin on 5/26/21. +// + +import UIKit + +class AdminTagViewController: UITableViewController { + var identity: AppIdentity! + private var tags: [TagRecord] = [] + private var isFetching = false + private var semaphore = DispatchSemaphore(value: 1) + + required init? (coder: NSCoder, identity: AppIdentity) { + self.identity = identity + super.init(coder: coder) + } + + required init? (coder: NSCoder) { + fatalError("Must be initialized with identity") + } + + override func viewDidLoad() { + self.configureRefreshControl() + self.handleRefreshAction() + NotificationCenter.default.addObserver(self, selector: #selector(handleRefreshAction), + name: .refreshTags, object: nil) + + } + + func configureRefreshControl() { + refreshControl = UIRefreshControl() + refreshControl?.addTarget(self, action: #selector(handleRefreshAction), for: .valueChanged) + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return tags.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "TagCell", for: indexPath) as UITableViewCell + let tag = self.tags[indexPath.row] + cell.textLabel?.text = tag.title + cell.detailTextLabel?.text = "" + + return cell + } + + @IBSegueAction func segueToTagCreate(_ coder: NSCoder) -> AdminTagCreateViewController? { + let view = AdminTagCreateViewController(coder: coder, identity: identity) + return view + } + + @objc func handleRefreshAction() { + + DispatchQueue.global(qos: .userInitiated).async { + self.semaphore.wait() + + let onSuccess = { (tags: [TagRecord]) -> Void in + self.tags = tags + } + + let after = { () -> Void in + DispatchQueue.main.async { + self.refreshControl?.endRefreshing() + self.tableView.reloadData() + } + self.semaphore.signal() + } + + let tagManager = TagManager(identity: self.identity) + unwindPagination(manager: tagManager, startingPage: 1, onSuccess: onSuccess, + onFailure: self.presentErrorFromMainThread, after: after) + } + + } +} + +extension Notification.Name { + static let refreshTags = Notification.Name(rawValue: "SLGRefreshTagsNotification") +} + +class AdminTagCreateViewController: UITableViewController { + var identity: AppIdentity! + @IBOutlet weak var tagTitle: UITextField! + + required init? (coder: NSCoder, identity: AppIdentity) { + self.identity = identity + super.init(coder: coder) + } + + required init? (coder: NSCoder) { + fatalError("Must be initialized with identity") + } + + override func viewDidLoad() { + super.viewDidLoad() + } + + @IBAction func doneButtonClicked(_ sender: Any) { + let title = tagTitle.text ?? "Default Tag Title" + let manager = TagManager(identity: identity) + let tag = WriteTagRecord(title: title) + manager.makeTag(tag: tag) { result in + self.processResult(result: result) { _ in + DispatchQueue.main.async { + NotificationCenter.default.post(name: .refreshTags, object: self) + self.navigationController?.popViewController(animated: true) + } + } + } + } +} From 70d8d0fd581c87cdaa8c133174583e6fb7153f5b Mon Sep 17 00:00:00 2001 From: Stephan Martin Date: Wed, 26 May 2021 22:49:05 -0700 Subject: [PATCH 189/224] Fixed linter issue with nav controller not have a storyboard ID --- Sluggo/Storyboards/Admin/AdminTag.storyboard | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sluggo/Storyboards/Admin/AdminTag.storyboard b/Sluggo/Storyboards/Admin/AdminTag.storyboard index 2f9cd14..7892b2b 100644 --- a/Sluggo/Storyboards/Admin/AdminTag.storyboard +++ b/Sluggo/Storyboards/Admin/AdminTag.storyboard @@ -116,7 +116,7 @@ - + From e8c2e8a3b701461fbe60252aa3a59648ada297dd Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Wed, 26 May 2021 23:30:35 -0700 Subject: [PATCH 190/224] Block sidebar presentation based on navigation controller VC count --- .../View Controllers/RootViewController.swift | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/Sluggo/View Controllers/RootViewController.swift b/Sluggo/View Controllers/RootViewController.swift index 56aaf7a..f783749 100644 --- a/Sluggo/View Controllers/RootViewController.swift +++ b/Sluggo/View Controllers/RootViewController.swift @@ -140,9 +140,15 @@ class RootViewController: UIViewController { } @IBAction func receivedGesture() { - NotificationCenter.default.post(name: .onSidebarTrigger, - object: self, userInfo: [Sidebar.USER_INFO_KEY: SidebarStatus.open]) + // Determine if feasible here + let shouldPresentSidebar = determineIfPresenting() + + if shouldPresentSidebar { + NotificationCenter.default.post(name: .onSidebarTrigger, + object: self, userInfo: [Sidebar.USER_INFO_KEY: SidebarStatus.open]) + } } + @IBAction func receieveLeft() { NotificationCenter.default.post(name: .onSidebarTrigger, object: self, userInfo: [Sidebar.USER_INFO_KEY: SidebarStatus.closed]) @@ -150,4 +156,38 @@ class RootViewController: UIViewController { @IBSegueAction func showSidebar(_ coder: NSCoder) -> UIViewController? { return SluggoSidebarContainerViewController(coder: coder, identity: self.identity) } + + private func determineIfPresenting() -> Bool { + /* + * Some notes on the below implementation: + * We determine if we can present based on the number of VCs in the + * navigation stack of each controller. This is acceptable, because + * navigation presentation is susceptible to still recieving the + * sidebar gestures. + * + * Modal presentation is *not* susceptible to this, and so does not + * need to be accounted for here. + */ + guard let tabBarControllerVCs = mainTabBarController?.viewControllers + else { return false } // return false if something goes wrong in determining + + var shouldPresentSidebar = true + + for tabVC in tabBarControllerVCs { + if let navVC = tabVC as? UINavigationController { + if navVC.viewControllers.count > 1 { + // More than one view controller present + print(navVC.viewControllers) + shouldPresentSidebar = false + break // save additional cycles + } + } else { + print("RootViewController: Error: Root view controller on" + + " tab bar controller was not a navigation controller") + continue + } + } + + return shouldPresentSidebar + } } From e03306baaf5690d7e2a4f02b8cdca8ba7c7f1dc0 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Thu, 27 May 2021 01:31:45 -0700 Subject: [PATCH 191/224] First pass at log out functionality --- Sluggo.xcodeproj/project.pbxproj | 4 +++ Sluggo/AppDelegate.swift | 2 +- Sluggo/Storyboards/Sidebar.storyboard | 8 +++++- .../Extensions/UIWindowExtensions.swift | 17 ++++++++++++ .../View Controllers/RootViewController.swift | 27 +++++++++++++++++-- ...SluggoSidebarContainerViewController.swift | 2 ++ .../TeamTableViewController.swift | 6 +++++ 7 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 Sluggo/View Controllers/Extensions/UIWindowExtensions.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 56ecece..fbb4367 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -74,6 +74,7 @@ 7DC2A227262948800002CEE6 /* SluggoSidebarContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */; }; 7DC54AE526437AB3000C8300 /* PinnedTicketRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC54AE426437AB3000C8300 /* PinnedTicketRecord.swift */; }; 7DEA17852639E7B5004E5A71 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DEA17842639E7B5004E5A71 /* LaunchScreen.storyboard */; }; + 7DF6CBA5265F8D620013CAEA /* UIWindowExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DF6CBA4265F8D620013CAEA /* UIWindowExtensions.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -166,6 +167,7 @@ 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SluggoSidebarContainerViewController.swift; sourceTree = ""; }; 7DC54AE426437AB3000C8300 /* PinnedTicketRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedTicketRecord.swift; sourceTree = ""; }; 7DEA17842639E7B5004E5A71 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + 7DF6CBA4265F8D620013CAEA /* UIWindowExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIWindowExtensions.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -284,6 +286,7 @@ isa = PBXGroup; children = ( 7D778C05264A374C00D4061A /* ViewControllerExtensions.swift */, + 7DF6CBA4265F8D620013CAEA /* UIWindowExtensions.swift */, ); path = Extensions; sourceTree = ""; @@ -575,6 +578,7 @@ 052258702636100B00B49CE4 /* LaunchViewController.swift in Sources */, 3A150A6726365A9A0052934B /* StatusRecord.swift in Sources */, 3A90FBB12651CEED00BE4F5D /* TicketTagTableViewCell.swift in Sources */, + 7DF6CBA5265F8D620013CAEA /* UIWindowExtensions.swift in Sources */, 5A25CFF1264BE28100852035 /* HasTitle.swift in Sources */, 7D01B55E264FADC40009FA97 /* AdminTableViewController.swift in Sources */, 7D53BE6926433D6600400D0E /* PinnedTicketManager.swift in Sources */, diff --git a/Sluggo/AppDelegate.swift b/Sluggo/AppDelegate.swift index dc669c1..39ef3ed 100644 --- a/Sluggo/AppDelegate.swift +++ b/Sluggo/AppDelegate.swift @@ -19,7 +19,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } // https://stackoverflow.com/questions/33714671/value-of-type-appdelegate-has-no-member-window - private func configureInitialViewController() { + func configureInitialViewController() { let storyboard = UIStoryboard(name: "Main", bundle: nil) let initialViewController: UIViewController diff --git a/Sluggo/Storyboards/Sidebar.storyboard b/Sluggo/Storyboards/Sidebar.storyboard index 90d4d02..d9bde71 100644 --- a/Sluggo/Storyboards/Sidebar.storyboard +++ b/Sluggo/Storyboards/Sidebar.storyboard @@ -40,7 +40,13 @@ - + + + + + + + diff --git a/Sluggo/View Controllers/Extensions/UIWindowExtensions.swift b/Sluggo/View Controllers/Extensions/UIWindowExtensions.swift new file mode 100644 index 0000000..a15fda7 --- /dev/null +++ b/Sluggo/View Controllers/Extensions/UIWindowExtensions.swift @@ -0,0 +1,17 @@ +// +// UIWindowExtensions.swift +// Sluggo +// +// Created by Isaac Trimble-Pederson on 5/27/21. +// + +import UIKit + +// Obtained from Stack Overflow here https://stackoverflow.com/questions/4544489/how-to-remove-a-uiwindow +// Not accounting for pre-iOS 12 because we are targeting latest +extension UIWindow { + func dismiss() { + isHidden = true + windowScene = nil + } +} diff --git a/Sluggo/View Controllers/RootViewController.swift b/Sluggo/View Controllers/RootViewController.swift index f783749..6c16ced 100644 --- a/Sluggo/View Controllers/RootViewController.swift +++ b/Sluggo/View Controllers/RootViewController.swift @@ -60,6 +60,27 @@ class RootViewController: UIViewController { getMember(completionHandler: updateAdminAttachmentForMemberRole) } + func logOutAction() { + // This needs to be done in a specific order to avoid nil exceptions + + // First, dismiss the main view controller and sidebar + // Assume we are in the key window + let keyWindow = UIApplication.shared.keyWindow + keyWindow?.dismiss() + + // Hopefully the user cannot access anything now + // We don't do background calls to API so this *shouldn't* crash + // Remove AppIdentity persistence file + let identityClearingSuccess = AppIdentity.deletePersistenceFile() + + // After this is done, make a call to reconfigure + guard let application = UIApplication.shared.delegate as? AppDelegate else { + print("FUCK") + return + } + application.configureInitialViewController() + } + func getMember(completionHandler: @escaping ((MemberRecord) -> Void)) { let memberManager = MemberManager(identity: identity) memberManager.getMemberRecord(user: identity.authenticatedUser!, identity: identity) { result in @@ -68,7 +89,6 @@ class RootViewController: UIViewController { completionHandler(member) case .failure: // Silently fails, for now - // TODO: is there a better approach? print("Getting the member failed.") } } @@ -154,7 +174,10 @@ class RootViewController: UIViewController { object: self, userInfo: [Sidebar.USER_INFO_KEY: SidebarStatus.closed]) } @IBSegueAction func showSidebar(_ coder: NSCoder) -> UIViewController? { - return SluggoSidebarContainerViewController(coder: coder, identity: self.identity) + let sidebarContainerVC = SluggoSidebarContainerViewController(coder: coder, identity: self.identity) + sidebarContainerVC?.logOutAction = self.logOutAction + + return sidebarContainerVC } private func determineIfPresenting() -> Bool { diff --git a/Sluggo/View Controllers/SluggoSidebarContainerViewController.swift b/Sluggo/View Controllers/SluggoSidebarContainerViewController.swift index c2ac7bb..e0f0fe2 100644 --- a/Sluggo/View Controllers/SluggoSidebarContainerViewController.swift +++ b/Sluggo/View Controllers/SluggoSidebarContainerViewController.swift @@ -15,6 +15,7 @@ class SluggoSidebarContainerViewController: UIViewController { @IBOutlet weak var sidebarContainerLeadingConstraint: NSLayoutConstraint! private var identity: AppIdentity + var logOutAction: (() -> Void)? init? (coder: NSCoder, identity: AppIdentity) { self.identity = identity @@ -100,6 +101,7 @@ class SluggoSidebarContainerViewController: UIViewController { @IBSegueAction func launchSidebar(_ coder: NSCoder) -> UITableViewController? { let view = TeamTableViewController(coder: coder) view?.identity = self.identity + view?.logOutAction = self.logOutAction view?.completion = { team in self.identity.team = team NotificationCenter.default.post(name: Constants.Signals.TEAM_CHANGE_NOTIFICATION, object: nil) diff --git a/Sluggo/View Controllers/TeamTableViewController.swift b/Sluggo/View Controllers/TeamTableViewController.swift index 0cdacb9..aadb1e5 100644 --- a/Sluggo/View Controllers/TeamTableViewController.swift +++ b/Sluggo/View Controllers/TeamTableViewController.swift @@ -10,6 +10,7 @@ import UIKit class TeamTableViewController: UITableViewController { var identity: AppIdentity! var completion: ((TeamRecord) -> Void)? + var logOutAction: (() -> Void)? private var maxNumber: Int = 0 private var fetchingTeams: [TeamRecord] = [] private var teams: [TeamRecord] = [] @@ -82,4 +83,9 @@ class TeamTableViewController: UITableViewController { after: after) } } + + @IBAction func doLogOutAction(_ sender: Any) { + self.logOutAction?() + } + } From 9cf141a2078ec4d0f228efc6ef47b6b567a2cbfc Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Thu, 27 May 2021 01:40:17 -0700 Subject: [PATCH 192/224] Animation fix --- .../View Controllers/RootViewController.swift | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/Sluggo/View Controllers/RootViewController.swift b/Sluggo/View Controllers/RootViewController.swift index 6c16ced..752aac9 100644 --- a/Sluggo/View Controllers/RootViewController.swift +++ b/Sluggo/View Controllers/RootViewController.swift @@ -62,23 +62,24 @@ class RootViewController: UIViewController { func logOutAction() { // This needs to be done in a specific order to avoid nil exceptions - // First, dismiss the main view controller and sidebar - // Assume we are in the key window - let keyWindow = UIApplication.shared.keyWindow - keyWindow?.dismiss() - - // Hopefully the user cannot access anything now - // We don't do background calls to API so this *shouldn't* crash - // Remove AppIdentity persistence file - let identityClearingSuccess = AppIdentity.deletePersistenceFile() - - // After this is done, make a call to reconfigure - guard let application = UIApplication.shared.delegate as? AppDelegate else { - print("FUCK") - return - } - application.configureInitialViewController() + self.mainTabBarController?.dismiss(animated: true, completion: { + // Assume we are in the key window + let keyWindow = UIApplication.shared.keyWindow + keyWindow?.dismiss() + + // Hopefully the user cannot access anything now + // We don't do background calls to API so this *shouldn't* crash + // Remove AppIdentity persistence file + let identityClearingSuccess = AppIdentity.deletePersistenceFile() + + // After this is done, make a call to reconfigure + guard let application = UIApplication.shared.delegate as? AppDelegate else { + print("FUCK") + return + } + application.configureInitialViewController() + }) } func getMember(completionHandler: @escaping ((MemberRecord) -> Void)) { From 2d8e4a57b54f9c4678a1ed7f23a764c0006736b3 Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Thu, 27 May 2021 01:52:13 -0700 Subject: [PATCH 193/224] Add stand-in window to prevent black flash when logging out --- Sluggo.xcodeproj/project.pbxproj | 4 ++ Sluggo/AppDelegate.swift | 5 +++ Sluggo/Storyboards/TitleScreen.storyboard | 44 +++++++++++++++++++ .../View Controllers/RootViewController.swift | 16 ++++++- 4 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 Sluggo/Storyboards/TitleScreen.storyboard diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index fbb4367..170d909 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -75,6 +75,7 @@ 7DC54AE526437AB3000C8300 /* PinnedTicketRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC54AE426437AB3000C8300 /* PinnedTicketRecord.swift */; }; 7DEA17852639E7B5004E5A71 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DEA17842639E7B5004E5A71 /* LaunchScreen.storyboard */; }; 7DF6CBA5265F8D620013CAEA /* UIWindowExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DF6CBA4265F8D620013CAEA /* UIWindowExtensions.swift */; }; + 7DF6CBA7265F93A90013CAEA /* TitleScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DF6CBA6265F93A90013CAEA /* TitleScreen.storyboard */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -168,6 +169,7 @@ 7DC54AE426437AB3000C8300 /* PinnedTicketRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedTicketRecord.swift; sourceTree = ""; }; 7DEA17842639E7B5004E5A71 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 7DF6CBA4265F8D620013CAEA /* UIWindowExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIWindowExtensions.swift; sourceTree = ""; }; + 7DF6CBA6265F93A90013CAEA /* TitleScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TitleScreen.storyboard; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -354,6 +356,7 @@ children = ( 7D679F802650EF83000429E8 /* Admin */, 5A79FFE0262E562A00D04320 /* Main.storyboard */, + 7DF6CBA6265F93A90013CAEA /* TitleScreen.storyboard */, 7D9963AB261EF3A4002338E7 /* Root.storyboard */, 7DC2A21626293F360002CEE6 /* Home.storyboard */, 7DC2A21B262943A70002CEE6 /* Sidebar.storyboard */, @@ -502,6 +505,7 @@ 5A79FFE1262E562A00D04320 /* Main.storyboard in Resources */, 05588A12264DB61E000D8674 /* TeamMembers.storyboard in Resources */, 7D9963AD261EF3A4002338E7 /* Root.storyboard in Resources */, + 7DF6CBA7265F93A90013CAEA /* TitleScreen.storyboard in Resources */, 7DEA17852639E7B5004E5A71 /* LaunchScreen.storyboard in Resources */, 7D01B55C264FAAEE0009FA97 /* Admin.storyboard in Resources */, 3A150A77263CB5F70052934B /* TicketDetail.storyboard in Resources */, diff --git a/Sluggo/AppDelegate.swift b/Sluggo/AppDelegate.swift index 39ef3ed..da8c1db 100644 --- a/Sluggo/AppDelegate.swift +++ b/Sluggo/AppDelegate.swift @@ -31,6 +31,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { }) window!.rootViewController = initialViewController window!.makeKeyAndVisible() + + // If any windows exist below, delete them + if(!(UIApplication.shared.windows.first == window)) { + UIApplication.shared.windows[0].dismiss() + } } // MARK: UISceneSession Lifecycle diff --git a/Sluggo/Storyboards/TitleScreen.storyboard b/Sluggo/Storyboards/TitleScreen.storyboard new file mode 100644 index 0000000..dfc7077 --- /dev/null +++ b/Sluggo/Storyboards/TitleScreen.storyboard @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sluggo/View Controllers/RootViewController.swift b/Sluggo/View Controllers/RootViewController.swift index 752aac9..94215a3 100644 --- a/Sluggo/View Controllers/RootViewController.swift +++ b/Sluggo/View Controllers/RootViewController.swift @@ -66,16 +66,28 @@ class RootViewController: UIViewController { self.mainTabBarController?.dismiss(animated: true, completion: { // Assume we are in the key window let keyWindow = UIApplication.shared.keyWindow + + // Setup temporary window to make it look like we don't cut in and out + // Relies on logo VC being identical in nature to existing VC + let temporaryLogoWindow = UIWindow() + let logoStoryboard = UIStoryboard(name: "TitleScreen", bundle: Bundle.main) + let logoVC = logoStoryboard.instantiateInitialViewController() + temporaryLogoWindow.rootViewController = logoVC + + // Make new logo window visible + temporaryLogoWindow.makeKeyAndVisible() + + // Dismiss our old window after this has been made key and visible keyWindow?.dismiss() // Hopefully the user cannot access anything now // We don't do background calls to API so this *shouldn't* crash // Remove AppIdentity persistence file - let identityClearingSuccess = AppIdentity.deletePersistenceFile() + _ = AppIdentity.deletePersistenceFile() // After this is done, make a call to reconfigure + // Reconfiguring will remove the logo window if it exists guard let application = UIApplication.shared.delegate as? AppDelegate else { - print("FUCK") return } application.configureInitialViewController() From de9e7f39d7ae7c9af1182063c05dd4ab3b1439fa Mon Sep 17 00:00:00 2001 From: Isaac Trimble-Pederson Date: Thu, 27 May 2021 02:24:52 -0700 Subject: [PATCH 194/224] Fix design flaw on team change; unwind controllers to root when team switched --- Sluggo.xcodeproj/project.pbxproj | 4 +++ Sluggo/Storyboards/Home.storyboard | 14 ++++---- Sluggo/Storyboards/TeamMembers.storyboard | 2 +- Sluggo/Storyboards/Ticket.storyboard | 2 +- .../View Controllers/RootViewController.swift | 25 +++++-------- .../SluggoNavigationController.swift | 35 +++++++++++++++++++ 6 files changed, 56 insertions(+), 26 deletions(-) create mode 100644 Sluggo/View Controllers/SluggoNavigationController.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 170d909..1f9c393 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -76,6 +76,7 @@ 7DEA17852639E7B5004E5A71 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DEA17842639E7B5004E5A71 /* LaunchScreen.storyboard */; }; 7DF6CBA5265F8D620013CAEA /* UIWindowExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DF6CBA4265F8D620013CAEA /* UIWindowExtensions.swift */; }; 7DF6CBA7265F93A90013CAEA /* TitleScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DF6CBA6265F93A90013CAEA /* TitleScreen.storyboard */; }; + 7DF6CBA9265F9A8E0013CAEA /* SluggoNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DF6CBA8265F9A8E0013CAEA /* SluggoNavigationController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -170,6 +171,7 @@ 7DEA17842639E7B5004E5A71 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 7DF6CBA4265F8D620013CAEA /* UIWindowExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIWindowExtensions.swift; sourceTree = ""; }; 7DF6CBA6265F93A90013CAEA /* TitleScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TitleScreen.storyboard; sourceTree = ""; }; + 7DF6CBA8265F9A8E0013CAEA /* SluggoNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SluggoNavigationController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -386,6 +388,7 @@ 05DE0CD6264F11E400D69C61 /* MemberListViewController.swift */, 7D01B55D264FADC40009FA97 /* AdminTableViewController.swift */, 3A90FBB426531AC600BE4F5D /* AdminUserRolesTableViewController.swift */, + 7DF6CBA8265F9A8E0013CAEA /* SluggoNavigationController.swift */, ); path = "View Controllers"; sourceTree = ""; @@ -574,6 +577,7 @@ 2FF89024264BBAC600D50B69 /* TicketDetailTableViewController.swift in Sources */, 5AC6459026412F0C00F46137 /* TeamTableViewCell.swift in Sources */, 051AAB0B2630FD6500BCB9C7 /* Constants.swift in Sources */, + 7DF6CBA9265F9A8E0013CAEA /* SluggoNavigationController.swift in Sources */, 2F3F66E2262DD3BA0040404C /* JsonLoader.swift in Sources */, 2F2247AC262920AC006C6EE6 /* TokenRecord.swift in Sources */, 7D1DA6AE262B82C200E7C12D /* RootViewController.swift in Sources */, diff --git a/Sluggo/Storyboards/Home.storyboard b/Sluggo/Storyboards/Home.storyboard index dc77a30..5480a08 100644 --- a/Sluggo/Storyboards/Home.storyboard +++ b/Sluggo/Storyboards/Home.storyboard @@ -10,7 +10,7 @@ - + @@ -62,29 +62,29 @@ - + - + - + - + diff --git a/Sluggo/Storyboards/TeamMembers.storyboard b/Sluggo/Storyboards/TeamMembers.storyboard index a7b9d36..7de5938 100644 --- a/Sluggo/Storyboards/TeamMembers.storyboard +++ b/Sluggo/Storyboards/TeamMembers.storyboard @@ -55,7 +55,7 @@ - + diff --git a/Sluggo/Storyboards/Ticket.storyboard b/Sluggo/Storyboards/Ticket.storyboard index cfca5bb..b23f06a 100644 --- a/Sluggo/Storyboards/Ticket.storyboard +++ b/Sluggo/Storyboards/Ticket.storyboard @@ -130,7 +130,7 @@ - + diff --git a/Sluggo/View Controllers/RootViewController.swift b/Sluggo/View Controllers/RootViewController.swift index 94215a3..8742e20 100644 --- a/Sluggo/View Controllers/RootViewController.swift +++ b/Sluggo/View Controllers/RootViewController.swift @@ -118,7 +118,7 @@ class RootViewController: UIViewController { adminVC.identity = self.identity // Does this create a race condition? // Wrap this in a navigation controller - let navVC = UINavigationController(rootViewController: adminVC) + let navVC = SluggoNavigationController(rootViewController: adminVC) // Create bar button item let adminBarButtonImage = UIImage(systemName: "shield") @@ -197,7 +197,7 @@ class RootViewController: UIViewController { /* * Some notes on the below implementation: * We determine if we can present based on the number of VCs in the - * navigation stack of each controller. This is acceptable, because + * navigation stack of the selected tab controller. This is acceptable, because * navigation presentation is susceptible to still recieving the * sidebar gestures. * @@ -207,23 +207,14 @@ class RootViewController: UIViewController { guard let tabBarControllerVCs = mainTabBarController?.viewControllers else { return false } // return false if something goes wrong in determining - var shouldPresentSidebar = true - - for tabVC in tabBarControllerVCs { - if let navVC = tabVC as? UINavigationController { - if navVC.viewControllers.count > 1 { - // More than one view controller present - print(navVC.viewControllers) - shouldPresentSidebar = false - break // save additional cycles - } - } else { - print("RootViewController: Error: Root view controller on" + - " tab bar controller was not a navigation controller") - continue + let selectedTabVC = self.mainTabBarController?.selectedViewController + if let navVC = selectedTabVC as? UINavigationController { + if navVC.viewControllers.count > 1 { + // More than one view controller present + return false } } - return shouldPresentSidebar + return true } } diff --git a/Sluggo/View Controllers/SluggoNavigationController.swift b/Sluggo/View Controllers/SluggoNavigationController.swift new file mode 100644 index 0000000..c166b8b --- /dev/null +++ b/Sluggo/View Controllers/SluggoNavigationController.swift @@ -0,0 +1,35 @@ +// +// SluggoNavigationController.swift +// Sluggo +// +// Created by Isaac Trimble-Pederson on 5/27/21. +// + +import UIKit + +/** + This class is identical to the Navigation Controller, but enables listening for the team + changer notification, upon which it will unwind to the root. This prevents potential + inconsistencies with nested presenting controllers, and enables the user to switch + from them when something is being presented in another controller. + + Note: This only handles the case with regards to navigation presentation, but this is + OK as modal presentations will block the tab bar. + */ +class SluggoNavigationController: UINavigationController { + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + NotificationCenter.default.addObserver(self, + selector: #selector(didRecieveTeamChange), + name: Constants.Signals.TEAM_CHANGE_NOTIFICATION, + object: nil) + } + + @objc private func didRecieveTeamChange() { + self.popToRootViewController(animated: true) + } + +} From c85dcbc4109cf0eff5500cc6b82f82ffccff0928 Mon Sep 17 00:00:00 2001 From: Stephan Martin Date: Fri, 28 May 2021 14:56:12 -0700 Subject: [PATCH 195/224] Admin tag deletion working, plus a few warning fixes --- Sluggo/AppDelegate.swift | 4 ++-- Sluggo/Models/Managers/TagManager.swift | 9 ++++++++ .../AdminTagViewController.swift | 22 +++++++++++++++++++ .../TeamTableViewController.swift | 4 ++-- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/Sluggo/AppDelegate.swift b/Sluggo/AppDelegate.swift index da8c1db..be239c3 100644 --- a/Sluggo/AppDelegate.swift +++ b/Sluggo/AppDelegate.swift @@ -31,9 +31,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { }) window!.rootViewController = initialViewController window!.makeKeyAndVisible() - + // If any windows exist below, delete them - if(!(UIApplication.shared.windows.first == window)) { + if !(UIApplication.shared.windows.first == window) { UIApplication.shared.windows[0].dismiss() } } diff --git a/Sluggo/Models/Managers/TagManager.swift b/Sluggo/Models/Managers/TagManager.swift index 0ad2e08..58a5eb1 100644 --- a/Sluggo/Models/Managers/TagManager.swift +++ b/Sluggo/Models/Managers/TagManager.swift @@ -52,4 +52,13 @@ class TagManager: TeamPaginatedListable { JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) } + public func deleteTag(tag: TagRecord, completionHandler: @escaping(Result) -> Void) { + + let requestBuilder = URLRequestBuilder(url: makeDetailUrl(tagRecord: tag)) + .setMethod(method: .DELETE) + .setIdentity(identity: self.identity) + + JsonLoader.executeEmptyRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) + } + } diff --git a/Sluggo/View Controllers/AdminTagViewController.swift b/Sluggo/View Controllers/AdminTagViewController.swift index 81b2bd6..c75fbc8 100644 --- a/Sluggo/View Controllers/AdminTagViewController.swift +++ b/Sluggo/View Controllers/AdminTagViewController.swift @@ -48,6 +48,28 @@ class AdminTagViewController: UITableViewController { return cell } + override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, + forRowAt indexPath: IndexPath) { + if editingStyle == .delete { + let acon = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + acon.addAction(UIAlertAction(title: "Delete Tag", style: .destructive, handler: { _ in + let manager = TagManager(identity: self.identity) + let tag = self.tags[indexPath.row] + manager.deleteTag(tag: tag) { result in + self.processResult(result: result) { _ in + DispatchQueue.main.async { + NotificationCenter.default.post(name: .refreshTags, object: self) + NotificationCenter.default.post(name: .refreshTrigger, object: self) + } + } + } + })) + acon.addAction(UIAlertAction(title: "Cancel", style: .cancel)) + acon.popoverPresentationController?.barButtonItem = self.navigationItem.rightBarButtonItem + present(acon, animated: true) + } + } + @IBSegueAction func segueToTagCreate(_ coder: NSCoder) -> AdminTagCreateViewController? { let view = AdminTagCreateViewController(coder: coder, identity: identity) return view diff --git a/Sluggo/View Controllers/TeamTableViewController.swift b/Sluggo/View Controllers/TeamTableViewController.swift index aadb1e5..33c9909 100644 --- a/Sluggo/View Controllers/TeamTableViewController.swift +++ b/Sluggo/View Controllers/TeamTableViewController.swift @@ -83,9 +83,9 @@ class TeamTableViewController: UITableViewController { after: after) } } - + @IBAction func doLogOutAction(_ sender: Any) { self.logOutAction?() } - + } From 48eb1fffe97f9d2e70cfb2b85d2f921f7f31a7c5 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Fri, 28 May 2021 15:06:11 -0700 Subject: [PATCH 196/224] started refactoring add Tag --- Sluggo/Storyboards/Admin/AdminTag.storyboard | 57 +------------------ .../AdminTagViewController.swift | 29 +++++++++- .../TicketDetailTableViewController.swift | 4 +- 3 files changed, 31 insertions(+), 59 deletions(-) diff --git a/Sluggo/Storyboards/Admin/AdminTag.storyboard b/Sluggo/Storyboards/Admin/AdminTag.storyboard index 7892b2b..7bfcd5f 100644 --- a/Sluggo/Storyboards/Admin/AdminTag.storyboard +++ b/Sluggo/Storyboards/Admin/AdminTag.storyboard @@ -49,8 +49,7 @@ - - + @@ -59,60 +58,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Sluggo/View Controllers/AdminTagViewController.swift b/Sluggo/View Controllers/AdminTagViewController.swift index 81b2bd6..d9dde72 100644 --- a/Sluggo/View Controllers/AdminTagViewController.swift +++ b/Sluggo/View Controllers/AdminTagViewController.swift @@ -52,7 +52,34 @@ class AdminTagViewController: UITableViewController { let view = AdminTagCreateViewController(coder: coder, identity: identity) return view } - + @IBAction func addTagHit(_ sender: Any) { + let aCon = UIAlertController(title: "Create a Tag", message: "", preferredStyle: .alert) + aCon.addTextField() { textField in + textField.placeholder = "Enter Tag Title" + } + aCon.addAction(UIAlertAction(title: "Delete Ticket", style: .default, handler: doCreateAction)) + aCon.addAction(UIAlertAction(title: "Cancel", style: .cancel)) + let saveAction = UIAlertAction(title: "Save", style: .default, handler: { alert -> Void in + + }) + + } + + func doCreateAction(action: UIAlertAction) { +// let firstTextField = action..textFields![0] as UITextField + let title = action.textFields?[0] ?? "Default Tag Title" + let manager = TagManager(identity: identity) + let tag = WriteTagRecord(title: title) + manager.makeTag(tag: tag) { result in + self.processResult(result: result) { _ in + DispatchQueue.main.async { + NotificationCenter.default.post(name: .refreshTags, object: self) + self.navigationController?.popViewController(animated: true) + } + } + } + } + @objc func handleRefreshAction() { DispatchQueue.global(qos: .userInitiated).async { diff --git a/Sluggo/View Controllers/TicketDetailTableViewController.swift b/Sluggo/View Controllers/TicketDetailTableViewController.swift index c584fe0..4d868da 100644 --- a/Sluggo/View Controllers/TicketDetailTableViewController.swift +++ b/Sluggo/View Controllers/TicketDetailTableViewController.swift @@ -58,7 +58,7 @@ class TicketDetailTableViewController: UITableViewController { }, onError: { _ in self.pinnedTicket = nil }, after: { - let title = self.pinnedTicket != nil ? "Un-pin Ticket" : "Pin Ticket" + let title = self.pinnedTicket != nil ? "Unpin Ticket" : "Pin Ticket" self.pinTicketButton.setTitle(title, for: .normal) }) } @@ -256,7 +256,7 @@ class TicketDetailTableViewController: UITableViewController { self.processResult(result: result) { record in DispatchQueue.main.async { NotificationCenter.default.post(name: .refreshTrigger, object: self) - self.pinTicketButton.setTitle("Un-pin Ticket", for: .normal) + self.pinTicketButton.setTitle("Unpin Ticket", for: .normal) self.pinnedTicket = record } } From 6ae4c8cf2e40ba6b6a42ba05c0e6dab007b65092 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Fri, 28 May 2021 15:34:43 -0700 Subject: [PATCH 197/224] Finished Tag Adjustments. --- .../AdminTagViewController.swift | 85 +++++-------------- 1 file changed, 23 insertions(+), 62 deletions(-) diff --git a/Sluggo/View Controllers/AdminTagViewController.swift b/Sluggo/View Controllers/AdminTagViewController.swift index 68334aa..851c80e 100644 --- a/Sluggo/View Controllers/AdminTagViewController.swift +++ b/Sluggo/View Controllers/AdminTagViewController.swift @@ -51,8 +51,8 @@ class AdminTagViewController: UITableViewController { override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == .delete { - let acon = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) - acon.addAction(UIAlertAction(title: "Delete Tag", style: .destructive, handler: { _ in + let aCon = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + aCon.addAction(UIAlertAction(title: "Delete Tag", style: .destructive, handler: { _ in let manager = TagManager(identity: self.identity) let tag = self.tags[indexPath.row] manager.deleteTag(tag: tag) { result in @@ -64,44 +64,37 @@ class AdminTagViewController: UITableViewController { } } })) - acon.addAction(UIAlertAction(title: "Cancel", style: .cancel)) - acon.popoverPresentationController?.barButtonItem = self.navigationItem.rightBarButtonItem - present(acon, animated: true) + aCon.addAction(UIAlertAction(title: "Cancel", style: .cancel)) + aCon.popoverPresentationController?.barButtonItem = self.navigationItem.rightBarButtonItem + present(aCon, animated: true) } } - @IBSegueAction func segueToTagCreate(_ coder: NSCoder) -> AdminTagCreateViewController? { - let view = AdminTagCreateViewController(coder: coder, identity: identity) - return view - } @IBAction func addTagHit(_ sender: Any) { - let aCon = UIAlertController(title: "Create a Tag", message: "", preferredStyle: .alert) - aCon.addTextField() { textField in + let aCon = UIAlertController(title: "Create a Tag", message: nil, preferredStyle: .alert) + aCon.addTextField { textField in textField.placeholder = "Enter Tag Title" } - aCon.addAction(UIAlertAction(title: "Delete Ticket", style: .default, handler: doCreateAction)) aCon.addAction(UIAlertAction(title: "Cancel", style: .cancel)) - let saveAction = UIAlertAction(title: "Save", style: .default, handler: { alert -> Void in - - }) - - } - - func doCreateAction(action: UIAlertAction) { -// let firstTextField = action..textFields![0] as UITextField - let title = action.textFields?[0] ?? "Default Tag Title" - let manager = TagManager(identity: identity) - let tag = WriteTagRecord(title: title) - manager.makeTag(tag: tag) { result in - self.processResult(result: result) { _ in - DispatchQueue.main.async { - NotificationCenter.default.post(name: .refreshTags, object: self) - self.navigationController?.popViewController(animated: true) + aCon.addAction(UIAlertAction(title: "Save", style: .default, handler: { _ in + let textField = aCon.textFields?[0] ?? nil + if let text = textField?.text { + let manager = TagManager(identity: self.identity) + let tag = WriteTagRecord(title: text) + manager.makeTag(tag: tag) { result in + self.processResult(result: result) { _ in + DispatchQueue.main.async { + NotificationCenter.default.post(name: .refreshTags, object: self) + } + } } } - } + })) + aCon.popoverPresentationController?.barButtonItem = self.navigationItem.rightBarButtonItem + present(aCon, animated: true) + } - + @objc func handleRefreshAction() { DispatchQueue.global(qos: .userInitiated).async { @@ -130,35 +123,3 @@ class AdminTagViewController: UITableViewController { extension Notification.Name { static let refreshTags = Notification.Name(rawValue: "SLGRefreshTagsNotification") } - -class AdminTagCreateViewController: UITableViewController { - var identity: AppIdentity! - @IBOutlet weak var tagTitle: UITextField! - - required init? (coder: NSCoder, identity: AppIdentity) { - self.identity = identity - super.init(coder: coder) - } - - required init? (coder: NSCoder) { - fatalError("Must be initialized with identity") - } - - override func viewDidLoad() { - super.viewDidLoad() - } - - @IBAction func doneButtonClicked(_ sender: Any) { - let title = tagTitle.text ?? "Default Tag Title" - let manager = TagManager(identity: identity) - let tag = WriteTagRecord(title: title) - manager.makeTag(tag: tag) { result in - self.processResult(result: result) { _ in - DispatchQueue.main.async { - NotificationCenter.default.post(name: .refreshTags, object: self) - self.navigationController?.popViewController(animated: true) - } - } - } - } -} From e07ea24a4b664c3e496cebc37ef2d8d41ae08921 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Sat, 29 May 2021 18:45:52 -0700 Subject: [PATCH 198/224] Added Readme --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index ecac89b..8ed9f34 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,28 @@ This repository will hold the source code for the iOS client for the Sluggo issue tracker. It is currently beign developed for CSE 115A at UCSC, and will eventually expand into a fully maintained frontend for the Sluggo project. + + +## About Sluggo +Sluggo is an open source Issue Tracker for companies, teams, and more. The software is selfhosted, and decentralized in a way that any team can spin up their own instance in minutes! The program started within CSE 183 Spring quarter 2020, and has since had multiple rewrites and expansions. From the [Single Page Application built in Vue]() as a web based front end, to the [Django REST Application]() as the backend, there have also been other expansions such as a [Slack Extension]() and now an iOS app. + +## Installing the API: +*Note: If you are using the SPA, the login process is simpler* +1) Before using the Sluggo iOS application, make sure to install the backend API following the instructions on the [repository here](). +2) After completing the installation process, proceed to the admin dashboard (default `127.0.0.8:8000/admin`) and sign on with your super user account that you just created. +3) In this page, you will be able to create a record for your Team by selecting `Team` on the admin page, and hitting the `Add Team` button in the top right. Just give the team a name and hit save. +4) Finally, after creating your team go through the same process and create a `Member` record for yourself on the Member page. Select your admin user, your team, and fill out any optional fields such as Pronouns or Bio. + +## Installing the Application +*Note: Once the app is placed on the App Store, the installation process is much simpler* +1) Clone this repository on a Mac device with Xcode installed. +2) Run the API on a system with either port forwarding enabled and a public IP, or on the same machine as the simulated app. +3) Open the repository in Xcode, and hit build. +4) If you are using the simulator, select your device of choice and hit run. +5) If you have an apple device you would like to test on, follow [Apple's instruction on running apps on your phone](). +6) Hit run and load the application on your phone or the simulator. + +## Running the App +1) When launching the app for the first time, fill in your user name and password for your sluggo instance. +2) Your Sluggo instance is the url of the API, make sure to have the entire path listed for example: `http://127.0.0.1:8000/` +3) You will be dropped on the home screen, feel free to poke around! \ No newline at end of file From b0a775a247add275c2fc45f9ecfabce2b4234801 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Sat, 29 May 2021 18:50:33 -0700 Subject: [PATCH 199/224] added urls --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8ed9f34..c24fb16 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,11 @@ eventually expand into a fully maintained frontend for the Sluggo project. ## About Sluggo -Sluggo is an open source Issue Tracker for companies, teams, and more. The software is selfhosted, and decentralized in a way that any team can spin up their own instance in minutes! The program started within CSE 183 Spring quarter 2020, and has since had multiple rewrites and expansions. From the [Single Page Application built in Vue]() as a web based front end, to the [Django REST Application]() as the backend, there have also been other expansions such as a [Slack Extension]() and now an iOS app. +Sluggo is an open source Issue Tracker for companies, teams, and more. The software is selfhosted, and decentralized in a way that any team can spin up their own instance in minutes! The program started within CSE 183 Spring quarter 2020, and has since had multiple rewrites and expansions. From the [Single Page Application built in Vue](https://github.com/Slugbotics/Sluggo-SPA) as a web based front end, to the [Django REST Application](https://github.com/Slugbotics/Sluggo-API) as the backend, there have also been other expansions such as a [Slack Extension](https://github.com/Slugbotics/Sluggo-Slack) and now an iOS app. ## Installing the API: *Note: If you are using the SPA, the login process is simpler* -1) Before using the Sluggo iOS application, make sure to install the backend API following the instructions on the [repository here](). +1) Before using the Sluggo iOS application, make sure to install the backend API following the instructions on the [repository here](https://github.com/Slugbotics/Sluggo-API). 2) After completing the installation process, proceed to the admin dashboard (default `127.0.0.8:8000/admin`) and sign on with your super user account that you just created. 3) In this page, you will be able to create a record for your Team by selecting `Team` on the admin page, and hitting the `Add Team` button in the top right. Just give the team a name and hit save. 4) Finally, after creating your team go through the same process and create a `Member` record for yourself on the Member page. Select your admin user, your team, and fill out any optional fields such as Pronouns or Bio. @@ -20,7 +20,7 @@ Sluggo is an open source Issue Tracker for companies, teams, and more. The softw 2) Run the API on a system with either port forwarding enabled and a public IP, or on the same machine as the simulated app. 3) Open the repository in Xcode, and hit build. 4) If you are using the simulator, select your device of choice and hit run. -5) If you have an apple device you would like to test on, follow [Apple's instruction on running apps on your phone](). +5) If you have an apple device you would like to test on, follow [Apple's instruction on running apps on your phone](https://developer.apple.com/documentation/xcode/running-your-app-in-the-simulator-or-on-a-device). 6) Hit run and load the application on your phone or the simulator. ## Running the App From ae70ef68e188c377425cc68c5ede91ef14216db1 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Sat, 29 May 2021 23:50:07 -0700 Subject: [PATCH 200/224] Added checks for tickets and tags title being filled. --- .../AdminTagViewController.swift | 17 +++++++++++++++-- .../TicketDetailTableViewController.swift | 7 ++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Sluggo/View Controllers/AdminTagViewController.swift b/Sluggo/View Controllers/AdminTagViewController.swift index 851c80e..a832ab4 100644 --- a/Sluggo/View Controllers/AdminTagViewController.swift +++ b/Sluggo/View Controllers/AdminTagViewController.swift @@ -76,7 +76,8 @@ class AdminTagViewController: UITableViewController { textField.placeholder = "Enter Tag Title" } aCon.addAction(UIAlertAction(title: "Cancel", style: .cancel)) - aCon.addAction(UIAlertAction(title: "Save", style: .default, handler: { _ in + + let saveAction = UIAlertAction(title: "Save", style: .default, handler: { _ in let textField = aCon.textFields?[0] ?? nil if let text = textField?.text { let manager = TagManager(identity: self.identity) @@ -89,7 +90,19 @@ class AdminTagViewController: UITableViewController { } } } - })) + }) + + saveAction.enabled = false + // adding the notification observer here + NSNotificationCenter.defaultCenter().addObserverForName(UITextFieldTextDidChangeNotification, object:aCon.textFields?[0], + queue: NSOperationQueue.mainQueue()) { notification in + + let title = aCon.textFields?[0] as! UITextField + saveAction.enabled = !title.text.isEmpty + } + + aCon.addAction(saveAction) + aCon.popoverPresentationController?.barButtonItem = self.navigationItem.rightBarButtonItem present(aCon, animated: true) diff --git a/Sluggo/View Controllers/TicketDetailTableViewController.swift b/Sluggo/View Controllers/TicketDetailTableViewController.swift index 4d868da..9010b50 100644 --- a/Sluggo/View Controllers/TicketDetailTableViewController.swift +++ b/Sluggo/View Controllers/TicketDetailTableViewController.swift @@ -157,7 +157,7 @@ class TicketDetailTableViewController: UITableViewController { } func doSave() { - let title = ticketTitle.text ?? "Default Title" + let title = ticketTitle.text ?? "" let description = ticketDescription.text let date = dueDateSwitch.isOn ? dueDatePicker.date : nil let member = currentMember?.id @@ -166,6 +166,11 @@ class TicketDetailTableViewController: UITableViewController { let manager = TicketManager(identity) + if title.isEmpty { + // createError Error + self.presentError(error: Exception.runtimeError(message: "Ticket Title Cannot Be Empty.")) + return + } if editingTicket { ticket!.title = title ticket!.description = description From 3ceff96b06c17f3c8def12331f5b1bf52b731b12 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Sun, 30 May 2021 00:11:08 -0700 Subject: [PATCH 201/224] Readme adjustments --- README.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c24fb16..3aa7475 100644 --- a/README.md +++ b/README.md @@ -12,18 +12,24 @@ Sluggo is an open source Issue Tracker for companies, teams, and more. The softw 1) Before using the Sluggo iOS application, make sure to install the backend API following the instructions on the [repository here](https://github.com/Slugbotics/Sluggo-API). 2) After completing the installation process, proceed to the admin dashboard (default `127.0.0.8:8000/admin`) and sign on with your super user account that you just created. 3) In this page, you will be able to create a record for your Team by selecting `Team` on the admin page, and hitting the `Add Team` button in the top right. Just give the team a name and hit save. -4) Finally, after creating your team go through the same process and create a `Member` record for yourself on the Member page. Select your admin user, your team, and fill out any optional fields such as Pronouns or Bio. +4) Finally, after creating your team go through the same process and create a `Member` record for yourself on the Member page through the `Add Member` button on your top right. Set you user role as admin, select your team, and fill out any optional fields such as pronouns or Bio, then hit save. + + +## SwiftLint +The development of Sluggo uses SwiftLint to standardize all changes and coding styles, if you are contributing to Sluggo, please follow the installation instructions for SwiftLint [here](https://github.com/realm/SwiftLint). ## Installing the Application *Note: Once the app is placed on the App Store, the installation process is much simpler* 1) Clone this repository on a Mac device with Xcode installed. -2) Run the API on a system with either port forwarding enabled and a public IP, or on the same machine as the simulated app. -3) Open the repository in Xcode, and hit build. -4) If you are using the simulator, select your device of choice and hit run. -5) If you have an apple device you would like to test on, follow [Apple's instruction on running apps on your phone](https://developer.apple.com/documentation/xcode/running-your-app-in-the-simulator-or-on-a-device). -6) Hit run and load the application on your phone or the simulator. + - Preferably this is on the same device as the API. Otherwise, you must have the API acessable on your network with a public IP. +3) Open the repository in Xcode, and select build. +4) After this, your app is ready to run, configure your device and then select Run. + - If you have an apple device you would like to test on, follow [Apple's instruction on running apps on your phone](https://developer.apple.com/documentation/xcode/running-your-app-in-the-simulator-or-on-a-device). + - Otherwise you may use one of the built in simulators on XCode ## Running the App 1) When launching the app for the first time, fill in your user name and password for your sluggo instance. 2) Your Sluggo instance is the url of the API, make sure to have the entire path listed for example: `http://127.0.0.1:8000/` -3) You will be dropped on the home screen, feel free to poke around! \ No newline at end of file +3) You will be dropped in the team select page, where you may choose any team your account is apart of. Once you have picked one the homepage will appear. Feel free to poke around! + + From 0f5e0979366cbc3aa69b15540ad4946d50376f6d Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Sun, 30 May 2021 00:30:08 -0700 Subject: [PATCH 202/224] Added Creating/Inviting Users info. --- README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3aa7475..1cc88c3 100644 --- a/README.md +++ b/README.md @@ -32,4 +32,21 @@ The development of Sluggo uses SwiftLint to standardize all changes and coding s 2) Your Sluggo instance is the url of the API, make sure to have the entire path listed for example: `http://127.0.0.1:8000/` 3) You will be dropped in the team select page, where you may choose any team your account is apart of. Once you have picked one the homepage will appear. Feel free to poke around! - +## Creating Users +New users (*for now*) must be created through the admin dashboard similarily to the admin user. As the Sluggo ecosystem progresses, this will be adjusted, but it is now the job of the Sysadmin to create accounts for every member. +1) Open the admin dashboard on your web page (default `127.0.0.1:8000/admin`), scroll down to the **Authentication and Authorization** section, and select `Users`. +2) Create a new user by hitting `Add User` in the top right corner, enter a username and password for the user, and hit save. +3) This will lead you to a more indepth user editing page where their name, email, and other such fields can be filled out. (**Note: If you want to invite your user to your team, they must have an email address entered**) Fill out the necessarily fields and hit save. +4) After creating the user, proceed to the next section to add them to your team. + +## Inviting Users to Your Team +After a user has been created, they cannot do much until they are apart of your team, currently there are two ways to accomplish this: +1) **Adding Members in App**: + - Signed into the Sluggo iOS application, proceed to the admin tab and select the *Invite* row. Here you will be able to enter the email address of the created user which will notify them the next time they log in that there is an invite waiting for accepting. + - As the new user, once they log into the application with the same instance url, they will see no teams they can currently access. However, there will be a section on the Team Select view for *Invites* where they can accept or decline the invitation from you as a Sysadmin. + - If they accept, it will add them to your team, and they may proceed to the rest of the application. +2) **Adding Members through the Admin Dashboard**: + - If you choose against adding members in app, you may go and follow the same process outlined in ``Installing the API`` *Step 4*. + - Log into the admin dashboard, and select the `Members` field in the **API** section. Select create a member in top right and select the new user's `User Record` in the dropdown and the `Team` record corresponding to your team. + - Any optional fields such as role, pronouns, or bio can all be adjusted here before hitting save. + - The next time the user logs into the iOS application, they will see the team you created the record for in their Team select and proceed to the rest of the application. From 45d623b7bac43fb1ad4bba36322ad406eef23090 Mon Sep 17 00:00:00 2001 From: Firestarsam Date: Sun, 30 May 2021 01:22:23 -0700 Subject: [PATCH 203/224] Stephan edit: Fixed a few bugs from Andrew working on non-Xcode (good job by the way on that). Also added it so that ticket title and tag title will error out correctly when inproper information is put in --- .../AdminTagViewController.swift | 37 +++++++++++-------- .../HomeTableViewController.swift | 6 +-- .../TicketDetailTableViewController.swift | 2 +- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/Sluggo/View Controllers/AdminTagViewController.swift b/Sluggo/View Controllers/AdminTagViewController.swift index a832ab4..4559fd9 100644 --- a/Sluggo/View Controllers/AdminTagViewController.swift +++ b/Sluggo/View Controllers/AdminTagViewController.swift @@ -76,31 +76,38 @@ class AdminTagViewController: UITableViewController { textField.placeholder = "Enter Tag Title" } aCon.addAction(UIAlertAction(title: "Cancel", style: .cancel)) - + let saveAction = UIAlertAction(title: "Save", style: .default, handler: { _ in - let textField = aCon.textFields?[0] ?? nil - if let text = textField?.text { - let manager = TagManager(identity: self.identity) - let tag = WriteTagRecord(title: text) - manager.makeTag(tag: tag) { result in - self.processResult(result: result) { _ in - DispatchQueue.main.async { - NotificationCenter.default.post(name: .refreshTags, object: self) + if aCon.textFields![0].text!.contains(" ") { + self.presentError(error: Exception.runtimeError(message: "Tag title cannot contain any spaces")) + } else { + let textField = aCon.textFields?[0] ?? nil + if let text = textField?.text { + let manager = TagManager(identity: self.identity) + let tag = WriteTagRecord(title: text) + manager.makeTag(tag: tag) { result in + self.processResult(result: result) { _ in + DispatchQueue.main.async { + NotificationCenter.default.post(name: .refreshTags, object: self) + } } } } } }) - saveAction.enabled = false + saveAction.isEnabled = false // adding the notification observer here - NSNotificationCenter.defaultCenter().addObserverForName(UITextFieldTextDidChangeNotification, object:aCon.textFields?[0], - queue: NSOperationQueue.mainQueue()) { notification in - let title = aCon.textFields?[0] as! UITextField - saveAction.enabled = !title.text.isEmpty + NotificationCenter.default.addObserver( + forName: UITextField.textDidChangeNotification, + object: aCon.textFields?[0], + queue: OperationQueue.main) { _ in + + let title = aCon.textFields?[0] + saveAction.isEnabled = !title!.text!.isEmpty } - + aCon.addAction(saveAction) aCon.popoverPresentationController?.barButtonItem = self.navigationItem.rightBarButtonItem diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index 3db26cb..1b444a8 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -135,7 +135,7 @@ class HomeTableViewController: UITableViewController { override func numberOfSections(in tableView: UITableView) -> Int { // #warning Incomplete implementation, return the number of sections - return 3 + return 2 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { @@ -145,8 +145,8 @@ class HomeTableViewController: UITableViewController { return self.assignedTickets.count case HomepageCategories.pinned.rawValue: return self.pinnedTickets.count - case HomepageCategories.tags.rawValue: - return 1 + // case HomepageCategories.tags.rawValue: + // return 1 default: fatalError("Invalid section count queried") } diff --git a/Sluggo/View Controllers/TicketDetailTableViewController.swift b/Sluggo/View Controllers/TicketDetailTableViewController.swift index 9010b50..2719518 100644 --- a/Sluggo/View Controllers/TicketDetailTableViewController.swift +++ b/Sluggo/View Controllers/TicketDetailTableViewController.swift @@ -166,7 +166,7 @@ class TicketDetailTableViewController: UITableViewController { let manager = TicketManager(identity) - if title.isEmpty { + if title.trimmingCharacters(in: .whitespaces).isEmpty { // createError Error self.presentError(error: Exception.runtimeError(message: "Ticket Title Cannot Be Empty.")) return From a4303216f7238a393b5692bce87862998d91be59 Mon Sep 17 00:00:00 2001 From: Troy Ebert Date: Sun, 30 May 2021 19:53:36 -0700 Subject: [PATCH 204/224] Added pending invites skeleton to the sidebar --- Sluggo.xcodeproj/project.pbxproj | 4 + Sluggo/Storyboards/Sidebar.storyboard | 83 +++++++++++++++- .../PendingInvitesViewController.swift | 97 +++++++++++++++++++ .../TeamTableViewController.swift | 8 ++ 4 files changed, 187 insertions(+), 5 deletions(-) create mode 100644 Sluggo/View Controllers/PendingInvitesViewController.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 4a6aa67..6569cad 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 051AAB0B2630FD6500BCB9C7 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 051AAB0A2630FD6500BCB9C7 /* Constants.swift */; }; 052258702636100B00B49CE4 /* LaunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0522586F2636100B00B49CE4 /* LaunchViewController.swift */; }; + 053A259F26645DDD0072A773 /* PendingInvitesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 053A259E26645DDD0072A773 /* PendingInvitesViewController.swift */; }; 05588A12264DB61E000D8674 /* TeamMembers.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 05588A11264DB61E000D8674 /* TeamMembers.storyboard */; }; 05DE0CD7264F11E400D69C61 /* MemberListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05DE0CD6264F11E400D69C61 /* MemberListViewController.swift */; }; 2F2247AC262920AC006C6EE6 /* TokenRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2247AB262920AC006C6EE6 /* TokenRecord.swift */; }; @@ -101,6 +102,7 @@ /* Begin PBXFileReference section */ 051AAB0A2630FD6500BCB9C7 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 0522586F2636100B00B49CE4 /* LaunchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchViewController.swift; sourceTree = ""; }; + 053A259E26645DDD0072A773 /* PendingInvitesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingInvitesViewController.swift; sourceTree = ""; }; 05588A11264DB61E000D8674 /* TeamMembers.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TeamMembers.storyboard; sourceTree = ""; }; 05DE0CD6264F11E400D69C61 /* MemberListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemberListViewController.swift; sourceTree = ""; }; 2F2247AB262920AC006C6EE6 /* TokenRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenRecord.swift; sourceTree = ""; }; @@ -385,6 +387,7 @@ 7DC2A226262948800002CEE6 /* SluggoSidebarContainerViewController.swift */, 5A9FF88D26386D4700378550 /* TicketListController.swift */, 5A9C4C1B2640BC4E00B270AB /* TeamTableViewController.swift */, + 053A259E26645DDD0072A773 /* PendingInvitesViewController.swift */, 5A9C4C202641113D00B270AB /* TeamSelectorContainerViewController.swift */, 7D23834A264119050091C7E8 /* HomeTableViewController.swift */, 5A25CFAB264B966300852035 /* TicketFilterTableViewController.swift */, @@ -611,6 +614,7 @@ 5AB1C8EF262FE3210010F85E /* TagManager.swift in Sources */, 5A9FF88E26386D4700378550 /* TicketListController.swift in Sources */, 5AB1C8E5262FE2E60010F85E /* TicketManager.swift in Sources */, + 053A259F26645DDD0072A773 /* PendingInvitesViewController.swift in Sources */, 2F3AD7A82627C89B00B61A97 /* MemberRecord.swift in Sources */, 5AB1C8DD262FE2DD0010F85E /* TeamManager.swift in Sources */, 5A00A045263A34CC0085C8C6 /* TicketTableViewCell.swift in Sources */, diff --git a/Sluggo/Storyboards/Sidebar.storyboard b/Sluggo/Storyboards/Sidebar.storyboard index d9bde71..13ca221 100644 --- a/Sluggo/Storyboards/Sidebar.storyboard +++ b/Sluggo/Storyboards/Sidebar.storyboard @@ -1,8 +1,8 @@ - + - + @@ -18,14 +18,14 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -129,5 +199,8 @@ + + + diff --git a/Sluggo/View Controllers/PendingInvitesViewController.swift b/Sluggo/View Controllers/PendingInvitesViewController.swift new file mode 100644 index 0000000..43b7112 --- /dev/null +++ b/Sluggo/View Controllers/PendingInvitesViewController.swift @@ -0,0 +1,97 @@ +// +// PendingInvitesViewController.swift +// Sluggo +// +// Created by Troy Ebert on 5/30/21. +// + +import UIKit + +class PendingInvitesViewController: UITableViewController { + var identity: AppIdentity! + var generateSegueableController: ((AppIdentity, MemberRecord?) -> UIViewController?)? + var generateMemberDetail: ((MemberRecord) -> String?)? + private var maxNumber: Int = 0 + private var members: [MemberRecord] = [] + private var isFetching = false + private var semaphore = DispatchSemaphore(value: 1) + + required init? (coder: NSCoder, identity: AppIdentity) { + self.identity = identity + super.init(coder: coder) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + + override func viewDidLoad() { + self.configureRefreshControl() + self.handleRefreshAction() + NotificationCenter.default.addObserver(self, + selector: #selector(handleRefreshAction), + name: .refreshMembers, object: nil) + + self.tableView.allowsSelection = (generateSegueableController != nil) + } + + func configureRefreshControl() { + refreshControl = UIRefreshControl() + refreshControl?.addTarget(self, action: #selector(handleRefreshAction), for: .valueChanged) + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return members.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "pInviteCell", for: indexPath) as UITableViewCell + let member = self.members[indexPath.row] + cell.textLabel?.text = member.owner.username + cell.detailTextLabel?.text = generateMemberDetail?(member) ?? "" + + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let generateController = self.generateSegueableController else { return } + + guard let viewController = generateController(identity, self.members[indexPath.row]) else { return } + + if let navController = self.navigationController { + navController.show(viewController, sender: self) + } else { + self.present(viewController, animated: true, completion: nil) + } + } + + @objc func handleRefreshAction() { + + DispatchQueue.global(qos: .userInitiated).async { + self.semaphore.wait() + + let onSuccess = { (members: [MemberRecord]) -> Void in + self.members = members + } + + let after = { () -> Void in + DispatchQueue.main.async { + self.refreshControl?.endRefreshing() + self.tableView.reloadData() + } + self.semaphore.signal() + } + + let memberManager = MemberManager(identity: self.identity) + unwindPagination(manager: memberManager, + startingPage: 1, + onSuccess: onSuccess, + onFailure: self.presentErrorFromMainThread, + after: after) + } + } + + @IBAction func acceptInvite(_ sender: Any) { + print("Accepted!") + } +} diff --git a/Sluggo/View Controllers/TeamTableViewController.swift b/Sluggo/View Controllers/TeamTableViewController.swift index 33c9909..12de992 100644 --- a/Sluggo/View Controllers/TeamTableViewController.swift +++ b/Sluggo/View Controllers/TeamTableViewController.swift @@ -88,4 +88,12 @@ class TeamTableViewController: UITableViewController { self.logOutAction?() } + @IBAction func showPendingInvites(_ sender: Any) { + if let view = storyboard?.instantiateViewController(identifier: "pendingInvites") { + if let child = view.children[0] as? PendingInvitesViewController { + child.identity = self.identity + } + self.present(view, animated: true, completion: nil) + } + } } From d8cbfd9c2f2e9cdc0bebafd515187f8042680c1d Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Sun, 30 May 2021 23:18:01 -0700 Subject: [PATCH 205/224] Fixed admin and commented out rest of your tags --- .../AdminUserRolesTableViewController.swift | 4 ++-- .../View Controllers/HomeTableViewController.swift | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sluggo/View Controllers/AdminUserRolesTableViewController.swift b/Sluggo/View Controllers/AdminUserRolesTableViewController.swift index 56fe9d8..d0c4d76 100644 --- a/Sluggo/View Controllers/AdminUserRolesTableViewController.swift +++ b/Sluggo/View Controllers/AdminUserRolesTableViewController.swift @@ -57,9 +57,9 @@ class AdminUserRolesTableViewController: UITableViewController { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if indexPath.row == 0 { - role = MemberRoleCategories.admin.rawValue + role = MemberRoleCategories.approved.rawValue } else { - role = MemberRoleCategories.approved.rawValue + role = MemberRoleCategories.admin.rawValue } } diff --git a/Sluggo/View Controllers/HomeTableViewController.swift b/Sluggo/View Controllers/HomeTableViewController.swift index 1b444a8..3d9e1a0 100644 --- a/Sluggo/View Controllers/HomeTableViewController.swift +++ b/Sluggo/View Controllers/HomeTableViewController.swift @@ -164,10 +164,10 @@ class HomeTableViewController: UITableViewController { for: indexPath) as! TicketTableViewCell cell.loadFromTicketRecord(ticket: pinnedTickets[indexPath.row].ticket) return cell - case HomepageCategories.tags.rawValue: - let cell = tableView.dequeueReusableCell(withIdentifier: "PlaceholderCell")! - cell.textLabel?.text = "Not yet implemented." - return cell + // case HomepageCategories.tags.rawValue: + // let cell = tableView.dequeueReusableCell(withIdentifier: "PlaceholderCell")! + // cell.textLabel?.text = "Not yet implemented." + // return cell default: fatalError("Accessed section outside of scope, should never occur") } @@ -179,8 +179,8 @@ class HomeTableViewController: UITableViewController { return "Assigned to You" case HomepageCategories.pinned.rawValue: return "Pinned Tickets" - case HomepageCategories.tags.rawValue: - return "Your Tags" + // case HomepageCategories.tags.rawValue: + // return "Your Tags" default: return "Error!" } From a93a48179cecf9452b9bf129ed7c2494c6e2fd6c Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Sun, 30 May 2021 23:32:58 -0700 Subject: [PATCH 206/224] README Adjustments --- README.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 1cc88c3..fb823be 100644 --- a/README.md +++ b/README.md @@ -3,23 +3,24 @@ This repository will hold the source code for the iOS client for the Sluggo issue tracker. It is currently beign developed for CSE 115A at UCSC, and will eventually expand into a fully maintained frontend for the Sluggo project. +# NOTE: ALL DOCUMENTATION FOR CSE115A RESIDES WITHIN THE [docs](docs/) FOLDER AT THE ROOT OF THIS PROJECT. ## About Sluggo -Sluggo is an open source Issue Tracker for companies, teams, and more. The software is selfhosted, and decentralized in a way that any team can spin up their own instance in minutes! The program started within CSE 183 Spring quarter 2020, and has since had multiple rewrites and expansions. From the [Single Page Application built in Vue](https://github.com/Slugbotics/Sluggo-SPA) as a web based front end, to the [Django REST Application](https://github.com/Slugbotics/Sluggo-API) as the backend, there have also been other expansions such as a [Slack Extension](https://github.com/Slugbotics/Sluggo-Slack) and now an iOS app. +Sluggo is an open source Issue Tracker for companies, teams, and more. The software is selfhosted, and decentralized in a way that any team can spin up their own instance in minutes! This software is broken into two main parts: the [Django REST Application](https://github.com/Slugbotics/Sluggo-API) as the backend and the Sluggo iOS app as a front end. ## Installing the API: -*Note: If you are using the SPA, the login process is simpler* 1) Before using the Sluggo iOS application, make sure to install the backend API following the instructions on the [repository here](https://github.com/Slugbotics/Sluggo-API). 2) After completing the installation process, proceed to the admin dashboard (default `127.0.0.8:8000/admin`) and sign on with your super user account that you just created. 3) In this page, you will be able to create a record for your Team by selecting `Team` on the admin page, and hitting the `Add Team` button in the top right. Just give the team a name and hit save. 4) Finally, after creating your team go through the same process and create a `Member` record for yourself on the Member page through the `Add Member` button on your top right. Set you user role as admin, select your team, and fill out any optional fields such as pronouns or Bio, then hit save. -## SwiftLint +## SwiftLint and Coding Standards The development of Sluggo uses SwiftLint to standardize all changes and coding styles, if you are contributing to Sluggo, please follow the installation instructions for SwiftLint [here](https://github.com/realm/SwiftLint). +In addition, all coding standards and acceptance tests reside within the [documentation folder](docs/), these standards are required to be followed before contributing to the project. + ## Installing the Application -*Note: Once the app is placed on the App Store, the installation process is much simpler* 1) Clone this repository on a Mac device with Xcode installed. - Preferably this is on the same device as the API. Otherwise, you must have the API acessable on your network with a public IP. 3) Open the repository in Xcode, and select build. @@ -27,10 +28,6 @@ The development of Sluggo uses SwiftLint to standardize all changes and coding s - If you have an apple device you would like to test on, follow [Apple's instruction on running apps on your phone](https://developer.apple.com/documentation/xcode/running-your-app-in-the-simulator-or-on-a-device). - Otherwise you may use one of the built in simulators on XCode -## Running the App -1) When launching the app for the first time, fill in your user name and password for your sluggo instance. -2) Your Sluggo instance is the url of the API, make sure to have the entire path listed for example: `http://127.0.0.1:8000/` -3) You will be dropped in the team select page, where you may choose any team your account is apart of. Once you have picked one the homepage will appear. Feel free to poke around! ## Creating Users New users (*for now*) must be created through the admin dashboard similarily to the admin user. As the Sluggo ecosystem progresses, this will be adjusted, but it is now the job of the Sysadmin to create accounts for every member. @@ -50,3 +47,9 @@ After a user has been created, they cannot do much until they are apart of your - Log into the admin dashboard, and select the `Members` field in the **API** section. Select create a member in top right and select the new user's `User Record` in the dropdown and the `Team` record corresponding to your team. - Any optional fields such as role, pronouns, or bio can all be adjusted here before hitting save. - The next time the user logs into the iOS application, they will see the team you created the record for in their Team select and proceed to the rest of the application. + + +## Running the App +1) When launching the app for the first time, fill in your user name and password for your sluggo instance. +2) Your Sluggo instance is the url of the API, make sure to have the entire path listed for example: `http://127.0.0.1:8000/` +3) You will be dropped in the team select page, where you may choose any team your account is apart of. Once you have picked one the homepage will appear. Feel free to poke around! From d7ad61ef0e546562f60b4f7d7769162fd7a439b4 Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Sun, 30 May 2021 23:33:50 -0700 Subject: [PATCH 207/224] adjusted relative link. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fb823be..08443fd 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This repository will hold the source code for the iOS client for the Sluggo issue tracker. It is currently beign developed for CSE 115A at UCSC, and will eventually expand into a fully maintained frontend for the Sluggo project. -# NOTE: ALL DOCUMENTATION FOR CSE115A RESIDES WITHIN THE [docs](docs/) FOLDER AT THE ROOT OF THIS PROJECT. +# NOTE: ALL DOCUMENTATION FOR CSE115A RESIDES WITHIN THE [docs](doc/) FOLDER AT THE ROOT OF THIS PROJECT. ## About Sluggo Sluggo is an open source Issue Tracker for companies, teams, and more. The software is selfhosted, and decentralized in a way that any team can spin up their own instance in minutes! This software is broken into two main parts: the [Django REST Application](https://github.com/Slugbotics/Sluggo-API) as the backend and the Sluggo iOS app as a front end. @@ -18,7 +18,7 @@ Sluggo is an open source Issue Tracker for companies, teams, and more. The softw ## SwiftLint and Coding Standards The development of Sluggo uses SwiftLint to standardize all changes and coding styles, if you are contributing to Sluggo, please follow the installation instructions for SwiftLint [here](https://github.com/realm/SwiftLint). -In addition, all coding standards and acceptance tests reside within the [documentation folder](docs/), these standards are required to be followed before contributing to the project. +In addition, all coding standards and acceptance tests reside within the [documentation folder](doc/), these standards are required to be followed before contributing to the project. ## Installing the Application 1) Clone this repository on a Mac device with Xcode installed. From b01e77b1392945f67472a4455191a3245c48d495 Mon Sep 17 00:00:00 2001 From: Firestarsam Date: Sun, 30 May 2021 23:41:49 -0700 Subject: [PATCH 208/224] Fix to edit where an error in edit would cause the button to change to edit instead of staying on save --- Sluggo/View Controllers/TicketDetailTableViewController.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sluggo/View Controllers/TicketDetailTableViewController.swift b/Sluggo/View Controllers/TicketDetailTableViewController.swift index 2719518..d002f2e 100644 --- a/Sluggo/View Controllers/TicketDetailTableViewController.swift +++ b/Sluggo/View Controllers/TicketDetailTableViewController.swift @@ -212,7 +212,9 @@ class TicketDetailTableViewController: UITableViewController { barButton.title = "Save" } else if barButton.title == "Save" { doSave() - barButton.title = "Edit" + if !ticketTitle.text!.trimmingCharacters(in: .whitespaces).isEmpty { + barButton.title = "Edit" + } } else { editingTicket = false doSave() From 68298a7a74a500dd3402f6bdcc2d458c0b15fc8e Mon Sep 17 00:00:00 2001 From: Andrew Gavgavian Date: Sun, 30 May 2021 23:46:22 -0700 Subject: [PATCH 209/224] This may be a better approach for doSave --- .../TicketDetailTableViewController.swift | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Sluggo/View Controllers/TicketDetailTableViewController.swift b/Sluggo/View Controllers/TicketDetailTableViewController.swift index d002f2e..48ce590 100644 --- a/Sluggo/View Controllers/TicketDetailTableViewController.swift +++ b/Sluggo/View Controllers/TicketDetailTableViewController.swift @@ -156,7 +156,7 @@ class TicketDetailTableViewController: UITableViewController { } } - func doSave() { + func doSave() -> Bool { let title = ticketTitle.text ?? "" let description = ticketDescription.text let date = dueDateSwitch.isOn ? dueDatePicker.date : nil @@ -169,7 +169,7 @@ class TicketDetailTableViewController: UITableViewController { if title.trimmingCharacters(in: .whitespaces).isEmpty { // createError Error self.presentError(error: Exception.runtimeError(message: "Ticket Title Cannot Be Empty.")) - return + return false } if editingTicket { ticket!.title = title @@ -184,6 +184,7 @@ class TicketDetailTableViewController: UITableViewController { DispatchQueue.main.async { self.setEditMode(false) NotificationCenter.default.post(name: .refreshTrigger, object: self) + return true } } } @@ -199,10 +200,12 @@ class TicketDetailTableViewController: UITableViewController { DispatchQueue.main.async { NotificationCenter.default.post(name: .refreshTrigger, object: self) self.dismiss(animated: true, completion: nil) + return true } } } } + return false } @IBAction func editButtonHit(_ sender: Any) { @@ -211,13 +214,12 @@ class TicketDetailTableViewController: UITableViewController { setEditMode(true) barButton.title = "Save" } else if barButton.title == "Save" { - doSave() - if !ticketTitle.text!.trimmingCharacters(in: .whitespaces).isEmpty { + if doSave() { barButton.title = "Edit" } } else { editingTicket = false - doSave() + _ = doSave() } } } From 08381227de1c63d6d9df8ddcb359b236c0272bbf Mon Sep 17 00:00:00 2001 From: Firestarsam Date: Sun, 30 May 2021 23:52:31 -0700 Subject: [PATCH 210/224] Fixed errors from Andrew's blind coding --- .../View Controllers/TicketDetailTableViewController.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sluggo/View Controllers/TicketDetailTableViewController.swift b/Sluggo/View Controllers/TicketDetailTableViewController.swift index 48ce590..b16cb21 100644 --- a/Sluggo/View Controllers/TicketDetailTableViewController.swift +++ b/Sluggo/View Controllers/TicketDetailTableViewController.swift @@ -184,10 +184,10 @@ class TicketDetailTableViewController: UITableViewController { DispatchQueue.main.async { self.setEditMode(false) NotificationCenter.default.post(name: .refreshTrigger, object: self) - return true } } } + return true } else { let ticket = WriteTicketRecord(tag_list: tags, assigned_user: member, @@ -200,12 +200,11 @@ class TicketDetailTableViewController: UITableViewController { DispatchQueue.main.async { NotificationCenter.default.post(name: .refreshTrigger, object: self) self.dismiss(animated: true, completion: nil) - return true } } } + return true } - return false } @IBAction func editButtonHit(_ sender: Any) { From fd1ac2bf3854e73cf7f1a3bde045d59dcb5717b7 Mon Sep 17 00:00:00 2001 From: Troy Ebert Date: Mon, 31 May 2021 13:25:12 -0700 Subject: [PATCH 211/224] Added back button and possibly fixed url --- Sluggo.xcodeproj/project.pbxproj | 4 ++ Sluggo/Models/Managers/InviteManager.swift | 29 +++++++++++ Sluggo/Storyboards/Sidebar.storyboard | 39 ++++++++++++--- .../PendingInvitesViewController.swift | 48 +++++++++---------- 4 files changed, 87 insertions(+), 33 deletions(-) create mode 100644 Sluggo/Models/Managers/InviteManager.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 6569cad..24a911a 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 051AAB0B2630FD6500BCB9C7 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 051AAB0A2630FD6500BCB9C7 /* Constants.swift */; }; 052258702636100B00B49CE4 /* LaunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0522586F2636100B00B49CE4 /* LaunchViewController.swift */; }; 053A259F26645DDD0072A773 /* PendingInvitesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 053A259E26645DDD0072A773 /* PendingInvitesViewController.swift */; }; + 0541572326655FBA002A85F2 /* InviteManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0541572226655FBA002A85F2 /* InviteManager.swift */; }; 05588A12264DB61E000D8674 /* TeamMembers.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 05588A11264DB61E000D8674 /* TeamMembers.storyboard */; }; 05DE0CD7264F11E400D69C61 /* MemberListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05DE0CD6264F11E400D69C61 /* MemberListViewController.swift */; }; 2F2247AC262920AC006C6EE6 /* TokenRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2247AB262920AC006C6EE6 /* TokenRecord.swift */; }; @@ -103,6 +104,7 @@ 051AAB0A2630FD6500BCB9C7 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 0522586F2636100B00B49CE4 /* LaunchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchViewController.swift; sourceTree = ""; }; 053A259E26645DDD0072A773 /* PendingInvitesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingInvitesViewController.swift; sourceTree = ""; }; + 0541572226655FBA002A85F2 /* InviteManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteManager.swift; sourceTree = ""; }; 05588A11264DB61E000D8674 /* TeamMembers.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TeamMembers.storyboard; sourceTree = ""; }; 05DE0CD6264F11E400D69C61 /* MemberListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemberListViewController.swift; sourceTree = ""; }; 2F2247AB262920AC006C6EE6 /* TokenRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenRecord.swift; sourceTree = ""; }; @@ -280,6 +282,7 @@ 5AB1C8EE262FE3210010F85E /* TagManager.swift */, 5AB1C908262FFF500010F85E /* UserManager.swift */, 5A25CFA6264B934400852035 /* StatusManager.swift */, + 0541572226655FBA002A85F2 /* InviteManager.swift */, ); path = Managers; sourceTree = ""; @@ -570,6 +573,7 @@ files = ( 5A9C4C212641113D00B270AB /* TeamSelectorContainerViewController.swift in Sources */, 5A79FFE6262E563F00D04320 /* LoginViewController.swift in Sources */, + 0541572326655FBA002A85F2 /* InviteManager.swift in Sources */, 5AB1C8FC262FF1690010F85E /* TicketRecord.swift in Sources */, 3A90FBB32651D76900BE4F5D /* TicketTabTableViewController.swift in Sources */, 5A25CFDF264BCF2C00852035 /* TicketFilterParameters.swift in Sources */, diff --git a/Sluggo/Models/Managers/InviteManager.swift b/Sluggo/Models/Managers/InviteManager.swift new file mode 100644 index 0000000..ba9eed8 --- /dev/null +++ b/Sluggo/Models/Managers/InviteManager.swift @@ -0,0 +1,29 @@ +// +// InviteManager.swift +// Sluggo +// +// Created by Troy on 5/31/21. +// + +import Foundation + +class InviteManager: TeamPaginatedListable { + + static let urlBase = "api/teams/" + private var identity: AppIdentity + + init(identity: AppIdentity) { + self.identity = identity + } + + func listFromTeams(page: Int, completionHandler: @escaping (Result, Error>) -> Void) { + + let urlString = identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" + + "/invites/" + "?page=\(page)" + let requestBuilder = URLRequestBuilder(url: URL(string: urlString)!) + .setIdentity(identity: identity) + .setMethod(method: .GET) + + JsonLoader.executeCodableRequest(request: requestBuilder.getRequest(), completionHandler: completionHandler) + } +} diff --git a/Sluggo/Storyboards/Sidebar.storyboard b/Sluggo/Storyboards/Sidebar.storyboard index 13ca221..ccf77b3 100644 --- a/Sluggo/Storyboards/Sidebar.storyboard +++ b/Sluggo/Storyboards/Sidebar.storyboard @@ -73,11 +73,24 @@ - + - - - + + + + + + @@ -100,7 +116,13 @@ - + + + + + + + @@ -109,7 +131,6 @@ - @@ -119,6 +140,7 @@ + @@ -202,5 +224,8 @@ + + + diff --git a/Sluggo/View Controllers/PendingInvitesViewController.swift b/Sluggo/View Controllers/PendingInvitesViewController.swift index 43b7112..1fb0a4d 100644 --- a/Sluggo/View Controllers/PendingInvitesViewController.swift +++ b/Sluggo/View Controllers/PendingInvitesViewController.swift @@ -9,10 +9,10 @@ import UIKit class PendingInvitesViewController: UITableViewController { var identity: AppIdentity! - var generateSegueableController: ((AppIdentity, MemberRecord?) -> UIViewController?)? - var generateMemberDetail: ((MemberRecord) -> String?)? + var generateSegueableController: ((AppIdentity, TeamRecord?) -> UIViewController?)? + var generateTeamDetail: ((TeamRecord) -> String?)? private var maxNumber: Int = 0 - private var members: [MemberRecord] = [] + private var teams: [TeamRecord] = [] private var isFetching = false private var semaphore = DispatchSemaphore(value: 1) @@ -28,9 +28,10 @@ class PendingInvitesViewController: UITableViewController { override func viewDidLoad() { self.configureRefreshControl() self.handleRefreshAction() - NotificationCenter.default.addObserver(self, - selector: #selector(handleRefreshAction), - name: .refreshMembers, object: nil) + +// NotificationCenter.default.addObserver(self, +// selector: #selector(handleRefreshAction), +// name: .refreshTeams, object: nil) self.tableView.allowsSelection = (generateSegueableController != nil) } @@ -41,37 +42,26 @@ class PendingInvitesViewController: UITableViewController { } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return members.count + return teams.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "pInviteCell", for: indexPath) as UITableViewCell - let member = self.members[indexPath.row] - cell.textLabel?.text = member.owner.username - cell.detailTextLabel?.text = generateMemberDetail?(member) ?? "" + let team = self.teams[indexPath.row] + cell.textLabel?.text = team.name + cell.detailTextLabel?.text = generateTeamDetail?(team) ?? "" return cell } - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard let generateController = self.generateSegueableController else { return } - - guard let viewController = generateController(identity, self.members[indexPath.row]) else { return } - - if let navController = self.navigationController { - navController.show(viewController, sender: self) - } else { - self.present(viewController, animated: true, completion: nil) - } - } - @objc func handleRefreshAction() { DispatchQueue.global(qos: .userInitiated).async { + self.semaphore.wait() - let onSuccess = { (members: [MemberRecord]) -> Void in - self.members = members + let onSuccess = { (teams: [TeamRecord]) -> Void in + self.teams = teams } let after = { () -> Void in @@ -82,8 +72,8 @@ class PendingInvitesViewController: UITableViewController { self.semaphore.signal() } - let memberManager = MemberManager(identity: self.identity) - unwindPagination(manager: memberManager, + let inviteManager = InviteManager(identity: self.identity) + unwindPagination(manager: inviteManager, startingPage: 1, onSuccess: onSuccess, onFailure: self.presentErrorFromMainThread, @@ -94,4 +84,10 @@ class PendingInvitesViewController: UITableViewController { @IBAction func acceptInvite(_ sender: Any) { print("Accepted!") } + @IBAction func rejectInvite(_ sender: Any) { + print("Rejected!") + } + @IBAction func backButton(_ sender: Any) { + self.dismiss(animated: true, completion: nil) + } } From c19914f93e45757f0294397dffe4117f13e7bd35 Mon Sep 17 00:00:00 2001 From: Troy Ebert Date: Mon, 31 May 2021 16:46:11 -0700 Subject: [PATCH 212/224] Created a codable to handle the TeamInvite json, list now shows correctly --- Sluggo.xcodeproj/project.pbxproj | 4 +++ Sluggo/Models/Codables/InviteRecord.swift | 15 ++++++++ Sluggo/Models/Managers/InviteManager.swift | 9 +++-- Sluggo/Storyboards/Main.storyboard | 8 ++--- Sluggo/Storyboards/TeamMembers.storyboard | 10 +++--- .../PendingInvitesViewController.swift | 34 ++++++++----------- 6 files changed, 46 insertions(+), 34 deletions(-) create mode 100644 Sluggo/Models/Codables/InviteRecord.swift diff --git a/Sluggo.xcodeproj/project.pbxproj b/Sluggo.xcodeproj/project.pbxproj index 24a911a..e8cf872 100644 --- a/Sluggo.xcodeproj/project.pbxproj +++ b/Sluggo.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 052258702636100B00B49CE4 /* LaunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0522586F2636100B00B49CE4 /* LaunchViewController.swift */; }; 053A259F26645DDD0072A773 /* PendingInvitesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 053A259E26645DDD0072A773 /* PendingInvitesViewController.swift */; }; 0541572326655FBA002A85F2 /* InviteManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0541572226655FBA002A85F2 /* InviteManager.swift */; }; + 0541572826659F2E002A85F2 /* InviteRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0541572726659F2E002A85F2 /* InviteRecord.swift */; }; 05588A12264DB61E000D8674 /* TeamMembers.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 05588A11264DB61E000D8674 /* TeamMembers.storyboard */; }; 05DE0CD7264F11E400D69C61 /* MemberListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05DE0CD6264F11E400D69C61 /* MemberListViewController.swift */; }; 2F2247AC262920AC006C6EE6 /* TokenRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2247AB262920AC006C6EE6 /* TokenRecord.swift */; }; @@ -105,6 +106,7 @@ 0522586F2636100B00B49CE4 /* LaunchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchViewController.swift; sourceTree = ""; }; 053A259E26645DDD0072A773 /* PendingInvitesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingInvitesViewController.swift; sourceTree = ""; }; 0541572226655FBA002A85F2 /* InviteManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteManager.swift; sourceTree = ""; }; + 0541572726659F2E002A85F2 /* InviteRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteRecord.swift; sourceTree = ""; }; 05588A11264DB61E000D8674 /* TeamMembers.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TeamMembers.storyboard; sourceTree = ""; }; 05DE0CD6264F11E400D69C61 /* MemberListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemberListViewController.swift; sourceTree = ""; }; 2F2247AB262920AC006C6EE6 /* TokenRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenRecord.swift; sourceTree = ""; }; @@ -268,6 +270,7 @@ 3A150A6626365A9A0052934B /* StatusRecord.swift */, 3A150A6B26365B670052934B /* TagRecord.swift */, 7DC54AE426437AB3000C8300 /* PinnedTicketRecord.swift */, + 0541572726659F2E002A85F2 /* InviteRecord.swift */, ); path = Codables; sourceTree = ""; @@ -577,6 +580,7 @@ 5AB1C8FC262FF1690010F85E /* TicketRecord.swift in Sources */, 3A90FBB32651D76900BE4F5D /* TicketTabTableViewController.swift in Sources */, 5A25CFDF264BCF2C00852035 /* TicketFilterParameters.swift in Sources */, + 0541572826659F2E002A85F2 /* InviteRecord.swift in Sources */, 5A25CFE7264BD73D00852035 /* TicketFilterTableViewCell.swift in Sources */, 7D1DA6B7262B866D00E7C12D /* SidebarData.swift in Sources */, 3A90FBB526531AC600BE4F5D /* AdminUserRolesTableViewController.swift in Sources */, diff --git a/Sluggo/Models/Codables/InviteRecord.swift b/Sluggo/Models/Codables/InviteRecord.swift new file mode 100644 index 0000000..51766d6 --- /dev/null +++ b/Sluggo/Models/Codables/InviteRecord.swift @@ -0,0 +1,15 @@ +// +// InviteRecord.swift +// Sluggo +// +// Created by Troy Ebert on 5/31/21. +// + +import Foundation +import NullCodable + +// swiftlint:disable identifier_name +struct InviteRecord: Codable { + var id: Int + var team: TeamRecord +} diff --git a/Sluggo/Models/Managers/InviteManager.swift b/Sluggo/Models/Managers/InviteManager.swift index ba9eed8..d233391 100644 --- a/Sluggo/Models/Managers/InviteManager.swift +++ b/Sluggo/Models/Managers/InviteManager.swift @@ -7,19 +7,18 @@ import Foundation -class InviteManager: TeamPaginatedListable { +class InviteManager { - static let urlBase = "api/teams/" + static let urlBase = "api/users/invites/" private var identity: AppIdentity init(identity: AppIdentity) { self.identity = identity } - func listFromTeams(page: Int, completionHandler: @escaping (Result, Error>) -> Void) { + func getInvites(completionHandler: @escaping (Result<[InviteRecord], Error>) -> Void) { - let urlString = identity.baseAddress + TeamManager.urlBase + "\(identity.team!.id)" - + "/invites/" + "?page=\(page)" + let urlString = identity.baseAddress + InviteManager.urlBase let requestBuilder = URLRequestBuilder(url: URL(string: urlString)!) .setIdentity(identity: identity) .setMethod(method: .GET) diff --git a/Sluggo/Storyboards/Main.storyboard b/Sluggo/Storyboards/Main.storyboard index b29852a..b889460 100644 --- a/Sluggo/Storyboards/Main.storyboard +++ b/Sluggo/Storyboards/Main.storyboard @@ -1,8 +1,8 @@ - + - + @@ -170,7 +170,7 @@ - + @@ -216,7 +216,7 @@

fW5vEu$9bexkHfRSr)Mhh zOv5>c(~*6lz4n$-6~vq-hi=+N+K|mkUp^A0^~W|R>V^=;FitCogp#K=p$dw8;{<59 zh*oA)1*J4cN1U|xxEIn4@JJCp>3u>?=##JuR}A)bg33Lu;UVb<4-9>aT z8WuqrI_-KDthqjGAro*+F4<$#TH?;1SyBLc0XtDyc5bS+5HW9x<<~Wz#88`Jyt$NF zxgxi7OKL+`(NvVxC}8UPsAy>x44&4Qk1C-5G|K1bypznw&o16^J%vKV#(8Is(do6d z`_4FWWUGr5zv4B?Jb5}VN@jg5*UKn{!dl^#j*%|az?j?v*9cb$xdFOK^GUl&W58_k zd{bMFvsP1BQ%>X^gG$O(U;}CnNQH&Bn^=pwKg^<`?VnvmI67P9lw2s8!g*n3v4=Yt zPgbF>eRCgd=zOSHQr+R;QF72dL3a|4)yQcv+Lf}|X0q9Kx7l{F@%FXxK5pavw6rFn zv?i!j?((%oOwUR6*)icTLRLJ+6K9ZTAcmpGrH{v_m%ft^Tqd0A&xp3kX71a45{9v4 z>f4E&x>DFDSuWyRd zyKxq>2IYvy7$2||?3;E>nxEXUaV5PG+0<#p6vmc33m}58ptSR6ZHRo6!+LVrgxxE~ z82K;`MQ8nqox>K!N5$6Tu;U?w*zIxkWPSaG$?i8DgsxJo_UXJQVyeXF&NsixOjlqf z8k}41qz6Yn5S);)OHrsFL)!5qqdZcG)O@fGi`!TzFcqoZj3Y2M_sjWvz<@(vb#k2B z4wB&hVHn%Xmz|NrF@5w0L$beYyx`k&iVnde4^}x_*?SY8$mSMZ16=w}i;{s0d4mR6g=W|s!Q2ZdG$GikczzoS(%g-y$;!vI&oe3(H zbFG^d7EIB+F~DPwB0QqBI{W)7n2+{dnAPamITM@}7=veN+0L_Q*;dYMGK5{w%^Nnn ztQ%-A+fiyGfxG|iv6BT2jDw((DlfBS_eg{NSTLbaMld4WDHeGzKx~on9vfcAnhpju z4Q5!~J+N?%hxX5TWb(lN&#Cag!Y;&r;1U1Q7ceqM@qfWDzz_rw4u`#KB~^ zVQ;eezXXOPSPBT*8&%#U;{{M|MWU4 z=A&%`h!MTG%~m-F)L_yia44F#o*Fkl^l+X+*JzqC%z|fK&{1Bs`hA8s=k|Ak@j%0w4x`!-ynK2j`iP%A0{aYmE^bL z|0&xeA@&}Ahks;HD4cBkR~ZNlA~%d*WDpock_@Q*B7?%EB!1Tefxr^K>w!QJG86Ud zS`bKLk7WAA9taA9LjIJ&$zA9-Jt>Jl_W=j}t_P7oAb#H)L=pn|Lr)S8`#oj|3`#zo zzxstr!C>G&WQad<1%*S&bk)zbZZ2kI7S4s9OrKr0@wD74(OF$bN4LFh^}W4*XVboa aCL=1YZe}iS--}5u3|x}@ml(?0D*pkT=<759 literal 0 HcmV?d00001 diff --git a/doc/Sprint Plans/Sprint 1 Plan.pdf b/doc/Sprint Plans/Sprint 1 Plan.pdf new file mode 100644 index 0000000000000000000000000000000000000000..835a368e4c418c06b4ef820fd899622ebf6ec95b GIT binary patch literal 96683 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

#+A=H1@y0F?@wA;k+#rC?R2>cQPkt zDDrftVUIBF6(H(J4n*)p`n+6UG|A;ISN!;NFWI%+aZ|QIelaS9@}$Q{hbxq6{@m1# zmB)_bkvUflE54|G!%erifpqgZv^7Nk9C_Ok+ka*g@4;1k+Vgnh;6d9W7vU(w)GW{D zolS{}2VqZ%DZ8w#LhP8v`f4>eI%l?BihI3niU-3334VEin zGtzOeZJ=%FStx$^r*^e~@c>(Y;yJ5=v$(n9Mfykn!jBp0UwQEvC?;u2U=n}9n2HR3 zD*mJ{#Q7_z;TuAw7vH+wK< zhVsz0q%UTNYanKd)mJ6ki5aC8yp^aG#S_7DSHgj?+aZOd$ox&fMC3Y0Ifo!;CAT<- z@=d^e>-`-n zq$YHdE57@I%&X6#FP0-uMjbYwxR=|85>&(}{!=W1;O?3@_idF> zSjk~YW~LBKW9p-4_{ZmCD$^{=lhFL2YbX;rBwOS9@8d*+Yg?0bvlKVN>-E8(4Wn%d z9#w|xTcf8&I_gzOzh^*G5Q>uwpVdq4Lkf|sbHZu}_6kCkpc?39~ z=W;t;NnB-eIZ)a5%JV=ZIs~~I;{6sS=bie*o&uW*u?Ef$J(nZyUTm;g29@7!ZBFcr z;EZ>JIZGYlgfMI&Zy%Q4*Hs%7M4X@eAW!&&Be>YIwzg*2&`eKWsH%pYIXqBcS?Y}% zzHt&B#WMJyIh%Bz*YVC6(R0-cI%ys$VTB5y;42buzjLYMWJ!AvOc<6*ANS< zAs}K1nYtz4W+wx~hJ5NvtjmwFHGg2!;#GOPO)umZ$Q!4|#V38Z%1=PZpx-7~nLJ8j zK|8RInwh*a_2ViQ_(u5V!z?CgrO31;+nfJrP;3>t2d5 z`T5Nr<%m%z2AF=yj7E+{;VCKI@RlG<-;2p7-AE ziTIqB$;fDFpm!8rTq_28TMH*<=C_h_YrW)uoz3#wtC`*Xm!fzHJfE0<|ud%9eXvC2}BjppXUbgFT!~|2dIvz zy>;Gy%wjj|lzXHUo5sBjzNtoUYndl{?gX}q&he@$rdjfJ+kxAw$yJoj%p7aY?UXPm zSrLj!&s0lwD)UQK5${u5=1KKqCCbIGHi&f?oEh{-ACoCuht<9XE#JEIE_FO5zseh# zcpAOlc8DWAWgZK8-omw433=)T-E@dION4bumS5HmQ?g;XBTzw4n-Zf%-)bcq-edM@ zA3ODL6PYULq}5{C(Gk$}>?x8HL0e*~R1FVL3@?;j$R4Tc-q<fPr%Vc>DA~I&(OGETL0dn-fF64wIhycBRB&ciJ~h84o{C z`x-Z!Yi5mx*6(X0(X~i0klU&lH_mh1xYf!)Pk;EZ@+>Hrf_i;7qF63xoVEs1|4gzHUiG*`Ab1<@Uvay3G z|ABNcdm2_%BHKzcv0h7WV%K{{08>X5(T8 zursp*fXwV5%$tpy<-fzH{{iFuZ$KFcwf;|>O!vE-WgjzA@V!8A(oFxRlsQeXeo7H$ z?IDVs7ucYhl86yOzvk{;Vx|EC*5bwFb6>pi3ct%b#rJbe*%)~71BNYhHC8xC`SHjf zFekFDC0FU15~O%%O%z+Jk1SzD;NMABb^;X8NWWN9x9VoW%X)qAuu3MZ>)cSx(!Gt|AT;eOJeARm$v&YYozwLG zbSsaX|2W@wcHCj&=CdI1qny~*t)UJ_Ju9xMU7Vc`t(cCArp6I)aOv5AYglyk486DH zsPW&IKJb5D6#sVhe}g^$y&C>s!OlO+?*9Ti|2XMCR;Ih7i75i;Py!GEbg<)}4*vz98W!ce=^mu zBJ(t`@Bx^ZWhra$kuaShX08brerK-$Ch#)g3Q?w*=}DJ1GUCO_xP^Bmleb3&goV=; z-ca?~o^WMf7m*x%h_sN0qVzsDRM{@zQ_M=O{{6@3J7}J6jMDq_#d#bH^mQC#$+oGn aoi3)vPzh_JbL@RwO{6VJyRGZ}`S<~Ry!*}o literal 0 HcmV?d00001 diff --git a/doc/Sprint Plans/Sprint 2 Plan.pdf b/doc/Sprint Plans/Sprint 2 Plan.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7430530511e186a2a0d916ed5621676ef69be412 GIT binary patch literal 105628 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%0Dg?1rC;fv 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 literal 0 HcmV?d00001 diff --git a/doc/Sprint Plans/Sprint 3 Plan.pdf b/doc/Sprint Plans/Sprint 3 Plan.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4c9c42fe2a8f035f2ae55573093629600d3e80ad GIT binary patch literal 140464 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

s$O$06e?a(cakdlFTeU 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 literal 0 HcmV?d00001 diff --git a/doc/Sprint Plans/Sprint 4 Plan.pdf b/doc/Sprint Plans/Sprint 4 Plan.pdf new file mode 100644 index 0000000000000000000000000000000000000000..cab68ff36bd35b6577e912340e93c8df5558c129 GIT binary patch literal 101513 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 z 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 literal 0 HcmV?d00001 diff --git a/doc/Sprint Reports/Sprint 1 Report.pdf b/doc/Sprint Reports/Sprint 1 Report.pdf new file mode 100644 index 0000000000000000000000000000000000000000..651e754c62f29a2c0530458c1619278c118379ab GIT binary patch literal 146153 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
_6U#2|{1+R^FM4nyq2{!XiZz4uYOR1$ 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 literal 0 HcmV?d00001 diff --git a/doc/Sprint Reports/Sprint 2 Report.pdf b/doc/Sprint Reports/Sprint 2 Report.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a3a94319a186e127cbcebf7d941d6159e07400af GIT binary patch literal 103418 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 LCeqMIDNFx9K-VE; literal 0 HcmV?d00001 diff --git a/doc/Sprint Reports/Sprint 3 Report.pdf b/doc/Sprint Reports/Sprint 3 Report.pdf new file mode 100644 index 0000000000000000000000000000000000000000..fa57fd6553f8c78a1db3939aa89e38a1f8458bda GIT binary patch literal 91996 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

FDoN5noHK{%j5i@oN ziYvQXnk|aG{s~2H6Q^nP*y?EBHRQ_QUia%stTRKCG(UZ6D3^aNKBbf`t{naXdfu&Y zt{LFqisL^eXn{rHaD`9cuXhpzpAFl7b+lZ^{g$=s7c~Oq&p{^wLJ~z79h|2~=h~C< z#n@E5LbyjLrMc9`3|`?W>F?RGi z#ETh)rE-=gMn*W5NyKS^AY0O`j)}t=X6VLedRXS2Af~5selK$MVMhl{W^ZJ?4^Nr8?9%~RikVVSS=VYLm4Y8u zY%ud7X6B@R2BLSj8F#XXQBGpkZUU09DK=F$vkC2&6>TjV%BUwEKGJNESHteP;g*!y zBm*~a%b~Rpxc&ypF(44eF>%2px_~9c$*}jOp||NEA!9jEgUm5t(KZpuJf6#Gj3~ha3g% ^tA$@JJQm>Oeq+wtzfdTn^oiI+|; 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%0DqbD3Rv6)( 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 Cb#{Wi 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 literal 0 HcmV?d00001 diff --git a/doc/Sprint Reports/Sprint 4 Report.pdf b/doc/Sprint Reports/Sprint 4 Report.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3b39ce352c3b4cdbade87456592eed49eacadc1d GIT binary patch literal 116925 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 literal 0 HcmV?d00001 diff --git a/doc/System and Unit Test Report.pdf b/doc/System and Unit Test Report.pdf new file mode 100644 index 0000000000000000000000000000000000000000..358328ec4e6e7124b86f9ea22ce09730f04e54dd GIT binary patch literal 83860 zcmd42Wpo_PvL$MZ*GspGfORIW{c6%==;uj@7y~VX1(=h z)|&oNm6aWtS+Og+vv=%hQh8xfT1Gk+7}EK(lN}gFLIy%xLkk!l9(rYSCo2FUg_66Y z6Tq6#z{Z$R#m3x;P#NIpM5qX`vvqKyGqyEyqlBTCcd#{fHUc<&8e5th(0|TIXwAt? zC}wMG`Z4BD)b7{BiO83afX!H_h;NPRPojJ0B8EOl6%<<~TRA%&i_Y6*B)GGJNg) z1Ya$C=;*fWAh-22FW2ww%|GKQx^k>M$ruPEhF0o}j9qHZ0th!-=*9HJFw4;^Wux{RIW}mFf`Zi3mj?{c41F zw|$}`4HI{{n@^-}EJqgL939m20~;%0Df7g^`UYq>4*V_5PJR_R7p*}dquX7CQ2!QG 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 H6vY2OTOK1_ literal 0 HcmV?d00001 diff --git a/doc/Working Prototype Known Problems Report.pdf b/doc/Working Prototype Known Problems Report.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3d560caaff554204847d75f0c700ab3eeb36be15 GIT binary patch literal 25789 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

Ts=~V@ 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 literal 0 HcmV?d00001