diff --git a/iOS/.gitignore b/iOS/.gitignore new file mode 100644 index 000000000..8d9ab5d53 --- /dev/null +++ b/iOS/.gitignore @@ -0,0 +1,120 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/cocoapods,swift,swiftpackagemanager,xcode +# Edit at https://www.toptal.com/developers/gitignore?templates=cocoapods,swift,swiftpackagemanager,xcode + +### CocoaPods ### +## CocoaPods GitIgnore Template + +# CocoaPods - Only use to conserve bandwidth / Save time on Pushing +# - Also handy if you have a large number of dependant pods +# - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGNORE THE LOCK FILE +Pods/ + +### Swift ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# Pods/ +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# Add this lines if you are using Accio dependency management (Deprecated since Xcode 12) +# Dependencies/ +# .accio/ + +# fastlane +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + +### SwiftPackageManager ### +Packages +xcuserdata +*.xcodeproj + + +### Xcode ### +# Xcode +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + + + + +## Gcc Patch +/*.gcno + +### Xcode Patch ### +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcworkspace/contents.xcworkspacedata +**/xcshareddata/WorkspaceSettings.xcsettings + +# End of https://www.toptal.com/developers/gitignore/api/cocoapods,swift,swiftpackagemanager,xcode diff --git a/iOS/issueTrackerApp/issueTrackerApp.xcodeproj/project.pbxproj b/iOS/issueTrackerApp/issueTrackerApp.xcodeproj/project.pbxproj new file mode 100644 index 000000000..34ee58cea --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp.xcodeproj/project.pbxproj @@ -0,0 +1,1080 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 52; + objects = { + +/* Begin PBXBuildFile section */ + 883F5148266F3EAF0055EC9E /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 883F5147266F3EAF0055EC9E /* LoginViewController.swift */; }; + 883F5159266F502F0055EC9E /* Issue.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 883F5157266F502F0055EC9E /* Issue.storyboard */; }; + 883F5161266F505E0055EC9E /* Label.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 883F5160266F505E0055EC9E /* Label.storyboard */; }; + 883F516A266F508B0055EC9E /* Milestones.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 883F5168266F508B0055EC9E /* Milestones.storyboard */; }; + 883F5172266F50A40055EC9E /* Setting.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 883F5171266F50A40055EC9E /* Setting.storyboard */; }; + 883F5185266F5F090055EC9E /* IssueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 883F5184266F5F090055EC9E /* IssueViewController.swift */; }; + 883F519E2670940E0055EC9E /* IssueCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 883F519C2670940E0055EC9E /* IssueCell.swift */; }; + 883F519F2670940E0055EC9E /* IssueCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 883F519D2670940E0055EC9E /* IssueCell.xib */; }; + 883F51A42670AAAB0055EC9E /* TagStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 883F51A32670AAAB0055EC9E /* TagStackView.swift */; }; + 883F51A92670ABDC0055EC9E /* TagLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 883F51A82670ABDC0055EC9E /* TagLabel.swift */; }; + 883F51AE2670B2F70055EC9E /* UIColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 883F51AD2670B2F70055EC9E /* UIColorExtension.swift */; }; + 883F51B62671E1710055EC9E /* CustomBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 883F51B52671E1710055EC9E /* CustomBarButtonItem.swift */; }; + 883F51C7267200EA0055EC9E /* Issue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 883F51C6267200EA0055EC9E /* Issue.swift */; }; + 883F51CC2672041A0055EC9E /* Milestones.swift in Sources */ = {isa = PBXBuildFile; fileRef = 883F51CB2672041A0055EC9E /* Milestones.swift */; }; + 883F51D4267204520055EC9E /* LabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 883F51D3267204520055EC9E /* LabelModel.swift */; }; + 883F51DC267204DC0055EC9E /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 883F51DB267204DC0055EC9E /* Comment.swift */; }; + 883F522A267312CE0055EC9E /* IssueEndPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 883F5229267312CE0055EC9E /* IssueEndPoint.swift */; }; + 883F5234267318240055EC9E /* IssueRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 883F5233267318240055EC9E /* IssueRequest.swift */; }; + 883F5239267654CB0055EC9E /* IssueNetworkController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 883F5238267654CB0055EC9E /* IssueNetworkController.swift */; }; + 88C3A9F426773B660090FAD8 /* IssueViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C3A9F326773B660090FAD8 /* IssueViewModel.swift */; }; + 88C3A9F9267742140090FAD8 /* NotificationNameExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C3A9F8267742140090FAD8 /* NotificationNameExtension.swift */; }; + 88C3A9FE26785B160090FAD8 /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C3A9FD26785B160090FAD8 /* Status.swift */; }; + 88C3AA0B267874470090FAD8 /* IssuePatchRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C3AA0A267874470090FAD8 /* IssuePatchRequest.swift */; }; + 88C3AA132678F7330090FAD8 /* IssueSelectTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C3AA122678F7330090FAD8 /* IssueSelectTableViewController.swift */; }; + 930FAE86266E112700422968 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 930FAE85266E112700422968 /* AppDelegate.swift */; }; + 930FAE88266E112700422968 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 930FAE87266E112700422968 /* SceneDelegate.swift */; }; + 930FAE8D266E112700422968 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 930FAE8B266E112700422968 /* Main.storyboard */; }; + 930FAE8F266E112900422968 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 930FAE8E266E112900422968 /* Assets.xcassets */; }; + 930FAE92266E112900422968 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 930FAE90266E112900422968 /* LaunchScreen.storyboard */; }; + 930FAE9D266E112900422968 /* issueTrackerAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 930FAE9C266E112900422968 /* issueTrackerAppTests.swift */; }; + 930FAEA8266E112A00422968 /* issueTrackerAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 930FAEA7266E112A00422968 /* issueTrackerAppUITests.swift */; }; + 930FAEC7266E13F500422968 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 930FAEC6266E13F500422968 /* Alamofire */; }; + 931BAE1E26730C8600CD9244 /* SettingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931BAE1D26730C8600CD9244 /* SettingViewController.swift */; }; + 931BAE2F267310EB00CD9244 /* JWTDecode in Frameworks */ = {isa = PBXBuildFile; productRef = 931BAE2E267310EB00CD9244 /* JWTDecode */; }; + 931BAE41267343B900CD9244 /* AddIssueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931BAE40267343B900CD9244 /* AddIssueViewController.swift */; }; + 931BAE4626734FA500CD9244 /* MoreInformationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931BAE4526734FA500CD9244 /* MoreInformationViewController.swift */; }; + 931BAE4C26737F5100CD9244 /* MarkdownKit in Frameworks */ = {isa = PBXBuildFile; productRef = 931BAE4B26737F5100CD9244 /* MarkdownKit */; }; + 931BAE5526749FCD00CD9244 /* MoreInfoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931BAE5426749FCD00CD9244 /* MoreInfoCell.swift */; }; + 931BAE5C2674B90100CD9244 /* MoreInfoTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931BAE5B2674B90100CD9244 /* MoreInfoTableViewDataSource.swift */; }; + 931BAE622674DAF100CD9244 /* MoreInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931BAE612674DAF100CD9244 /* MoreInfo.swift */; }; + 931BAE6B2674EE6F00CD9244 /* MoreInfos.json in Resources */ = {isa = PBXBuildFile; fileRef = 931BAE6A2674EE6F00CD9244 /* MoreInfos.json */; }; + 931BAE702674F0D000CD9244 /* DummyDataLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931BAE6F2674F0D000CD9244 /* DummyDataLoader.swift */; }; + 931BAE762674FC4700CD9244 /* ArrayTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931BAE752674FC4700CD9244 /* ArrayTableViewDataSource.swift */; }; + 931BAE7E2675A27000CD9244 /* UITableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931BAE7D2675A27000CD9244 /* UITableView.swift */; }; + 931BAEA52677973200CD9244 /* AddIssueViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931BAEA42677973200CD9244 /* AddIssueViewModel.swift */; }; + 931BAEAD2678843200CD9244 /* CommentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931BAEAC2678843200CD9244 /* CommentViewController.swift */; }; + 931BAEB226788EA700CD9244 /* MarkdownViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931BAEB126788EA700CD9244 /* MarkdownViewController.swift */; }; + 9343E2CA266EF9CD0086F977 /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9343E2C9266EF9CD0086F977 /* KeychainManager.swift */; }; + 9343E2D3266EFBA70086F977 /* GitHubEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9343E2D2266EFBA70086F977 /* GitHubEndpoint.swift */; }; + 9343E2D8266F03E90086F977 /* LoginNetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9343E2D7266F03E90086F977 /* LoginNetworkManager.swift */; }; + 9343E2F2266F5CC70086F977 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9343E2F1266F5CC70086F977 /* Coordinator.swift */; }; + 9343E2F7266F5E890086F977 /* MainTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9343E2F6266F5E890086F977 /* MainTabBarController.swift */; }; + 9343E300266F603C0086F977 /* AccessTokenRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9343E2FF266F603C0086F977 /* AccessTokenRequest.swift */; }; + 9343E305266F60B50086F977 /* NetworkRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9343E304266F60B50086F977 /* NetworkRequest.swift */; }; + 9343E30D266F90EF0086F977 /* LoginFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9343E30C266F90EF0086F977 /* LoginFlowCoordinator.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 930FAE99266E112900422968 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 930FAE7A266E112700422968 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 930FAE81266E112700422968; + remoteInfo = issueTrackerApp; + }; + 930FAEA4266E112A00422968 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 930FAE7A266E112700422968 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 930FAE81266E112700422968; + remoteInfo = issueTrackerApp; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 883F5147266F3EAF0055EC9E /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LoginViewController.swift; path = issueTrackerApp/Presentation/UI/ViewController/Login/LoginViewController.swift; sourceTree = SOURCE_ROOT; }; + 883F5153266F442C0055EC9E /* issueTrackerApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = issueTrackerApp.entitlements; sourceTree = ""; }; + 883F5158266F502F0055EC9E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = issueTrackerApp/Base.lproj/Issue.storyboard; sourceTree = ""; }; + 883F5160266F505E0055EC9E /* Label.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Label.storyboard; sourceTree = ""; }; + 883F5169266F508B0055EC9E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = issueTrackerApp/Base.lproj/Milestones.storyboard; sourceTree = ""; }; + 883F5171266F50A40055EC9E /* Setting.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Setting.storyboard; sourceTree = ""; }; + 883F5184266F5F090055EC9E /* IssueViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = IssueViewController.swift; path = issueTrackerApp/Presentation/UI/ViewController/Issue/IssueViewController.swift; sourceTree = SOURCE_ROOT; }; + 883F519C2670940E0055EC9E /* IssueCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = IssueCell.swift; path = issueTrackerApp/Presentation/UI/ViewController/Issue/IssueCell.swift; sourceTree = SOURCE_ROOT; }; + 883F519D2670940E0055EC9E /* IssueCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = IssueCell.xib; path = issueTrackerApp/Presentation/UI/ViewController/Issue/IssueCell.xib; sourceTree = SOURCE_ROOT; }; + 883F51A32670AAAB0055EC9E /* TagStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TagStackView.swift; path = issueTrackerApp/Presentation/UI/ViewController/Issue/TagStackView.swift; sourceTree = SOURCE_ROOT; }; + 883F51A82670ABDC0055EC9E /* TagLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TagLabel.swift; path = issueTrackerApp/Presentation/UI/ViewController/Issue/TagLabel.swift; sourceTree = SOURCE_ROOT; }; + 883F51AD2670B2F70055EC9E /* UIColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColorExtension.swift; sourceTree = ""; }; + 883F51B52671E1710055EC9E /* CustomBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CustomBarButtonItem.swift; path = issueTrackerApp/Presentation/UI/ViewController/Issue/CustomBarButtonItem.swift; sourceTree = SOURCE_ROOT; }; + 883F51C6267200EA0055EC9E /* Issue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Issue.swift; path = issueTrackerApp/Domain/Entity/Issue.swift; sourceTree = SOURCE_ROOT; }; + 883F51CB2672041A0055EC9E /* Milestones.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Milestones.swift; path = issueTrackerApp/Domain/Entity/Milestones.swift; sourceTree = SOURCE_ROOT; }; + 883F51D3267204520055EC9E /* LabelModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LabelModel.swift; path = issueTrackerApp/Domain/Entity/LabelModel.swift; sourceTree = SOURCE_ROOT; }; + 883F51DB267204DC0055EC9E /* Comment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Comment.swift; path = issueTrackerApp/Domain/Entity/Comment.swift; sourceTree = SOURCE_ROOT; }; + 883F5229267312CE0055EC9E /* IssueEndPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueEndPoint.swift; sourceTree = ""; }; + 883F5233267318240055EC9E /* IssueRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueRequest.swift; sourceTree = ""; }; + 883F5238267654CB0055EC9E /* IssueNetworkController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = IssueNetworkController.swift; path = issueTrackerApp/Data/Network/Issue/Request/IssueNetworkController.swift; sourceTree = SOURCE_ROOT; }; + 88C3A9F326773B660090FAD8 /* IssueViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueViewModel.swift; sourceTree = ""; }; + 88C3A9F8267742140090FAD8 /* NotificationNameExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationNameExtension.swift; sourceTree = ""; }; + 88C3A9FD26785B160090FAD8 /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = ""; }; + 88C3AA0A267874470090FAD8 /* IssuePatchRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssuePatchRequest.swift; sourceTree = ""; }; + 88C3AA122678F7330090FAD8 /* IssueSelectTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueSelectTableViewController.swift; sourceTree = ""; }; + 930FAE82266E112700422968 /* issueTrackerApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = issueTrackerApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 930FAE85266E112700422968 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 930FAE87266E112700422968 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 930FAE8C266E112700422968 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 930FAE8E266E112900422968 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 930FAE91266E112900422968 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 930FAE93266E112900422968 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 930FAE98266E112900422968 /* issueTrackerAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = issueTrackerAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 930FAE9C266E112900422968 /* issueTrackerAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = issueTrackerAppTests.swift; sourceTree = ""; }; + 930FAE9E266E112900422968 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 930FAEA3266E112A00422968 /* issueTrackerAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = issueTrackerAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 930FAEA7266E112A00422968 /* issueTrackerAppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = issueTrackerAppUITests.swift; sourceTree = ""; }; + 930FAEA9266E112A00422968 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 931BAE1D26730C8600CD9244 /* SettingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingViewController.swift; sourceTree = ""; }; + 931BAE40267343B900CD9244 /* AddIssueViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddIssueViewController.swift; sourceTree = ""; }; + 931BAE4526734FA500CD9244 /* MoreInformationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreInformationViewController.swift; sourceTree = ""; }; + 931BAE5426749FCD00CD9244 /* MoreInfoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreInfoCell.swift; sourceTree = ""; }; + 931BAE5B2674B90100CD9244 /* MoreInfoTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreInfoTableViewDataSource.swift; sourceTree = ""; }; + 931BAE612674DAF100CD9244 /* MoreInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreInfo.swift; sourceTree = ""; }; + 931BAE6A2674EE6F00CD9244 /* MoreInfos.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = MoreInfos.json; sourceTree = ""; }; + 931BAE6F2674F0D000CD9244 /* DummyDataLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyDataLoader.swift; sourceTree = ""; }; + 931BAE752674FC4700CD9244 /* ArrayTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayTableViewDataSource.swift; sourceTree = ""; }; + 931BAE7D2675A27000CD9244 /* UITableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITableView.swift; sourceTree = ""; }; + 931BAEA42677973200CD9244 /* AddIssueViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddIssueViewModel.swift; sourceTree = ""; }; + 931BAEAC2678843200CD9244 /* CommentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentViewController.swift; sourceTree = ""; }; + 931BAEB126788EA700CD9244 /* MarkdownViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownViewController.swift; sourceTree = ""; }; + 9343E2C9266EF9CD0086F977 /* KeychainManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManager.swift; sourceTree = ""; }; + 9343E2D2266EFBA70086F977 /* GitHubEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitHubEndpoint.swift; sourceTree = ""; }; + 9343E2D7266F03E90086F977 /* LoginNetworkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginNetworkManager.swift; sourceTree = ""; }; + 9343E2F1266F5CC70086F977 /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; + 9343E2F6266F5E890086F977 /* MainTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarController.swift; sourceTree = ""; }; + 9343E2FF266F603C0086F977 /* AccessTokenRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessTokenRequest.swift; sourceTree = ""; }; + 9343E304266F60B50086F977 /* NetworkRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkRequest.swift; sourceTree = ""; }; + 9343E30C266F90EF0086F977 /* LoginFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginFlowCoordinator.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 930FAE7F266E112700422968 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 931BAE2F267310EB00CD9244 /* JWTDecode in Frameworks */, + 931BAE4C26737F5100CD9244 /* MarkdownKit in Frameworks */, + 930FAEC7266E13F500422968 /* Alamofire in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 930FAE95266E112900422968 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 930FAEA0266E112A00422968 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 883F514F266F3F060055EC9E /* Login */ = { + isa = PBXGroup; + children = ( + 883F5147266F3EAF0055EC9E /* LoginViewController.swift */, + ); + path = Login; + sourceTree = ""; + }; + 883F5182266F5EE80055EC9E /* Issue */ = { + isa = PBXGroup; + children = ( + 883F5184266F5F090055EC9E /* IssueViewController.swift */, + 88C3AA122678F7330090FAD8 /* IssueSelectTableViewController.swift */, + 931BAE40267343B900CD9244 /* AddIssueViewController.swift */, + 931BAE4526734FA500CD9244 /* MoreInformationViewController.swift */, + 931BAEAC2678843200CD9244 /* CommentViewController.swift */, + 931BAEB126788EA700CD9244 /* MarkdownViewController.swift */, + ); + path = Issue; + sourceTree = ""; + }; + 883F522E267312D60055EC9E /* EndPoint */ = { + isa = PBXGroup; + children = ( + 883F5229267312CE0055EC9E /* IssueEndPoint.swift */, + ); + path = EndPoint; + sourceTree = ""; + }; + 883F5232267317BB0055EC9E /* Request */ = { + isa = PBXGroup; + children = ( + 883F5233267318240055EC9E /* IssueRequest.swift */, + 88C3AA0A267874470090FAD8 /* IssuePatchRequest.swift */, + ); + path = Request; + sourceTree = ""; + }; + 930FAE79266E112700422968 = { + isa = PBXGroup; + children = ( + 930FAE84266E112700422968 /* issueTrackerApp */, + 930FAE9B266E112900422968 /* issueTrackerAppTests */, + 930FAEA6266E112A00422968 /* issueTrackerAppUITests */, + 930FAE83266E112700422968 /* Products */, + ); + sourceTree = ""; + }; + 930FAE83266E112700422968 /* Products */ = { + isa = PBXGroup; + children = ( + 930FAE82266E112700422968 /* issueTrackerApp.app */, + 930FAE98266E112900422968 /* issueTrackerAppTests.xctest */, + 930FAEA3266E112A00422968 /* issueTrackerAppUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 930FAE84266E112700422968 /* issueTrackerApp */ = { + isa = PBXGroup; + children = ( + 883F5153266F442C0055EC9E /* issueTrackerApp.entitlements */, + 930FAEB8266E115700422968 /* Data */, + 930FAEBC266E126F00422968 /* Domain */, + 930FAEBF266E129000422968 /* Presentation */, + 930FAE85266E112700422968 /* AppDelegate.swift */, + 930FAE87266E112700422968 /* SceneDelegate.swift */, + 930FAE8B266E112700422968 /* Main.storyboard */, + 883F5157266F502F0055EC9E /* Issue.storyboard */, + 883F5160266F505E0055EC9E /* Label.storyboard */, + 883F5168266F508B0055EC9E /* Milestones.storyboard */, + 883F5171266F50A40055EC9E /* Setting.storyboard */, + 930FAE8E266E112900422968 /* Assets.xcassets */, + 930FAE90266E112900422968 /* LaunchScreen.storyboard */, + 930FAE93266E112900422968 /* Info.plist */, + 88C3A9F8267742140090FAD8 /* NotificationNameExtension.swift */, + 883F51AD2670B2F70055EC9E /* UIColorExtension.swift */, + ); + path = issueTrackerApp; + sourceTree = ""; + }; + 930FAE9B266E112900422968 /* issueTrackerAppTests */ = { + isa = PBXGroup; + children = ( + 930FAE9C266E112900422968 /* issueTrackerAppTests.swift */, + 930FAE9E266E112900422968 /* Info.plist */, + ); + path = issueTrackerAppTests; + sourceTree = ""; + }; + 930FAEA6266E112A00422968 /* issueTrackerAppUITests */ = { + isa = PBXGroup; + children = ( + 930FAEA7266E112A00422968 /* issueTrackerAppUITests.swift */, + 930FAEA9266E112A00422968 /* Info.plist */, + ); + path = issueTrackerAppUITests; + sourceTree = ""; + }; + 930FAEB8266E115700422968 /* Data */ = { + isa = PBXGroup; + children = ( + 930FAEB9266E116300422968 /* Network */, + 930FAEBA266E11BA00422968 /* Storage */, + 930FAEBB266E126100422968 /* Repository */, + ); + path = Data; + sourceTree = ""; + }; + 930FAEB9266E116300422968 /* Network */ = { + isa = PBXGroup; + children = ( + 931BADFE26720B7D00CD9244 /* Issue */, + 931BADFA26720B7200CD9244 /* OAuth */, + ); + path = Network; + sourceTree = ""; + }; + 930FAEBA266E11BA00422968 /* Storage */ = { + isa = PBXGroup; + children = ( + ); + path = Storage; + sourceTree = ""; + }; + 930FAEBB266E126100422968 /* Repository */ = { + isa = PBXGroup; + children = ( + 931BAE0326720C0900CD9244 /* Issue */, + 931BADFF26720C0200CD9244 /* OAuth */, + ); + path = Repository; + sourceTree = ""; + }; + 930FAEBC266E126F00422968 /* Domain */ = { + isa = PBXGroup; + children = ( + 930FAEBE266E128700422968 /* UseCase */, + 930FAEBD266E127B00422968 /* Entity */, + 931BAE692674EE5E00CD9244 /* Dummy data */, + 931BAE742674FA8F00CD9244 /* Protocols and extentions */, + ); + path = Domain; + sourceTree = ""; + }; + 930FAEBD266E127B00422968 /* Entity */ = { + isa = PBXGroup; + children = ( + 883F51C6267200EA0055EC9E /* Issue.swift */, + 883F51CB2672041A0055EC9E /* Milestones.swift */, + 883F51D3267204520055EC9E /* LabelModel.swift */, + 883F51DB267204DC0055EC9E /* Comment.swift */, + 931BAE612674DAF100CD9244 /* MoreInfo.swift */, + 88C3A9FD26785B160090FAD8 /* Status.swift */, + ); + path = Entity; + sourceTree = ""; + }; + 930FAEBE266E128700422968 /* UseCase */ = { + isa = PBXGroup; + children = ( + 9343E2F0266F5CBC0086F977 /* Coordinator */, + ); + path = UseCase; + sourceTree = ""; + }; + 930FAEBF266E129000422968 /* Presentation */ = { + isa = PBXGroup; + children = ( + 9343E2F6266F5E890086F977 /* MainTabBarController.swift */, + 930FAEC4266E12E900422968 /* ViewModel */, + 930FAEC0266E129C00422968 /* UI */, + ); + path = Presentation; + sourceTree = ""; + }; + 930FAEC0266E129C00422968 /* UI */ = { + isa = PBXGroup; + children = ( + 930FAEC3266E12CF00422968 /* UIControl */, + 930FAEC2266E12C800422968 /* UIView */, + 930FAEC1266E12C200422968 /* ViewController */, + ); + path = UI; + sourceTree = ""; + }; + 930FAEC1266E12C200422968 /* ViewController */ = { + isa = PBXGroup; + children = ( + 883F514F266F3F060055EC9E /* Login */, + 883F5182266F5EE80055EC9E /* Issue */, + 931BAE1726725DC600CD9244 /* Setting */, + ); + path = ViewController; + sourceTree = ""; + }; + 930FAEC2266E12C800422968 /* UIView */ = { + isa = PBXGroup; + children = ( + 931BAE0426720C1500CD9244 /* Issue */, + ); + path = UIView; + sourceTree = ""; + }; + 930FAEC3266E12CF00422968 /* UIControl */ = { + isa = PBXGroup; + children = ( + 883F51B52671E1710055EC9E /* CustomBarButtonItem.swift */, + ); + path = UIControl; + sourceTree = ""; + }; + 930FAEC4266E12E900422968 /* ViewModel */ = { + isa = PBXGroup; + children = ( + 931BAEA42677973200CD9244 /* AddIssueViewModel.swift */, + 88C3A9F326773B660090FAD8 /* IssueViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + 931BADF626720B6600CD9244 /* Endpoint */ = { + isa = PBXGroup; + children = ( + 9343E2D2266EFBA70086F977 /* GitHubEndpoint.swift */, + ); + path = Endpoint; + sourceTree = ""; + }; + 931BADFA26720B7200CD9244 /* OAuth */ = { + isa = PBXGroup; + children = ( + 931BADF626720B6600CD9244 /* Endpoint */, + 9343E2FE266F60320086F977 /* Requests */, + ); + path = OAuth; + sourceTree = ""; + }; + 931BADFE26720B7D00CD9244 /* Issue */ = { + isa = PBXGroup; + children = ( + 883F522E267312D60055EC9E /* EndPoint */, + 883F5232267317BB0055EC9E /* Request */, + ); + path = Issue; + sourceTree = ""; + }; + 931BADFF26720C0200CD9244 /* OAuth */ = { + isa = PBXGroup; + children = ( + 9343E2C9266EF9CD0086F977 /* KeychainManager.swift */, + 9343E2D7266F03E90086F977 /* LoginNetworkManager.swift */, + ); + path = OAuth; + sourceTree = ""; + }; + 931BAE0326720C0900CD9244 /* Issue */ = { + isa = PBXGroup; + children = ( + 883F5238267654CB0055EC9E /* IssueNetworkController.swift */, + ); + path = Issue; + sourceTree = ""; + }; + 931BAE0426720C1500CD9244 /* Issue */ = { + isa = PBXGroup; + children = ( + 883F519C2670940E0055EC9E /* IssueCell.swift */, + 883F519D2670940E0055EC9E /* IssueCell.xib */, + 883F51A32670AAAB0055EC9E /* TagStackView.swift */, + 883F51A82670ABDC0055EC9E /* TagLabel.swift */, + 931BAE5426749FCD00CD9244 /* MoreInfoCell.swift */, + 931BAE5B2674B90100CD9244 /* MoreInfoTableViewDataSource.swift */, + ); + path = Issue; + sourceTree = ""; + }; + 931BAE1726725DC600CD9244 /* Setting */ = { + isa = PBXGroup; + children = ( + 931BAE1D26730C8600CD9244 /* SettingViewController.swift */, + ); + name = Setting; + path = issueTrackerApp/Presentation/UI/ViewController/Setting; + sourceTree = SOURCE_ROOT; + }; + 931BAE692674EE5E00CD9244 /* Dummy data */ = { + isa = PBXGroup; + children = ( + 931BAE6A2674EE6F00CD9244 /* MoreInfos.json */, + 931BAE6F2674F0D000CD9244 /* DummyDataLoader.swift */, + ); + path = "Dummy data"; + sourceTree = ""; + }; + 931BAE742674FA8F00CD9244 /* Protocols and extentions */ = { + isa = PBXGroup; + children = ( + 931BAE752674FC4700CD9244 /* ArrayTableViewDataSource.swift */, + 931BAE7D2675A27000CD9244 /* UITableView.swift */, + ); + path = "Protocols and extentions"; + sourceTree = ""; + }; + 9343E2F0266F5CBC0086F977 /* Coordinator */ = { + isa = PBXGroup; + children = ( + 9343E2F1266F5CC70086F977 /* Coordinator.swift */, + 9343E30C266F90EF0086F977 /* LoginFlowCoordinator.swift */, + ); + path = Coordinator; + sourceTree = ""; + }; + 9343E2FE266F60320086F977 /* Requests */ = { + isa = PBXGroup; + children = ( + 9343E2FF266F603C0086F977 /* AccessTokenRequest.swift */, + 9343E304266F60B50086F977 /* NetworkRequest.swift */, + ); + path = Requests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 930FAE81266E112700422968 /* issueTrackerApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 930FAEAC266E112A00422968 /* Build configuration list for PBXNativeTarget "issueTrackerApp" */; + buildPhases = ( + 930FAE7E266E112700422968 /* Sources */, + 930FAE7F266E112700422968 /* Frameworks */, + 930FAE80266E112700422968 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = issueTrackerApp; + packageProductDependencies = ( + 930FAEC6266E13F500422968 /* Alamofire */, + 931BAE2E267310EB00CD9244 /* JWTDecode */, + 931BAE4B26737F5100CD9244 /* MarkdownKit */, + ); + productName = issueTrackerApp; + productReference = 930FAE82266E112700422968 /* issueTrackerApp.app */; + productType = "com.apple.product-type.application"; + }; + 930FAE97266E112900422968 /* issueTrackerAppTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 930FAEAF266E112A00422968 /* Build configuration list for PBXNativeTarget "issueTrackerAppTests" */; + buildPhases = ( + 930FAE94266E112900422968 /* Sources */, + 930FAE95266E112900422968 /* Frameworks */, + 930FAE96266E112900422968 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 930FAE9A266E112900422968 /* PBXTargetDependency */, + ); + name = issueTrackerAppTests; + productName = issueTrackerAppTests; + productReference = 930FAE98266E112900422968 /* issueTrackerAppTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 930FAEA2266E112A00422968 /* issueTrackerAppUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 930FAEB2266E112A00422968 /* Build configuration list for PBXNativeTarget "issueTrackerAppUITests" */; + buildPhases = ( + 930FAE9F266E112A00422968 /* Sources */, + 930FAEA0266E112A00422968 /* Frameworks */, + 930FAEA1266E112A00422968 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 930FAEA5266E112A00422968 /* PBXTargetDependency */, + ); + name = issueTrackerAppUITests; + productName = issueTrackerAppUITests; + productReference = 930FAEA3266E112A00422968 /* issueTrackerAppUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 930FAE7A266E112700422968 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1230; + LastUpgradeCheck = 1230; + TargetAttributes = { + 930FAE81266E112700422968 = { + CreatedOnToolsVersion = 12.3; + }; + 930FAE97266E112900422968 = { + CreatedOnToolsVersion = 12.3; + TestTargetID = 930FAE81266E112700422968; + }; + 930FAEA2266E112A00422968 = { + CreatedOnToolsVersion = 12.3; + TestTargetID = 930FAE81266E112700422968; + }; + }; + }; + buildConfigurationList = 930FAE7D266E112700422968 /* Build configuration list for PBXProject "issueTrackerApp" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 930FAE79266E112700422968; + packageReferences = ( + 930FAEC5266E13F500422968 /* XCRemoteSwiftPackageReference "Alamofire" */, + 931BAE2D267310EB00CD9244 /* XCRemoteSwiftPackageReference "JWTDecode.swift" */, + 931BAE4A26737F5100CD9244 /* XCRemoteSwiftPackageReference "MarkdownKit" */, + ); + productRefGroup = 930FAE83266E112700422968 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 930FAE81266E112700422968 /* issueTrackerApp */, + 930FAE97266E112900422968 /* issueTrackerAppTests */, + 930FAEA2266E112A00422968 /* issueTrackerAppUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 930FAE80266E112700422968 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 883F5161266F505E0055EC9E /* Label.storyboard in Resources */, + 883F516A266F508B0055EC9E /* Milestones.storyboard in Resources */, + 883F5172266F50A40055EC9E /* Setting.storyboard in Resources */, + 931BAE6B2674EE6F00CD9244 /* MoreInfos.json in Resources */, + 883F5159266F502F0055EC9E /* Issue.storyboard in Resources */, + 930FAE92266E112900422968 /* LaunchScreen.storyboard in Resources */, + 930FAE8F266E112900422968 /* Assets.xcassets in Resources */, + 930FAE8D266E112700422968 /* Main.storyboard in Resources */, + 883F519F2670940E0055EC9E /* IssueCell.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 930FAE96266E112900422968 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 930FAEA1266E112A00422968 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 930FAE7E266E112700422968 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 88C3A9F9267742140090FAD8 /* NotificationNameExtension.swift in Sources */, + 883F5234267318240055EC9E /* IssueRequest.swift in Sources */, + 883F5148266F3EAF0055EC9E /* LoginViewController.swift in Sources */, + 9343E2D3266EFBA70086F977 /* GitHubEndpoint.swift in Sources */, + 931BAE41267343B900CD9244 /* AddIssueViewController.swift in Sources */, + 931BAE5C2674B90100CD9244 /* MoreInfoTableViewDataSource.swift in Sources */, + 9343E2F7266F5E890086F977 /* MainTabBarController.swift in Sources */, + 931BAE702674F0D000CD9244 /* DummyDataLoader.swift in Sources */, + 88C3A9F426773B660090FAD8 /* IssueViewModel.swift in Sources */, + 9343E30D266F90EF0086F977 /* LoginFlowCoordinator.swift in Sources */, + 883F5239267654CB0055EC9E /* IssueNetworkController.swift in Sources */, + 930FAE86266E112700422968 /* AppDelegate.swift in Sources */, + 931BAEAD2678843200CD9244 /* CommentViewController.swift in Sources */, + 931BAE4626734FA500CD9244 /* MoreInformationViewController.swift in Sources */, + 9343E305266F60B50086F977 /* NetworkRequest.swift in Sources */, + 88C3AA0B267874470090FAD8 /* IssuePatchRequest.swift in Sources */, + 931BAE1E26730C8600CD9244 /* SettingViewController.swift in Sources */, + 931BAEA52677973200CD9244 /* AddIssueViewModel.swift in Sources */, + 883F51A42670AAAB0055EC9E /* TagStackView.swift in Sources */, + 883F519E2670940E0055EC9E /* IssueCell.swift in Sources */, + 9343E2D8266F03E90086F977 /* LoginNetworkManager.swift in Sources */, + 883F51CC2672041A0055EC9E /* Milestones.swift in Sources */, + 883F51DC267204DC0055EC9E /* Comment.swift in Sources */, + 9343E2F2266F5CC70086F977 /* Coordinator.swift in Sources */, + 931BAE7E2675A27000CD9244 /* UITableView.swift in Sources */, + 9343E300266F603C0086F977 /* AccessTokenRequest.swift in Sources */, + 931BAE622674DAF100CD9244 /* MoreInfo.swift in Sources */, + 883F5185266F5F090055EC9E /* IssueViewController.swift in Sources */, + 930FAE88266E112700422968 /* SceneDelegate.swift in Sources */, + 931BAEB226788EA700CD9244 /* MarkdownViewController.swift in Sources */, + 883F51AE2670B2F70055EC9E /* UIColorExtension.swift in Sources */, + 931BAE762674FC4700CD9244 /* ArrayTableViewDataSource.swift in Sources */, + 9343E2CA266EF9CD0086F977 /* KeychainManager.swift in Sources */, + 931BAE5526749FCD00CD9244 /* MoreInfoCell.swift in Sources */, + 883F51A92670ABDC0055EC9E /* TagLabel.swift in Sources */, + 88C3A9FE26785B160090FAD8 /* Status.swift in Sources */, + 883F51B62671E1710055EC9E /* CustomBarButtonItem.swift in Sources */, + 883F522A267312CE0055EC9E /* IssueEndPoint.swift in Sources */, + 88C3AA132678F7330090FAD8 /* IssueSelectTableViewController.swift in Sources */, + 883F51D4267204520055EC9E /* LabelModel.swift in Sources */, + 883F51C7267200EA0055EC9E /* Issue.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 930FAE94266E112900422968 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 930FAE9D266E112900422968 /* issueTrackerAppTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 930FAE9F266E112A00422968 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 930FAEA8266E112A00422968 /* issueTrackerAppUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 930FAE9A266E112900422968 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 930FAE81266E112700422968 /* issueTrackerApp */; + targetProxy = 930FAE99266E112900422968 /* PBXContainerItemProxy */; + }; + 930FAEA5266E112A00422968 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 930FAE81266E112700422968 /* issueTrackerApp */; + targetProxy = 930FAEA4266E112A00422968 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 883F5157266F502F0055EC9E /* Issue.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 883F5158266F502F0055EC9E /* Base */, + ); + name = Issue.storyboard; + sourceTree = ""; + }; + 883F5168266F508B0055EC9E /* Milestones.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 883F5169266F508B0055EC9E /* Base */, + ); + name = Milestones.storyboard; + sourceTree = ""; + }; + 930FAE8B266E112700422968 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 930FAE8C266E112700422968 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 930FAE90266E112900422968 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 930FAE91266E112900422968 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 930FAEAA266E112A00422968 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.3; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 930FAEAB266E112A00422968 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.3; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 930FAEAD266E112A00422968 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = issueTrackerApp/issueTrackerApp.entitlements; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = B3PWYBKFUK; + INFOPLIST_FILE = issueTrackerApp/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.zombietux.issueTrackerApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 930FAEAE266E112A00422968 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = issueTrackerApp/issueTrackerApp.entitlements; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = B3PWYBKFUK; + INFOPLIST_FILE = issueTrackerApp/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.zombietux.issueTrackerApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 930FAEB0266E112A00422968 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = B3PWYBKFUK; + INFOPLIST_FILE = issueTrackerAppTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.3; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.zombietux.issueTrackerAppTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/issueTrackerApp.app/issueTrackerApp"; + }; + name = Debug; + }; + 930FAEB1266E112A00422968 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = B3PWYBKFUK; + INFOPLIST_FILE = issueTrackerAppTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.3; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.zombietux.issueTrackerAppTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/issueTrackerApp.app/issueTrackerApp"; + }; + name = Release; + }; + 930FAEB3266E112A00422968 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = B3PWYBKFUK; + INFOPLIST_FILE = issueTrackerAppUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.zombietux.issueTrackerAppUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = issueTrackerApp; + }; + name = Debug; + }; + 930FAEB4266E112A00422968 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = B3PWYBKFUK; + INFOPLIST_FILE = issueTrackerAppUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.zombietux.issueTrackerAppUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = issueTrackerApp; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 930FAE7D266E112700422968 /* Build configuration list for PBXProject "issueTrackerApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 930FAEAA266E112A00422968 /* Debug */, + 930FAEAB266E112A00422968 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 930FAEAC266E112A00422968 /* Build configuration list for PBXNativeTarget "issueTrackerApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 930FAEAD266E112A00422968 /* Debug */, + 930FAEAE266E112A00422968 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 930FAEAF266E112A00422968 /* Build configuration list for PBXNativeTarget "issueTrackerAppTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 930FAEB0266E112A00422968 /* Debug */, + 930FAEB1266E112A00422968 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 930FAEB2266E112A00422968 /* Build configuration list for PBXNativeTarget "issueTrackerAppUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 930FAEB3266E112A00422968 /* Debug */, + 930FAEB4266E112A00422968 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 930FAEC5266E13F500422968 /* XCRemoteSwiftPackageReference "Alamofire" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Alamofire/Alamofire.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 5.4.3; + }; + }; + 931BAE2D267310EB00CD9244 /* XCRemoteSwiftPackageReference "JWTDecode.swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/auth0/JWTDecode.swift.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.6.1; + }; + }; + 931BAE4A26737F5100CD9244 /* XCRemoteSwiftPackageReference "MarkdownKit" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/bmoliveira/MarkdownKit.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.7.1; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 930FAEC6266E13F500422968 /* Alamofire */ = { + isa = XCSwiftPackageProductDependency; + package = 930FAEC5266E13F500422968 /* XCRemoteSwiftPackageReference "Alamofire" */; + productName = Alamofire; + }; + 931BAE2E267310EB00CD9244 /* JWTDecode */ = { + isa = XCSwiftPackageProductDependency; + package = 931BAE2D267310EB00CD9244 /* XCRemoteSwiftPackageReference "JWTDecode.swift" */; + productName = JWTDecode; + }; + 931BAE4B26737F5100CD9244 /* MarkdownKit */ = { + isa = XCSwiftPackageProductDependency; + package = 931BAE4A26737F5100CD9244 /* XCRemoteSwiftPackageReference "MarkdownKit" */; + productName = MarkdownKit; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 930FAE7A266E112700422968 /* Project object */; +} diff --git a/iOS/issueTrackerApp/issueTrackerApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/iOS/issueTrackerApp/issueTrackerApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/iOS/issueTrackerApp/issueTrackerApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/iOS/issueTrackerApp/issueTrackerApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/iOS/issueTrackerApp/issueTrackerApp.xcodeproj/project.xcworkspace/xcuserdata/zombietux.xcuserdatad/UserInterfaceState.xcuserstate b/iOS/issueTrackerApp/issueTrackerApp.xcodeproj/project.xcworkspace/xcuserdata/zombietux.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 000000000..d84ba28d4 Binary files /dev/null and b/iOS/issueTrackerApp/issueTrackerApp.xcodeproj/project.xcworkspace/xcuserdata/zombietux.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/iOS/issueTrackerApp/issueTrackerApp.xcodeproj/xcuserdata/zombietux.xcuserdatad/xcschemes/xcschememanagement.plist b/iOS/issueTrackerApp/issueTrackerApp.xcodeproj/xcuserdata/zombietux.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 000000000..7fd8d0206 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp.xcodeproj/xcuserdata/zombietux.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,56 @@ + + + + + SchemeUserState + + JWTDecode (Playground) 1.xcscheme + + isShown + + orderHint + 2 + + JWTDecode (Playground) 2.xcscheme + + isShown + + orderHint + 3 + + JWTDecode (Playground).xcscheme + + isShown + + orderHint + 0 + + SWXMLHashPlayground (Playground) 1.xcscheme + + isShown + + orderHint + 2 + + SWXMLHashPlayground (Playground) 2.xcscheme + + isShown + + orderHint + 3 + + SWXMLHashPlayground (Playground).xcscheme + + isShown + + orderHint + 1 + + issueTrackerApp.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/iOS/issueTrackerApp/issueTrackerApp/AppDelegate.swift b/iOS/issueTrackerApp/issueTrackerApp/AppDelegate.swift new file mode 100644 index 000000000..5ee37e392 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/AppDelegate.swift @@ -0,0 +1,31 @@ +// +// AppDelegate.swift +// issueTrackerApp +// +// Created by zombietux on 2021/06/07. +// + +import UIKit +import AuthenticationServices + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + + NotificationCenter.default.addObserver(forName: ASAuthorizationAppleIDProvider.credentialRevokedNotification, object: nil, queue: nil) { (notification) in + print("로그인 페이지로 이동") + } + + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + +} + diff --git a/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/AccentColor.colorset/Contents.json b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..eb8789700 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..9221b9bb1 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/Contents.json b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/Icon-29.imageset/Contents.json b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/Icon-29.imageset/Contents.json new file mode 100644 index 000000000..3798a8b5a --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/Icon-29.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "filename" : "Icon-29.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/Icon-29.imageset/Icon-29.png b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/Icon-29.imageset/Icon-29.png new file mode 100644 index 000000000..712c051b2 Binary files /dev/null and b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/Icon-29.imageset/Icon-29.png differ diff --git a/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/github.imageset/25231.png b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/github.imageset/25231.png new file mode 100644 index 000000000..9490ffc6d Binary files /dev/null and b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/github.imageset/25231.png differ diff --git a/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/github.imageset/Contents.json b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/github.imageset/Contents.json new file mode 100644 index 000000000..ffd65ace5 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/github.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "25231.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/icon.imageset/Contents.json b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/icon.imageset/Contents.json new file mode 100644 index 000000000..45ed516cd --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/icon.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "filename" : "Icon-20-2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/icon.imageset/Icon-20-2.png b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/icon.imageset/Icon-20-2.png new file mode 100644 index 000000000..ba434cf9d Binary files /dev/null and b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/icon.imageset/Icon-20-2.png differ diff --git a/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/issue.imageset/Contents.json b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/issue.imageset/Contents.json new file mode 100644 index 000000000..b99c44b59 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/issue.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "filename" : "Icons.pdf", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/issue.imageset/Icons.pdf b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/issue.imageset/Icons.pdf new file mode 100644 index 000000000..e959b6739 Binary files /dev/null and b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/issue.imageset/Icons.pdf differ diff --git a/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/label.imageset/Contents.json b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/label.imageset/Contents.json new file mode 100644 index 000000000..b99c44b59 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/label.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "filename" : "Icons.pdf", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/label.imageset/Icons.pdf b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/label.imageset/Icons.pdf new file mode 100644 index 000000000..3c1350781 Binary files /dev/null and b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/label.imageset/Icons.pdf differ diff --git a/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/milestone.imageset/Contents.json b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/milestone.imageset/Contents.json new file mode 100644 index 000000000..b99c44b59 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/milestone.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "filename" : "Icons.pdf", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/milestone.imageset/Icons.pdf b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/milestone.imageset/Icons.pdf new file mode 100644 index 000000000..98834e6ca Binary files /dev/null and b/iOS/issueTrackerApp/issueTrackerApp/Assets.xcassets/milestone.imageset/Icons.pdf differ diff --git a/iOS/issueTrackerApp/issueTrackerApp/Base.lproj/Issue~.storyboard b/iOS/issueTrackerApp/issueTrackerApp/Base.lproj/Issue~.storyboard new file mode 100644 index 000000000..e05dcf523 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Base.lproj/Issue~.storyboard @@ -0,0 +1,325 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/issueTrackerApp/issueTrackerApp/Base.lproj/LaunchScreen.storyboard b/iOS/issueTrackerApp/issueTrackerApp/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..865e9329f --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/issueTrackerApp/issueTrackerApp/Base.lproj/Main.storyboard b/iOS/issueTrackerApp/issueTrackerApp/Base.lproj/Main.storyboard new file mode 100644 index 000000000..7c5197bfb --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Base.lproj/Main.storyboard @@ -0,0 +1,270 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/issueTrackerApp/issueTrackerApp/Base.lproj/Milestones~.storyboard b/iOS/issueTrackerApp/issueTrackerApp/Base.lproj/Milestones~.storyboard new file mode 100644 index 000000000..cbd0d3cb1 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Base.lproj/Milestones~.storyboard @@ -0,0 +1,275 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/issueTrackerApp/issueTrackerApp/Data/Network/Issue/EndPoint/IssueEndPoint.swift b/iOS/issueTrackerApp/issueTrackerApp/Data/Network/Issue/EndPoint/IssueEndPoint.swift new file mode 100644 index 000000000..6705c04cd --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Data/Network/Issue/EndPoint/IssueEndPoint.swift @@ -0,0 +1,24 @@ +// +// EndPoint.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/11. +// + +import Foundation + +struct IssueEndPoint { + static let scheme = "https" + static let host = "f88e009a-3e2b-4862-838e-1f2cde9b95ed.mock.pstmn.io" + static let basicPath = "/api/issues" +// static let countPath = "/count" + + static func url() -> URL { + var components = URLComponents() + components.scheme = IssueEndPoint.scheme + components.host = IssueEndPoint.host + components.path = IssueEndPoint.basicPath + return components.url! + } +} + diff --git a/iOS/issueTrackerApp/issueTrackerApp/Data/Network/Issue/Request/IssueNetworkController.swift b/iOS/issueTrackerApp/issueTrackerApp/Data/Network/Issue/Request/IssueNetworkController.swift new file mode 100644 index 000000000..c28b61581 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Data/Network/Issue/Request/IssueNetworkController.swift @@ -0,0 +1,42 @@ +// +// IssueNetworkController.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/13. +// + +import Foundation + +class IssueNetworkController { + private var requests: [URL: AnyObject] = [:] + + init() { + } + + func fetchIssues(completion: @escaping ([Issue]) -> Void) { + let req = IssueRequest() + let requestURL = req.fetchReq.url! + requests[requestURL] = req + + req.execute { (result) in + if let _ = result { + self.requests[requestURL] = nil + completion(result!) + } + } + } + + func deleteIssue(with issueId: Int, completion: @escaping (Status) -> Void) { + let req = IssuePatchRequest() + var requestURL = req.fetchReq.url! + requestURL.appendPathComponent(String(issueId), isDirectory: false) + requests[requestURL] = req + + req.execute { (status) in + if let _ = status { + self.requests[requestURL] = nil + completion(status!) + } + } + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Data/Network/Issue/Request/IssuePatchRequest.swift b/iOS/issueTrackerApp/issueTrackerApp/Data/Network/Issue/Request/IssuePatchRequest.swift new file mode 100644 index 000000000..dbeb96ff8 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Data/Network/Issue/Request/IssuePatchRequest.swift @@ -0,0 +1,32 @@ +// +// IssuePatchRequest.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/15. +// + +import Foundation + +class IssuePatchRequest { + let session = URLSession(configuration: .ephemeral, delegate: nil, delegateQueue: .main) + var task: URLSessionDataTask? + + init() { + } +} + +extension IssuePatchRequest: JSONDataRequest { + typealias ModelType = Status + + var fetchReq: URLRequest { + let urlComponents = URLComponents(url: IssueEndPoint.url(), resolvingAgainstBaseURL: false)! + var request = try! URLRequest(url: urlComponents, method: .patch) + let body = ["delete": true] + let bodyData = try? JSONSerialization.data( + withJSONObject: body, + options: []) + request.httpBody = bodyData + return request + } + +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Data/Network/Issue/Request/IssueRequest.swift b/iOS/issueTrackerApp/issueTrackerApp/Data/Network/Issue/Request/IssueRequest.swift new file mode 100644 index 000000000..dc4f6d935 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Data/Network/Issue/Request/IssueRequest.swift @@ -0,0 +1,27 @@ +// +// IssueNetworkRequest.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/11. +// + +import Foundation + +class IssueRequest { + let session = URLSession(configuration: .ephemeral, delegate: nil, delegateQueue: .main) + var task: URLSessionDataTask? + + init() { + } +} + +extension IssueRequest: JSONDataRequest { + typealias ModelType = [Issue] + + var fetchReq: URLRequest { + let urlComponents = URLComponents(url: IssueEndPoint.url(), resolvingAgainstBaseURL: false)! + let request = try! URLRequest(url: urlComponents, method: .get) + return request + } + +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Data/Network/OAuth/Endpoint/GitHubEndpoint.swift b/iOS/issueTrackerApp/issueTrackerApp/Data/Network/OAuth/Endpoint/GitHubEndpoint.swift new file mode 100644 index 000000000..be7b5743f --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Data/Network/OAuth/Endpoint/GitHubEndpoint.swift @@ -0,0 +1,62 @@ +// +// GitHubEndpoint.swift +// issueTrackerApp +// +// Created by zombietux on 2021/06/08. +// + +import Foundation + +struct GitHubEndpoint { + struct FieldNames { + static let state = "state" + static let clientID = "client_id" + static let clientSecret = "client_secret" + static let redirectURI = "redirect_uri" + static let authorizationCode = "code" + static let client = "ios" + static let page = "page" + static let scope = "scope" + } + + static let loginURL = "https://f88e009a-3e2b-4862-838e-1f2cde9b95ed.mock.pstmn.io/api/login/auth" + static let clientID = "34a66f51f68864c9adfd" + static let clientSecret = "8473cd9ac4bb16ef2156ed80b89646d09d4db01f" + static let scope = "user" + static let authorizationCallbackURLScheme = "issue-tracker" + static let redirectURI = "issue-tracker://login" + static let accessTokenURL = URL(string: "https://github.com/login/oauth/access_token")! + static let serverURL = URL(string: "https://github.com")! + static let authorizationURL = URL(string: "https://github.com/login/oauth/authorize")! + static let signOutURL = URL(string: "https://github.com/logout")! + static let apiRootURL = URL(string: "https://api.github.com")! + + static func authorizationUrl() -> URL { + var urlComponents = URLComponents(url: GitHubEndpoint.authorizationURL, resolvingAgainstBaseURL: false)! + urlComponents.queryItems = [ + URLQueryItem(name: FieldNames.clientID, value: GitHubEndpoint.clientID), + URLQueryItem(name: FieldNames.redirectURI, value: GitHubEndpoint.redirectURI), + URLQueryItem(name: FieldNames.scope, value: GitHubEndpoint.scope) + ] + + return urlComponents.url! + } +} + +extension URL { + var authorizationCode: String? { + guard absoluteString.contains(GitHubEndpoint.authorizationCallbackURLScheme) else { + return nil + } + guard let components = URLComponents(url: self, resolvingAgainstBaseURL: false), + let queryItems = components.queryItems else { + return nil + } + for queryItem in queryItems { + if queryItem.name == GitHubEndpoint.FieldNames.authorizationCode { + return queryItem.value + } + } + return nil + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Data/Network/OAuth/Requests/AccessTokenRequest.swift b/iOS/issueTrackerApp/issueTrackerApp/Data/Network/OAuth/Requests/AccessTokenRequest.swift new file mode 100644 index 000000000..3161f2990 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Data/Network/OAuth/Requests/AccessTokenRequest.swift @@ -0,0 +1,45 @@ +// +// AccessTokenRequest.swift +// issueTrackerApp +// +// Created by zombietux on 2021/06/08. +// + +import Foundation +import Alamofire + +class JWTRequest { + let client: String + let authorizationCode: String + let session = URLSession(configuration: .ephemeral, delegate: nil, delegateQueue: .main) + var task: URLSessionDataTask? + + init(authorizationCode: String, client: String) { + self.authorizationCode = authorizationCode + self.client = client + } +} + +// MARK: NetworkRequest +extension JWTRequest: JSONDataRequest { + typealias ModelType = Authorization + + var fetchReq: URLRequest { + let url = URL(string: GitHubEndpoint.loginURL)! + var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false)! + urlComponents.queryItems = [ + URLQueryItem(name: GitHubEndpoint.FieldNames.authorizationCode, value: authorizationCode), + URLQueryItem(name: GitHubEndpoint.FieldNames.client, value: client) + ] + let headers: HTTPHeaders = [GitHubEndpoint.FieldNames.authorizationCode : authorizationCode] + + let request = try! URLRequest(url: urlComponents, method: .get, headers: headers) + + return request + } +} + +// MARK: - AuthorizationResponse +struct Authorization: Decodable { + let jwt: String +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Data/Network/OAuth/Requests/NetworkRequest.swift b/iOS/issueTrackerApp/issueTrackerApp/Data/Network/OAuth/Requests/NetworkRequest.swift new file mode 100644 index 000000000..1c0077bf4 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Data/Network/OAuth/Requests/NetworkRequest.swift @@ -0,0 +1,57 @@ +// +// NetworkRequest.swift +// issueTrackerApp +// +// Created by zombietux on 2021/06/08. +// + +import Foundation + +protocol NetworkRequest: class { + associatedtype ModelType + var fetchReq: URLRequest { get } + var session: URLSession { get } + var task: URLSessionDataTask? { get set } + func deserialize(_ data: Data?, response: URLResponse?) -> ModelType? +} + +extension NetworkRequest { + func execute(withCompletion completion: @escaping (ModelType?) -> Void) { + task = session.dataTask(with: fetchReq) { [weak self] (data, response, error) in + completion( self?.deserialize(data, response: response) ) + } + task?.resume() + } +} + +// MARK: - JSONDataRequest +protocol JSONDataRequest: NetworkRequest where ModelType: Decodable {} + +extension JSONDataRequest { + func deserialize(_ data: Data?, response: URLResponse?) -> ModelType? { + guard let data = data else { + return nil + } + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601 + return try? decoder.decode(ModelType.self, from: data) + } +} + +// MARK: - HTTPStatusRequest +protocol HTTPStatusRequest: NetworkRequest {} + +extension HTTPStatusRequest { + func deserialize(_ data: Data?, response: URLResponse?) -> Bool? { + guard let response = response as? HTTPURLResponse else { + return nil + } + switch response.statusCode { + case 204: return true + case 404: return false + default: + assertionFailure("Unexpected status code") + return nil + } + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Data/Repository/OAuth/KeychainManager.swift b/iOS/issueTrackerApp/issueTrackerApp/Data/Repository/OAuth/KeychainManager.swift new file mode 100644 index 000000000..a7b5163c4 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Data/Repository/OAuth/KeychainManager.swift @@ -0,0 +1,63 @@ +// +// KeychainManager.swift +// issueTrackerApp +// +// Created by zombietux on 2021/06/08. +// + +import Foundation +import JWTDecode + +class KeychainManager { + private static let accountName = "AppUser" + + func readJWT() -> String? { + var query = tokenQuery + query[kSecMatchLimit as String] = kSecMatchLimitOne + query[kSecReturnAttributes as String] = true + query[kSecReturnData as String] = true + var keychainItem: CFTypeRef? + let status = SecItemCopyMatching(query as CFDictionary, &keychainItem) + guard status != errSecItemNotFound else { + return nil + } + guard let item = keychainItem as? [String : Any], + let data = item[kSecValueData as String] as? Data, + let token = String(data: data, encoding: .utf8) else { + return nil + } + return token + } + + func store(jWT: String) { + let encodedToken = jWT.data(using: .utf8)! + var query: [String: Any] = tokenQuery + if let _ = readJWT() { + SecItemUpdate(query as CFDictionary, [kSecValueData as String: encodedToken] as CFDictionary) + } else { + query[kSecValueData as String] = encodedToken + SecItemAdd(query as CFDictionary, nil) + } + } + + func deleteJWT() { + SecItemDelete(tokenQuery as CFDictionary) + } + + func storeAvatarImage(jWT: String) { + if let decodedToken = try? decode(jwt: jWT) { + print(decodedToken) + } + } +} + +//MARK:- Private +private extension KeychainManager { + var tokenQuery: [String: Any] { + return [ + kSecClass as String: kSecClassInternetPassword, + kSecAttrAccount as String: KeychainManager.accountName, + kSecAttrServer as String: GitHubEndpoint.serverURL.absoluteString + ] + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Data/Repository/OAuth/LoginNetworkManager.swift b/iOS/issueTrackerApp/issueTrackerApp/Data/Repository/OAuth/LoginNetworkManager.swift new file mode 100644 index 000000000..17d8ff404 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Data/Repository/OAuth/LoginNetworkManager.swift @@ -0,0 +1,48 @@ +// +// LoginNetworkManager.swift +// issueTrackerApp +// +// Created by zombietux on 2021/06/08. +// + +import Foundation + +class LoginNetworkManager { + private let keychainManager: KeychainManager + private var requests: [URL: AnyObject] = [:] + + init(keychainManager: KeychainManager) { + self.keychainManager = keychainManager + } + + var jWT: String? { + return keychainManager.readJWT() + } + + var isClientAuthenticated: Bool { + return jWT != nil + } + + func authenticateWith(authorizationCode: String, client: String, completion: @escaping () -> Void) { + let jWTRequest = JWTRequest(authorizationCode: authorizationCode, client: client) + let requestURL = jWTRequest.fetchReq.url! + requests[requestURL] = jWTRequest + + jWTRequest.execute { (authorization) in + if let jWT = authorization?.jwt { + self.keychainManager.store(jWT: jWT) + } + self.requests[requestURL] = nil + self.fetchUserAvatarImage()//test + completion() + } + } + + func logOut() { + keychainManager.deleteJWT() + } + + func fetchUserAvatarImage() { + keychainManager.storeAvatarImage(jWT: self.jWT ?? "") + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Domain/Dummy data/DummyDataLoader.swift b/iOS/issueTrackerApp/issueTrackerApp/Domain/Dummy data/DummyDataLoader.swift new file mode 100644 index 000000000..be521072a --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Domain/Dummy data/DummyDataLoader.swift @@ -0,0 +1,23 @@ +// +// DummyDataLoader.swift +// issueTrackerApp +// +// Created by zombietux on 2021/06/12. +// + +import Foundation + +struct DummyDataLoader { + static func loadDataFromJSONFile(withName name: String) -> Model? { + guard let url = Bundle.main.url(forResource: name, withExtension: "json"), + let data = try? Data(contentsOf: url) else { + return nil + } + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601 + guard let model = try? decoder.decode(Model.self, from: data) else { + return nil + } + return model + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Domain/Dummy data/MoreInfos.json b/iOS/issueTrackerApp/issueTrackerApp/Domain/Dummy data/MoreInfos.json new file mode 100644 index 000000000..089ccef8a --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Domain/Dummy data/MoreInfos.json @@ -0,0 +1,15 @@ + +[ + { + "title": "레이블", + "info": "iOS", + }, + { + "title": "마일스톤", + "info": "[iOS] Network Layer", + }, + { + "title": "작성자", + "info": "ITZombietux", + }, +] diff --git a/iOS/issueTrackerApp/issueTrackerApp/Domain/Entity/Comment.swift b/iOS/issueTrackerApp/issueTrackerApp/Domain/Entity/Comment.swift new file mode 100644 index 000000000..3e3344470 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Domain/Entity/Comment.swift @@ -0,0 +1,31 @@ +// +// Comment.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/10. +// + +import Foundation + +struct Comment: Codable { + let description, createdTime: String + let user: User + + enum CodingKeys: String, CodingKey { + case description = "description" + case createdTime = "created_time" + case user + } +} + +struct User: Codable { + let name: String + let avatarURL: String + let editable: Bool + + enum CodingKeys: String, CodingKey { + case name + case avatarURL = "avatar_url" + case editable + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Domain/Entity/Issue.swift b/iOS/issueTrackerApp/issueTrackerApp/Domain/Entity/Issue.swift new file mode 100644 index 000000000..0f24e60ef --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Domain/Entity/Issue.swift @@ -0,0 +1,41 @@ +// +// Issue.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/10. +// + +import Foundation + +struct Issue: Codable { + let id: Int + let title: String + let description: String + let authorAvatarURL: String + let labelList: [Label] + let issueNumber: Int + let createdAt: String + let milestoneTitle: String + + enum CodingKeys: String, CodingKey { + case id + case title + case description + case authorAvatarURL = "author_avatar_url" + case labelList = "label_list" + case issueNumber = "issue_number" + case createdAt = "created_time" + case milestoneTitle = "milestone_title" + } +} + +struct Label: Codable { + let id: Int + let title, colorCode: String + + enum CodingKeys: String, CodingKey { + case id + case title + case colorCode = "color_code" + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Domain/Entity/LabelModel.swift b/iOS/issueTrackerApp/issueTrackerApp/Domain/Entity/LabelModel.swift new file mode 100644 index 000000000..9e039635f --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Domain/Entity/LabelModel.swift @@ -0,0 +1,18 @@ +// +// Label.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/10. +// + +import Foundation + +struct LabelModel: Codable { + let title, description, colorCode: String + + enum CodingKeys: String, CodingKey { + case title + case description = "description" + case colorCode = "color_code" + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Domain/Entity/Milestones.swift b/iOS/issueTrackerApp/issueTrackerApp/Domain/Entity/Milestones.swift new file mode 100644 index 000000000..0d67bf88c --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Domain/Entity/Milestones.swift @@ -0,0 +1,21 @@ +// +// Label.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/10. +// + +import Foundation + +struct Milestones: Codable { + let title, description, dueDate: String + let openedIssueCount, closedIssueCount: Int + + enum CodingKeys: String, CodingKey { + case title + case description + case dueDate = "due_date" + case openedIssueCount = "opened_issue_count" + case closedIssueCount = "closed_issue_count" + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Domain/Entity/MoreInfo.swift b/iOS/issueTrackerApp/issueTrackerApp/Domain/Entity/MoreInfo.swift new file mode 100644 index 000000000..2295231ce --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Domain/Entity/MoreInfo.swift @@ -0,0 +1,13 @@ +// +// MoreInfo.swift +// issueTrackerApp +// +// Created by zombietux on 2021/06/12. +// + +import Foundation + +struct MoreInfo: Decodable { + let title: String + let info: String +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Domain/Entity/Status.swift b/iOS/issueTrackerApp/issueTrackerApp/Domain/Entity/Status.swift new file mode 100644 index 000000000..166d362b3 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Domain/Entity/Status.swift @@ -0,0 +1,12 @@ +// +// Status.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/15. +// + +import Foundation + +struct Status: Codable { + let status: String +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Domain/Protocols and extentions/ArrayTableViewDataSource.swift b/iOS/issueTrackerApp/issueTrackerApp/Domain/Protocols and extentions/ArrayTableViewDataSource.swift new file mode 100644 index 000000000..b6690ef11 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Domain/Protocols and extentions/ArrayTableViewDataSource.swift @@ -0,0 +1,55 @@ +// +// ArrayTableViewDataSource.swift +// issueTrackerApp +// +// Created by zombietux on 2021/06/12. +// + +import UIKit + +protocol ArrayTableViewDataSource: class { + associatedtype ModelType + associatedtype ViewModel + associatedtype Cell: UITableViewCell + var dataOrganizer: ArrayDataSourceOrganizer { get } + var viewModelCache: [IndexPath: ViewModel] { get set } + func viewModel(for value: ModelType) -> ViewModel + func configure(cell: Cell, with viewModel: ViewModel) +} + +extension ArrayTableViewDataSource { + var rowsCount: Int { + return dataOrganizer.rowsCount + } + + func cell(from tableView: UITableView, for indexPath: IndexPath) -> Cell { + func extractViewModel(at indexPath: IndexPath) -> ViewModel { + let viewModel: ViewModel + if let cachedViewModel = viewModelCache[indexPath] { + viewModel = cachedViewModel + } else { + let value = dataOrganizer[indexPath] + viewModel = self.viewModel(for: value) + viewModelCache[indexPath] = viewModel + } + return viewModel + } + + let cell: Cell = tableView.dequeueReusableCell(for: indexPath) + configure(cell: cell, with: extractViewModel(at: indexPath)) + return cell + } +} + +struct ArrayDataSourceOrganizer { + let items: [ModelType] + + var rowsCount: Int { + return items.count + } + + subscript(indexPath: IndexPath) -> ModelType { + return items[indexPath.row] + } +} + diff --git a/iOS/issueTrackerApp/issueTrackerApp/Domain/Protocols and extentions/UITableView.swift b/iOS/issueTrackerApp/issueTrackerApp/Domain/Protocols and extentions/UITableView.swift new file mode 100644 index 000000000..4f98f1db9 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Domain/Protocols and extentions/UITableView.swift @@ -0,0 +1,14 @@ +// +// UITableView.swift +// issueTrackerApp +// +// Created by zombietux on 2021/06/13. +// + +import UIKit + +extension UITableView { + func dequeueReusableCell(for indexPath: IndexPath) -> Cell { + return dequeueReusableCell(withIdentifier: String(describing: Cell.self), for: indexPath) as! Cell + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Domain/UseCase/Coordinator/Coordinator.swift b/iOS/issueTrackerApp/issueTrackerApp/Domain/UseCase/Coordinator/Coordinator.swift new file mode 100644 index 000000000..672c9c26e --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Domain/UseCase/Coordinator/Coordinator.swift @@ -0,0 +1,79 @@ +// +// Coordinator.swift +// issueTrackerApp +// +// Created by zombietux on 2021/06/08. +// + +import UIKit + +// MARK: Protocols +protocol Coordinator: class { + func configure(viewController: UIViewController) +} + +protocol Coordinated: class { + var coordinator: Coordinator? { get set } +} + +protocol MainCoordinated: class { + var mainCoordinator: MainFlowCoordinator? { get set } +} + +protocol LoginCoordinated: class { + var loginCoordinator: LoginFlowCoordinator? { get set } +} + +protocol LoginNetworked: class { + func setLoginNetworkManager(_ loginNetworkManager: LoginNetworkManager) +} + +protocol IssueNetworked: class { + func setIssueNetworkController(_ issueNetworkController: IssueNetworkController) +} + +protocol AddIssueViewModelType: class { + func setAddIssueViewModel(_ addIssueViewModel: AddIssueViewModel) +} + +protocol IssueViewModelType: class { + func setIssueViewModel(_ issueViewModel: IssueViewModel) +} + +class MainFlowCoordinator: NSObject { + private let mainTabBarController: MainTabBarController + private let keychainManager = KeychainManager() + private let loginFlowCoordinator = LoginFlowCoordinator() + private let addIssueViewModel = AddIssueViewModel() + private let issueViewModel = IssueViewModel() + + init(mainViewController: MainTabBarController) { + self.mainTabBarController = mainViewController + super.init() + loginFlowCoordinator.parent = self + configure(viewController: mainViewController) + } + + func logOut() { + loginFlowCoordinator.mainViewControllerRequiresAuthentication(mainTabBarController, isAppLaunch: false) + } +} + +extension MainFlowCoordinator: Coordinator { + func configure(viewController: UIViewController) { + (viewController as? MainCoordinated)?.mainCoordinator = self + (viewController as? LoginNetworked)?.setLoginNetworkManager(LoginNetworkManager(keychainManager: keychainManager)) + (viewController as? LoginCoordinated)?.loginCoordinator = loginFlowCoordinator + (viewController as? AddIssueViewModelType)?.setAddIssueViewModel(addIssueViewModel) + (viewController as? IssueNetworked)?.setIssueNetworkController(IssueNetworkController()) + (viewController as? IssueViewModelType)?.setIssueViewModel(issueViewModel) + + if let tabBarController = viewController as? UITabBarController { + tabBarController.viewControllers?.forEach(configure(viewController:)) + } + if let navigationController = viewController as? UINavigationController, + let rootViewController = navigationController.viewControllers.first { + configure(viewController: rootViewController) + } + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Domain/UseCase/Coordinator/LoginFlowCoordinator.swift b/iOS/issueTrackerApp/issueTrackerApp/Domain/UseCase/Coordinator/LoginFlowCoordinator.swift new file mode 100644 index 000000000..fc56c0702 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Domain/UseCase/Coordinator/LoginFlowCoordinator.swift @@ -0,0 +1,63 @@ +// +// LoginFlowCoordinator.swift +// issueTrackerApp +// +// Created by zombietux on 2021/06/08. +// + +import UIKit +import AuthenticationServices + +class LoginFlowCoordinator: NSObject, ASWebAuthenticationPresentationContextProviding { + weak var parent: Coordinator? + weak var loginViewController: UIViewController? + + func mainViewControllerRequiresAuthentication(_ viewController: MainTabBarController, isAppLaunch: Bool) { + let newViewController = viewController.storyboard?.instantiateViewController(withIdentifier: ViewControllerIDs.loginViewController) + guard let loginViewController = newViewController as? LoginViewController else { + return + } + self.loginViewController = loginViewController + configure(viewController: loginViewController) + viewController.present(loginViewController, animated: !isAppLaunch, completion: nil) + } + + func loginViewController(_ viewController: LoginViewController, didStartAuthorizationWithState state: String) { + let url = GitHubEndpoint.authorizationUrl() + + let session = ASWebAuthenticationSession(url: url, callbackURLScheme: GitHubEndpoint.authorizationCallbackURLScheme) + { callbackURL, error in + guard error == nil, let callbackURL = callbackURL else { return } + + if let authorizationCode = callbackURL.authorizationCode { + viewController.performAuthorization(with: authorizationCode) + } + } + session.presentationContextProvider = self + session.start() + } + + func loginViewControllerDidFinishAuthorization() { + loginViewController?.dismiss(animated: true, completion: nil) + } + + func loginViewControllerDidSignOut() { + UIApplication.shared.open(GitHubEndpoint.signOutURL, options: [:], completionHandler: nil) + } + + func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { + return ASPresentationAnchor() + } +} + +extension LoginFlowCoordinator: Coordinator { + func configure(viewController: UIViewController) { + parent?.configure(viewController: viewController) + } +} + +extension LoginFlowCoordinator { + struct ViewControllerIDs { + static let loginViewController = "LoginViewController" + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Info.plist b/iOS/issueTrackerApp/issueTrackerApp/Info.plist new file mode 100644 index 000000000..73401050a --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Info.plist @@ -0,0 +1,79 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + com.zombietux.issueTrackerApp + CFBundleURLSchemes + + issuetracker + + + + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/iOS/issueTrackerApp/issueTrackerApp/Label.storyboard b/iOS/issueTrackerApp/issueTrackerApp/Label.storyboard new file mode 100644 index 000000000..e4151cb41 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Label.storyboard @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/issueTrackerApp/issueTrackerApp/NotificationNameExtension.swift b/iOS/issueTrackerApp/issueTrackerApp/NotificationNameExtension.swift new file mode 100644 index 000000000..99c153542 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/NotificationNameExtension.swift @@ -0,0 +1,13 @@ +// +// NotificationNameExtension.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/14. +// + +import Foundation + +extension Notification.Name { + static let didFilterIssueData = Notification.Name("didFilterIssueData") + static let didReceiveIssueData = Notification.Name("didReceiveIssueData") +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/Issue/IssueViewController.swift b/iOS/issueTrackerApp/issueTrackerApp/Presentation/Issue/IssueViewController.swift new file mode 100644 index 000000000..5a7eacdba --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/Issue/IssueViewController.swift @@ -0,0 +1,29 @@ +// +// IssueViewController.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/08. +// + +import UIKit + +class IssueViewController: 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. + } + */ + +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/MainTabBarController.swift b/iOS/issueTrackerApp/issueTrackerApp/Presentation/MainTabBarController.swift new file mode 100644 index 000000000..3a101e457 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/MainTabBarController.swift @@ -0,0 +1,30 @@ +// +// MainTabBarController.swift +// issueTrackerApp +// +// Created by zombietux on 2021/06/08. +// + +import UIKit + +class MainTabBarController: UITabBarController, MainCoordinated, LoginNetworked, LoginCoordinated { + private var loginNetworkManager: LoginNetworkManager! + weak var mainCoordinator: MainFlowCoordinator? + weak var loginCoordinator: LoginFlowCoordinator? + + override func viewDidLoad() { + super.viewDidLoad() + guard (loginNetworkManager?.isClientAuthenticated ?? false) else { + loginCoordinator?.mainViewControllerRequiresAuthentication(self, isAppLaunch: true) + return + } + } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + loginCoordinator?.configure(viewController: segue.destination) + } + + func setLoginNetworkManager(_ loginNetworkManager: LoginNetworkManager) { + self.loginNetworkManager = loginNetworkManager + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/UIControl/CustomBarButtonItem.swift b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/UIControl/CustomBarButtonItem.swift new file mode 100644 index 000000000..f48388264 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/UIControl/CustomBarButtonItem.swift @@ -0,0 +1,85 @@ +// +// CustomBarButtonItem.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/10. +// + +import UIKit + +class CustomBarButtonItem: UIButton { + + var customImageView: UIImageView = { + let imageView = UIImageView() + imageView.translatesAutoresizingMaskIntoConstraints = false + imageView.contentMode = .scaleAspectFit + imageView.tintColor = .systemBlue + return imageView + }() + + var customTitleLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.textColor = .systemBlue + return label + }() + + + enum ImageLocation { + case left + case right + } + + init(title: String, image: UIImage, located: ImageLocation) { + super.init(frame: .zero) + + self.customTitleLabel.text = title + self.customImageView.image = image.withAlignmentRectInsets(UIEdgeInsets(top: -10, left: 0, bottom: -10, right: 0)) + self.addSubview(customTitleLabel) + self.addSubview(customImageView) + + if located == .left { + configureWithImageAtLeftSide() + } else { + configureWithImageAtRightSide() + } + + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + assertionFailure("Initializing CustomBarButtonItem with coder should not be called") + } + + private func configureWithImageAtLeftSide() { + self.customTitleLabel.textAlignment = .left + + NSLayoutConstraint.activate([ + self.customImageView.topAnchor.constraint(equalTo: self.topAnchor), + self.customImageView.bottomAnchor.constraint(equalTo: self.bottomAnchor), + self.customImageView.leadingAnchor.constraint(equalTo: self.leadingAnchor), + self.customImageView.widthAnchor.constraint(equalTo: self.heightAnchor), + + self.customTitleLabel.topAnchor.constraint(equalTo: self.topAnchor), + self.customTitleLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor), + self.customTitleLabel.leadingAnchor.constraint(equalTo: customImageView.trailingAnchor) + ]) + + } + + private func configureWithImageAtRightSide() { + self.customTitleLabel.textAlignment = .right + + NSLayoutConstraint.activate([ + self.customImageView.topAnchor.constraint(equalTo: self.topAnchor), + self.customImageView.bottomAnchor.constraint(equalTo: self.bottomAnchor), + self.customImageView.trailingAnchor.constraint(equalTo: self.trailingAnchor), + self.customImageView.widthAnchor.constraint(equalTo: self.heightAnchor), + + self.customTitleLabel.topAnchor.constraint(equalTo: self.topAnchor), + self.customTitleLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor), + self.customTitleLabel.trailingAnchor.constraint(equalTo: customImageView.leadingAnchor, constant: 5) + ]) + } + +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/UIView/Issue/IssueCell.swift b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/UIView/Issue/IssueCell.swift new file mode 100644 index 000000000..9f6923e3b --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/UIView/Issue/IssueCell.swift @@ -0,0 +1,64 @@ +// +// IssueCell.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/09. +// + +import UIKit + +class IssueCell: UITableViewCell { + @IBOutlet weak var titleLabel: UILabel! + @IBOutlet weak var descriptionLabel: UILabel! + @IBOutlet weak var milestonesLabel: UILabel! + @IBOutlet weak var tagStackView: TagStackView! + + static var identifier: String { + return String(describing: self) + } + + static var nib: UINib { + return UINib(nibName: identifier, bundle: nil) + } + + override func awakeFromNib() { + super.awakeFromNib() + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + } + + // will fetch issue data type from server later on + public func configureAll(_ issue: String? = nil) { + self.configureTitleLabel(with: issue) + self.configureDescriptionLabel(with: issue) + self.configureMileStonesLabel(with: issue) + self.configureTagLabelStack(with: issue) + } + + private func configureTitleLabel(with issue: String? = nil) { + self.titleLabel.text = "issue.title" + } + + private func configureDescriptionLabel(with issue: String? = nil) { + self.descriptionLabel.text = "issue.description" + } + + private func configureMileStonesLabel(with issue: String? = nil) { + let fullString = NSMutableAttributedString(string: "") + let imageAttachment = NSTextAttachment() + imageAttachment.image = UIImage(systemName: "exclamationmark.circle") + let imageString = NSAttributedString(attachment: imageAttachment) + fullString.append(imageString) + fullString.append(NSAttributedString(string: "issue.mileStoneName")) + + self.milestonesLabel.attributedText = fullString + } + + private func configureTagLabelStack(with issue: String? = nil) { + let tempTagLabel = TagLabel() + tempTagLabel.custom(title: " issue.tagTitle ", colorCode: "#f69e7b") + self.tagStackView.addTag(tagLabel: tempTagLabel) + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/UIView/Issue/IssueCell.xib b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/UIView/Issue/IssueCell.xib new file mode 100644 index 000000000..1d71e9bd2 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/UIView/Issue/IssueCell.xib @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/UIView/Issue/MoreInfoCell.swift b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/UIView/Issue/MoreInfoCell.swift new file mode 100644 index 000000000..68d51998d --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/UIView/Issue/MoreInfoCell.swift @@ -0,0 +1,34 @@ +// +// MoreInfoCell.swift +// issueTrackerApp +// +// Created by zombietux on 2021/06/12. +// + +import UIKit + +class MoreInfoCell: UITableViewCell { + @IBOutlet private weak var titleLabel: UILabel! + @IBOutlet private weak var infoLabel: UILabel! + + var viewModel = ViewModel() { + didSet { + titleLabel.text = viewModel.title + infoLabel.text = viewModel.info + } + } +} + +extension MoreInfoCell { + struct ViewModel { + var title = "" + var info = "" + } +} + +extension MoreInfoCell.ViewModel { + init(moreInfo: MoreInfo) { + title = moreInfo.title + info = moreInfo.info + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/UIView/Issue/MoreInfoTableViewDataSource.swift b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/UIView/Issue/MoreInfoTableViewDataSource.swift new file mode 100644 index 000000000..7557320bb --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/UIView/Issue/MoreInfoTableViewDataSource.swift @@ -0,0 +1,53 @@ +// +// MoreInfoTableViewDataSource.swift +// issueTrackerApp +// +// Created by zombietux on 2021/06/12. +// + +import UIKit + +class MoreInfoTableViewDataSource: NSObject { + internal var dataOrganizer: ArrayDataSourceOrganizer + internal var viewModelCache: [IndexPath : MoreInfoCell.ViewModel] = [:] + + init(moreInfos: [MoreInfo]) { + dataOrganizer = ArrayDataSourceOrganizer(items: moreInfos) + super.init() + } +} + +private extension MoreInfoTableViewDataSource { + struct DataOrganizer { + let moreInfos: [MoreInfo] + + var rowsCount: Int { + return moreInfos.count + } + + subscript(indexPath: IndexPath) -> MoreInfo { + return moreInfos[indexPath.row] + } + } +} + +extension MoreInfoTableViewDataSource: ArrayTableViewDataSource { + func viewModel(for value: ModelType) -> MoreInfoCell.ViewModel { + return MoreInfoCell.ViewModel(moreInfo: value) + } + + func configure(cell: MoreInfoCell, with viewModel: MoreInfoCell.ViewModel) { + cell.viewModel = viewModel + } +} + +extension MoreInfoTableViewDataSource: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return rowsCount + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + return cell(from: tableView, for: indexPath) + } +} + diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/UIView/Issue/TagLabel.swift b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/UIView/Issue/TagLabel.swift new file mode 100644 index 000000000..dc812c87d --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/UIView/Issue/TagLabel.swift @@ -0,0 +1,72 @@ +// +// TagLabel.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/09. +// + +import UIKit + +class TagLabel: UILabel { + + init() { + super.init(frame: .zero) + self.layer.cornerRadius = 10 + self.layer.masksToBounds = true + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + assertionFailure("Init with coder should not be called") + } + + public func custom(title: String, colorCode: String) { + self.text = title + self.backgroundColor = UIColor.hexString2UIColor(hexString: colorCode) + self.textColor = .white + } + + public func openIssue(of number: Int) { + let textColor = UIColor.hexString2UIColor(hexString: "#04009a") ?? .red + let backgroundColor = UIColor.hexString2UIColor(hexString: "#c0fefc") + + let font = UIFont.systemFont(ofSize: 30) + let attributes: [NSAttributedString.Key: Any] = [ + .font: font, + .foregroundColor: textColor, + ] + + let fullString = NSMutableAttributedString(string: "", attributes: attributes) + let imageAttachment = NSTextAttachment() + imageAttachment.image = UIImage(systemName: "exclamationmark.circle") + let imageString = NSAttributedString(attachment: imageAttachment) + fullString.append(imageString) + fullString.append(NSAttributedString(string: "열린 이슈 \(number)개")) + + self.backgroundColor = backgroundColor + self.layer.borderWidth = 1 + self.layer.borderColor = textColor.cgColor + } + + public func closedIssue(of number: Int) { + let textColor = UIColor.hexString2UIColor(hexString: "#3b14a7") ?? .red + let backgroundColor = UIColor.hexString2UIColor(hexString: "#ac66cc") + + let font = UIFont.systemFont(ofSize: 30) + let attributes: [NSAttributedString.Key: Any] = [ + .font: font, + .foregroundColor: textColor, + ] + + let fullString = NSMutableAttributedString(string: "", attributes: attributes) + let imageAttachment = NSTextAttachment() + imageAttachment.image = UIImage(systemName: "archivebox") + let imageString = NSAttributedString(attachment: imageAttachment) + fullString.append(imageString) + fullString.append(NSAttributedString(string: "닫힌 이슈 \(number)개")) + + self.backgroundColor = backgroundColor + self.layer.borderWidth = 1 + self.layer.borderColor = textColor.cgColor + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/UIView/Issue/TagStackView.swift b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/UIView/Issue/TagStackView.swift new file mode 100644 index 000000000..f6a16412e --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/UIView/Issue/TagStackView.swift @@ -0,0 +1,17 @@ +// +// TagStackView.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/09. +// + +import UIKit + +class TagStackView: UIStackView { + + public func addTag(tagLabel: UILabel) { + tagLabel.translatesAutoresizingMaskIntoConstraints = false + self.addArrangedSubview(tagLabel) + } + +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/AddIssueViewController.swift b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/AddIssueViewController.swift new file mode 100644 index 000000000..64efcea36 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/AddIssueViewController.swift @@ -0,0 +1,94 @@ +// +// AddIssueViewController.swift +// issueTrackerApp +// +// Created by zombietux on 2021/06/11. +// + +import UIKit + +class AddIssueViewController: UIViewController, AddIssueViewModelType, MainCoordinated { + @IBOutlet private weak var cancelButton: UIButton! + @IBOutlet private weak var saveButton: UIButton! + @IBOutlet private weak var markdownSegmentedControl: UISegmentedControl! + @IBOutlet private weak var titleTextField: UITextField! + @IBOutlet private weak var moreInformationView: UIView! + @IBOutlet private weak var commentView: UIView! + + private var addIssueViewModel: AddIssueViewModel! + weak var mainCoordinator: MainFlowCoordinator? + private lazy var commentInputViewControler: CommentViewController = { + let storyboard = UIStoryboard(name: "Issue", bundle: Bundle.main) + var viewController = storyboard.instantiateViewController(identifier: "CommentViewController") as! CommentViewController + viewController.setAddIssueViewModel(self.addIssueViewModel) + self.add(asChildViewController: viewController) + return viewController + }() + + private lazy var markdownInputViewControler: MarkdownViewController = { + let storyboard = UIStoryboard(name: "Issue", bundle: Bundle.main) + var viewController = storyboard.instantiateViewController(identifier: "MarkdownViewController") as! MarkdownViewController + viewController.setAddIssueViewModel(self.addIssueViewModel) + self.add(asChildViewController: viewController) + return viewController + }() + + override func viewDidLoad() { + super.viewDidLoad() + view.addSubview(moreInformationView) + setupView() + } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + mainCoordinator?.configure(viewController: segue.destination) + } + + func setAddIssueViewModel(_ addIssueViewModel: AddIssueViewModel) { + self.addIssueViewModel = addIssueViewModel + } +} + +//MARK:- Segmented Control +extension AddIssueViewController { + func setupView() { + configureSegmentedControl() + updateView() + } + + private func configureSegmentedControl() { + markdownSegmentedControl.removeAllSegments() + markdownSegmentedControl.insertSegment(withTitle: "마크다운", at: 0, animated: true) + markdownSegmentedControl.insertSegment(withTitle: "미리보기", at: 1, animated: true) + markdownSegmentedControl.addTarget(self, action: #selector(selectionDidChange(_:)), for: .valueChanged) + + markdownSegmentedControl.selectedSegmentIndex = 0 + } + + @objc func selectionDidChange(_ sender: UISegmentedControl) { + updateView() + } + + private func updateView() { + if markdownSegmentedControl.selectedSegmentIndex == 0 { + remove(asChildViewController: markdownInputViewControler) + add(asChildViewController: commentInputViewControler) + } else { + remove(asChildViewController: commentInputViewControler) + add(asChildViewController: markdownInputViewControler) + } + } + + private func add(asChildViewController viewController: UIViewController) { + addChild(viewController) + commentView.addSubview(viewController.view) + viewController.view.frame = view.bounds + viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] + viewController.didMove(toParent: self) + } + + private func remove(asChildViewController viewController: UIViewController) { + viewController.willMove(toParent: nil) + viewController.view.removeFromSuperview() + viewController.removeFromParent() + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/CommentViewController.swift b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/CommentViewController.swift new file mode 100644 index 000000000..27a6cd115 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/CommentViewController.swift @@ -0,0 +1,40 @@ +// +// CommentViewController.swift +// issueTrackerApp +// +// Created by zombietux on 2021/06/15. +// + +import UIKit + +class CommentViewController: UIViewController, AddIssueViewModelType, MainCoordinated { + + @IBOutlet weak var commentTextView: UITextView! + + private var addIssueViewModel: AddIssueViewModel! + weak var mainCoordinator: MainFlowCoordinator? + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + commentTextView.delegate = self + } + + func setAddIssueViewModel(_ addIssueViewModel: AddIssueViewModel) { + self.addIssueViewModel = addIssueViewModel + } +} + +extension CommentViewController: UITextViewDelegate { + func textViewDidEndEditing(_ textView: UITextView) { + addIssueViewModel?.updateComment(textView.text ?? "") + } + + func textViewDidBeginEditing(_ textView: UITextView) { + let menuItem = UIMenuItem(title: "Insert Photo", action: #selector(textViewDidTapped)) + UIMenuController.shared.menuItems = [menuItem] + } + + @objc func textViewDidTapped() { + + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/CustomBarButtonItem.swift b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/CustomBarButtonItem.swift new file mode 100644 index 000000000..f48388264 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/CustomBarButtonItem.swift @@ -0,0 +1,85 @@ +// +// CustomBarButtonItem.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/10. +// + +import UIKit + +class CustomBarButtonItem: UIButton { + + var customImageView: UIImageView = { + let imageView = UIImageView() + imageView.translatesAutoresizingMaskIntoConstraints = false + imageView.contentMode = .scaleAspectFit + imageView.tintColor = .systemBlue + return imageView + }() + + var customTitleLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.textColor = .systemBlue + return label + }() + + + enum ImageLocation { + case left + case right + } + + init(title: String, image: UIImage, located: ImageLocation) { + super.init(frame: .zero) + + self.customTitleLabel.text = title + self.customImageView.image = image.withAlignmentRectInsets(UIEdgeInsets(top: -10, left: 0, bottom: -10, right: 0)) + self.addSubview(customTitleLabel) + self.addSubview(customImageView) + + if located == .left { + configureWithImageAtLeftSide() + } else { + configureWithImageAtRightSide() + } + + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + assertionFailure("Initializing CustomBarButtonItem with coder should not be called") + } + + private func configureWithImageAtLeftSide() { + self.customTitleLabel.textAlignment = .left + + NSLayoutConstraint.activate([ + self.customImageView.topAnchor.constraint(equalTo: self.topAnchor), + self.customImageView.bottomAnchor.constraint(equalTo: self.bottomAnchor), + self.customImageView.leadingAnchor.constraint(equalTo: self.leadingAnchor), + self.customImageView.widthAnchor.constraint(equalTo: self.heightAnchor), + + self.customTitleLabel.topAnchor.constraint(equalTo: self.topAnchor), + self.customTitleLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor), + self.customTitleLabel.leadingAnchor.constraint(equalTo: customImageView.trailingAnchor) + ]) + + } + + private func configureWithImageAtRightSide() { + self.customTitleLabel.textAlignment = .right + + NSLayoutConstraint.activate([ + self.customImageView.topAnchor.constraint(equalTo: self.topAnchor), + self.customImageView.bottomAnchor.constraint(equalTo: self.bottomAnchor), + self.customImageView.trailingAnchor.constraint(equalTo: self.trailingAnchor), + self.customImageView.widthAnchor.constraint(equalTo: self.heightAnchor), + + self.customTitleLabel.topAnchor.constraint(equalTo: self.topAnchor), + self.customTitleLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor), + self.customTitleLabel.trailingAnchor.constraint(equalTo: customImageView.leadingAnchor, constant: 5) + ]) + } + +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/IssueCell.swift b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/IssueCell.swift new file mode 100644 index 000000000..59d18c04a --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/IssueCell.swift @@ -0,0 +1,74 @@ +// +// IssueCell.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/09. +// + +import UIKit + +class IssueCell: UITableViewCell { + @IBOutlet weak var titleLabel: UILabel! + @IBOutlet weak var descriptionLabel: UILabel! + @IBOutlet weak var milestonesLabel: UILabel! + @IBOutlet weak var tagStackView: TagStackView! + + static var identifier: String { + return String(describing: self) + } + + static var nib: UINib { + return UINib(nibName: identifier, bundle: nil) + } + + override func awakeFromNib() { + super.awakeFromNib() + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + } + + // will fetch issue data type from server later on + public func configureAll(with issue: Issue) { + self.clearCell() + self.configureTitleLabel(with: issue) + self.configureDescriptionLabel(with: issue) + self.configureMileStonesLabel(with: issue) + self.configureTagLabelStack(with: issue) + } + + private func clearCell() { + self.titleLabel.text = "" + self.descriptionLabel.text = "" + self.milestonesLabel.attributedText = NSAttributedString(string: "") + self.tagStackView.removeAllTags() + } + + private func configureTitleLabel(with issue: Issue) { + self.titleLabel.text = issue.title + } + + private func configureDescriptionLabel(with issue: Issue) { + self.descriptionLabel.text = issue.description + } + + private func configureMileStonesLabel(with issue: Issue) { + let fullString = NSMutableAttributedString(string: "") + let imageAttachment = NSTextAttachment() + imageAttachment.image = UIImage(systemName: "exclamationmark.circle") + let imageString = NSAttributedString(attachment: imageAttachment) + fullString.append(imageString) + fullString.append(NSAttributedString(string: issue.milestoneTitle)) + + self.milestonesLabel.attributedText = fullString + } + + private func configureTagLabelStack(with issue: Issue) { + issue.labelList.forEach { (label) in + let tempTagLabel = TagLabel() + tempTagLabel.custom(title: " \(label.title) ", colorCode: "\(label.colorCode)") + self.tagStackView.addTag(tagLabel: tempTagLabel) + } + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/IssueCell.xib b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/IssueCell.xib new file mode 100644 index 000000000..1d71e9bd2 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/IssueCell.xib @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/IssueSelectTableViewController.swift b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/IssueSelectTableViewController.swift new file mode 100644 index 000000000..8ff2158f8 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/IssueSelectTableViewController.swift @@ -0,0 +1,57 @@ +// +// IssueSelectTableViewController.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/15. +// + +import UIKit + +class IssueSelectTableViewController: UITableViewController, IssueViewModelType, MainCoordinated { + + private var issueViewModel: IssueViewModel! + var mainCoordinator: MainFlowCoordinator? + override func viewDidLoad() { + super.viewDidLoad() + + self.configureTableView() + self.configureBackBarButtonItem() + self.configureRightBarButtonItem() + } + + func setIssueViewModel(_ issueViewModel: IssueViewModel) { + self.issueViewModel = issueViewModel + } + + private func configureTableView() { + self.tableView.register(IssueCell.nib, forCellReuseIdentifier: IssueCell.identifier) + self.tableView.allowsMultipleSelection = true + } + + private func configureBackBarButtonItem() { + self.navigationItem.hidesBackButton = true + } + + private func configureRightBarButtonItem() { + let rightBarButtonItem = UIBarButtonItem(title: "취소", image: nil, primaryAction: UIAction.init(handler: { (touch) in + self.navigationController?.popViewController(animated: true) + }), menu: nil) + + self.navigationItem.rightBarButtonItem = rightBarButtonItem + } + + // MARK: - Table view data source + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return issueViewModel?.issues.count ?? 0 + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + guard let cell = self.tableView.dequeueReusableCell(withIdentifier: IssueCell.identifier) as? IssueCell else { return UITableViewCell() } + guard let issue = issueViewModel?.issues[indexPath.row] else { return UITableViewCell() } + cell.configureAll(with: issue) + + return cell + } + +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/IssueViewController.swift b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/IssueViewController.swift new file mode 100644 index 000000000..67279bf23 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/IssueViewController.swift @@ -0,0 +1,154 @@ +// +// IssueViewController.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/08. +// + +import UIKit + +class IssueViewController: UIViewController, IssueViewModelType, MainCoordinated { + + @IBOutlet weak var issueTableView: UITableView! + + private var issueViewModel: IssueViewModel! + var mainCoordinator: MainFlowCoordinator? + let searchController = UISearchController(searchResultsController: nil) + var isSearchBarEmpty: Bool { + return searchController.searchBar.text?.isEmpty ?? true + } + var isFiltering: Bool { + return searchController.isActive && !isSearchBarEmpty + } + + override func viewDidLoad() { + super.viewDidLoad() + self.configureLeftBarButtonItem() + self.configureRightBarButtonItem() + self.configureTableView() + self.issueViewModel?.fetchAllIssue() + self.configureNotificationCenter() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + self.configureSearchController() + } + + func setIssueViewModel(_ issueViewModel: IssueViewModel) { + self.issueViewModel = issueViewModel + } + + private func configureNotificationCenter() { + NotificationCenter.default.addObserver(self, selector: #selector(onDidReceiveIssueData), name: .didReceiveIssueData, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(onDidFilterIssueData), name: .didFilterIssueData, object: nil) + } + + @objc func onDidFilterIssueData() { + self.issueTableView.reloadData() + } + + @objc func onDidReceiveIssueData() { + self.issueTableView.reloadData() + } + + private func configureLeftBarButtonItem() { + let customLeftBarButton = CustomBarButtonItem(title: "필터", image: UIImage(systemName: "line.horizontal.3.decrease") ?? UIImage(), located: .left) + customLeftBarButton.addAction(UIAction.init(handler: { (touch) in + // to do + }), for: .touchUpInside) + let leftBarButtonItem = UIBarButtonItem(customView: customLeftBarButton) + self.navigationItem.leftBarButtonItem = leftBarButtonItem + } + + private func configureRightBarButtonItem() { + let customRightBarButton = CustomBarButtonItem(title: "선택", image: UIImage(systemName: "checkmark.circle") ?? UIImage(), located: .right) + customRightBarButton.addAction(UIAction(handler: { [weak self] (touch) in + + guard let self = self else { return } + let targetVC = self.storyboard?.instantiateViewController(identifier: "IssueSelectTableViewController") as! IssueSelectTableViewController + self.mainCoordinator?.configure(viewController: targetVC) + self.navigationController?.pushViewController(targetVC, animated: true) + + }), for: .touchUpInside) + let rightBarButtonItem = UIBarButtonItem(customView: customRightBarButton) + self.navigationItem.rightBarButtonItem = rightBarButtonItem + } + + private func configureSearchController() { + self.searchController.obscuresBackgroundDuringPresentation = false + self.searchController.searchBar.placeholder = "Search" + definesPresentationContext = true + self.searchController.searchResultsUpdater = self + navigationItem.searchController = searchController + } + + private func configureTableView() { + self.issueTableView.register(IssueCell.nib, forCellReuseIdentifier: IssueCell.identifier) + self.issueTableView.dataSource = self + self.issueTableView.delegate = self + } +} + +extension IssueViewController: UISearchResultsUpdating { + + func updateSearchResults(for searchController: UISearchController) { + let searchBar = searchController.searchBar + issueViewModel?.filterIssuesWithSearchText(searchBar.text!) + } +} + +extension IssueViewController: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + + if isFiltering { + guard let filteredIssue = issueViewModel?.filteredIssues else { return 0 } + return filteredIssue.count + } + guard let issues = issueViewModel?.issues else { return 0 } + return issues.count + + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = self.issueTableView.dequeueReusableCell(withIdentifier: IssueCell.identifier) as! IssueCell + + if isFiltering { + guard let filteredIssues = issueViewModel?.filteredIssues else { return cell } + cell.configureAll(with: filteredIssues[indexPath.row]) + } else { + guard let issues = issueViewModel?.issues else { return cell } + cell.configureAll(with: issues[indexPath.row]) + } + + return cell + } + +} + +extension IssueViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, + trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + // delete action + let delete = UIContextualAction(style: .destructive, + title: "삭제") { [weak self] (action, view, completionHandler) in + self?.issueViewModel?.deleteIssue(at: indexPath.row, completionHandler: { + completionHandler(true) + }) + } + delete.backgroundColor = .systemRed + delete.image = UIImage(systemName: "trash") + + // close action + let close = UIContextualAction(style: .normal, + title: "닫기") { [weak self] (action, view, completionHandler) in + // todo + completionHandler(true) + } + close.backgroundColor = UIColor.hexString2UIColor(hexString: "#CCD4FF") + close.image = UIImage(systemName: "archivebox") + + let configuration = UISwipeActionsConfiguration(actions: [close, delete]) + return configuration + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/MarkdownViewController.swift b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/MarkdownViewController.swift new file mode 100644 index 000000000..fe4933ad0 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/MarkdownViewController.swift @@ -0,0 +1,31 @@ +// +// MarkdownViewController.swift +// issueTrackerApp +// +// Created by zombietux on 2021/06/15. +// + +import UIKit +import MarkdownKit + +class MarkdownViewController: UIViewController, AddIssueViewModelType, MainCoordinated { + + @IBOutlet weak var markdownLabel: UILabel! + + private var addIssueViewModel: AddIssueViewModel! + weak var mainCoordinator: MainFlowCoordinator? + private let markdownParser = MarkdownParser() + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + configureMarkdownView() + } + + func setAddIssueViewModel(_ addIssueViewModel: AddIssueViewModel) { + self.addIssueViewModel = addIssueViewModel + } + + private func configureMarkdownView() { + markdownLabel.attributedText = markdownParser.parse(addIssueViewModel?.comment ?? "") + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/MoreInformationViewController.swift b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/MoreInformationViewController.swift new file mode 100644 index 000000000..612f15a5e --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/MoreInformationViewController.swift @@ -0,0 +1,33 @@ +// +// MoreInformationViewController.swift +// issueTrackerApp +// +// Created by zombietux on 2021/06/11. +// + +import UIKit + +class MoreInformationViewController: UIViewController, AddIssueViewModelType, MainCoordinated { + + @IBOutlet private weak var tableView: UITableView! + private var dataSource: MoreInfoTableViewDataSource? + private var addIssueViewModel: AddIssueViewModel! + weak var mainCoordinator: MainFlowCoordinator? + + override func viewDidLoad() { + super.viewDidLoad() + guard let moreInfos = addIssueViewModel?.moreInfos else { return } + let dataSource = MoreInfoTableViewDataSource(moreInfos: moreInfos) + self.dataSource = dataSource + tableView.dataSource = dataSource + tableView.reloadData() + } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + mainCoordinator?.configure(viewController: segue.destination) + } + + func setAddIssueViewModel(_ addIssueViewModel: AddIssueViewModel) { + self.addIssueViewModel = addIssueViewModel + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/TagLabel.swift b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/TagLabel.swift new file mode 100644 index 000000000..dc812c87d --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/TagLabel.swift @@ -0,0 +1,72 @@ +// +// TagLabel.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/09. +// + +import UIKit + +class TagLabel: UILabel { + + init() { + super.init(frame: .zero) + self.layer.cornerRadius = 10 + self.layer.masksToBounds = true + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + assertionFailure("Init with coder should not be called") + } + + public func custom(title: String, colorCode: String) { + self.text = title + self.backgroundColor = UIColor.hexString2UIColor(hexString: colorCode) + self.textColor = .white + } + + public func openIssue(of number: Int) { + let textColor = UIColor.hexString2UIColor(hexString: "#04009a") ?? .red + let backgroundColor = UIColor.hexString2UIColor(hexString: "#c0fefc") + + let font = UIFont.systemFont(ofSize: 30) + let attributes: [NSAttributedString.Key: Any] = [ + .font: font, + .foregroundColor: textColor, + ] + + let fullString = NSMutableAttributedString(string: "", attributes: attributes) + let imageAttachment = NSTextAttachment() + imageAttachment.image = UIImage(systemName: "exclamationmark.circle") + let imageString = NSAttributedString(attachment: imageAttachment) + fullString.append(imageString) + fullString.append(NSAttributedString(string: "열린 이슈 \(number)개")) + + self.backgroundColor = backgroundColor + self.layer.borderWidth = 1 + self.layer.borderColor = textColor.cgColor + } + + public func closedIssue(of number: Int) { + let textColor = UIColor.hexString2UIColor(hexString: "#3b14a7") ?? .red + let backgroundColor = UIColor.hexString2UIColor(hexString: "#ac66cc") + + let font = UIFont.systemFont(ofSize: 30) + let attributes: [NSAttributedString.Key: Any] = [ + .font: font, + .foregroundColor: textColor, + ] + + let fullString = NSMutableAttributedString(string: "", attributes: attributes) + let imageAttachment = NSTextAttachment() + imageAttachment.image = UIImage(systemName: "archivebox") + let imageString = NSAttributedString(attachment: imageAttachment) + fullString.append(imageString) + fullString.append(NSAttributedString(string: "닫힌 이슈 \(number)개")) + + self.backgroundColor = backgroundColor + self.layer.borderWidth = 1 + self.layer.borderColor = textColor.cgColor + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/TagStackView.swift b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/TagStackView.swift new file mode 100644 index 000000000..5a763fba6 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Issue/TagStackView.swift @@ -0,0 +1,22 @@ +// +// TagStackView.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/09. +// + +import UIKit + +class TagStackView: UIStackView { + + public func addTag(tagLabel: UILabel) { + tagLabel.translatesAutoresizingMaskIntoConstraints = false + self.addArrangedSubview(tagLabel) + } + + public func removeAllTags() { + self.arrangedSubviews.forEach { (view) in + view.removeFromSuperview() + } + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Login/LoginViewController.swift b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Login/LoginViewController.swift new file mode 100644 index 000000000..2f1b81d64 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Login/LoginViewController.swift @@ -0,0 +1,88 @@ +// +// LoginViewController.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/08. +// + +import UIKit +import AuthenticationServices + +class LoginViewController: UIViewController, LoginCoordinated, LoginNetworked { + + @IBOutlet weak var gitHubLoginButton: UIButton! + @IBOutlet weak var appleLoginButton: UIStackView! + + private let state = UUID().description + weak var loginCoordinator: LoginFlowCoordinator? + private var loginNetworkManager: LoginNetworkManager! + + var isAuthenticating: Bool = false { + didSet { + gitHubLoginButton.isEnabled = !isAuthenticating + } + } + + override func viewDidLoad() { + super.viewDidLoad() + configAppleLoginButton() + } + + func setLoginNetworkManager(_ loginNetworkManager: LoginNetworkManager) { + self.loginNetworkManager = loginNetworkManager + } +} + +//MARK:- APPLE ID LOGIN + +extension LoginViewController: ASAuthorizationControllerPresentationContextProviding, ASAuthorizationControllerDelegate { + private func configAppleLoginButton() { + let authorizationButton = ASAuthorizationAppleIDButton(type: .signIn, style: .black) + authorizationButton.addTarget(self, action: #selector(appleLoginButtonTapped), for: .touchUpInside) + self.appleLoginButton.addArrangedSubview(authorizationButton) + } + + @objc func appleLoginButtonTapped() { + let appleIDProvider = ASAuthorizationAppleIDProvider() + let request = appleIDProvider.createRequest() + request.requestedScopes = [.fullName, .email] + + let authorizationController = ASAuthorizationController(authorizationRequests: [request]) + authorizationController.delegate = self + authorizationController.presentationContextProvider = self + authorizationController.performRequests() + } + + func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { + return self.view.window! + } + + func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { + switch authorization.credential { + case let appleIDCredential as ASAuthorizationAppleIDCredential: + let accessToken = appleIDCredential.user + let fullName = appleIDCredential.fullName + let email = appleIDCredential.email + default: + break + } + } + + func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { + //to do + } +} + +//MARK:- GitHub LOGIN +extension LoginViewController { + func performAuthorization(with authorizationCode: String) { + isAuthenticating = true + loginNetworkManager?.authenticateWith(authorizationCode: authorizationCode, client: GitHubEndpoint.FieldNames.client) { [weak self] in + self?.loginCoordinator?.loginViewControllerDidFinishAuthorization() + } + } + + @IBAction func gitHubLoginButtonTapped(_ sender: Any) { + loginCoordinator?.loginViewController(self, didStartAuthorizationWithState: state) + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Setting/SettingViewController.swift b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Setting/SettingViewController.swift new file mode 100644 index 000000000..acb9acf40 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/UI/ViewController/Setting/SettingViewController.swift @@ -0,0 +1,27 @@ +// +// SettingViewController.swift +// issueTrackerApp +// +// Created by zombietux on 2021/06/11. +// + +import UIKit + +class SettingViewController: UIViewController, MainCoordinated, LoginNetworked { + private var loginNetworkManager: LoginNetworkManager! + weak var mainCoordinator: MainFlowCoordinator? + + override func viewDidLoad() { + super.viewDidLoad() + + } + + func setLoginNetworkManager(_ loginNetworkManager: LoginNetworkManager) { + self.loginNetworkManager = loginNetworkManager + } + + @IBAction func logoutButtonTapped(_ sender: Any) { + loginNetworkManager?.logOut() + mainCoordinator?.logOut() + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/ViewModel/AddIssueViewModel.swift b/iOS/issueTrackerApp/issueTrackerApp/Presentation/ViewModel/AddIssueViewModel.swift new file mode 100644 index 000000000..9485f683f --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/ViewModel/AddIssueViewModel.swift @@ -0,0 +1,26 @@ +// +// AddIssueViewModel.swift +// issueTrackerApp +// +// Created by zombietux on 2021/06/14. +// + +import Foundation + +class AddIssueViewModel { + //수정해야함!!!! + var issueTitle = String() + var comment = String() + + var moreInfos: [MoreInfo] { + return DummyDataLoader.loadDataFromJSONFile(withName: "MoreInfos") ?? [] + } + + func updateIssueTitle(_ issueTitle: String) { + self.issueTitle = issueTitle + } + + func updateComment(_ comment: String) { + self.comment = comment + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/Presentation/ViewModel/IssueViewModel.swift b/iOS/issueTrackerApp/issueTrackerApp/Presentation/ViewModel/IssueViewModel.swift new file mode 100644 index 000000000..91dd91f37 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Presentation/ViewModel/IssueViewModel.swift @@ -0,0 +1,67 @@ +// +// IssueViewModel.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/14. +// + +import Foundation + +protocol IssueViewModelProtocol { + var issues: [Issue] { get } + var filteredIssues: [Issue] { get set } + func fetchAllIssue() + func filterIssuesWithSearchText(_ string: String) + func deleteIssue(at index: Int, completionHandler: @escaping () -> Void) +} + +class IssueViewModel: IssueViewModelProtocol { + var networkController: IssueNetworkController? + var issues: [Issue] = [] { + didSet { + NotificationCenter.default.post(name: .didReceiveIssueData, object: nil) + } + } + + var filteredIssues: [Issue] = [] { + didSet { + NotificationCenter.default.post(name: .didFilterIssueData, object: nil) + } + } + + init(issueNetworkController: IssueNetworkController = IssueNetworkController()) { + self.networkController = issueNetworkController + } + + func fetchAllIssue() { +// networkController?.fetchIssues(completion: { (issues) in +// self.issues = issues +// }) + let label1 = Label(id: 1, title: "123", colorCode: "#912939") + let label2 = Label(id: 1, title: "121233", colorCode: "#153239") + + + let issue1 = Issue(id: 123, title: "sdf", description: "sdfsdfsdf", authorAvatarURL: "sdf", labelList: [label1, label2], issueNumber: 1, createdAt: "sdf", milestoneTitle: "milsteon1") + + let issue2 = Issue(id: 13, title: "ssdfsdfdf", description: "sdfsdfsdf", authorAvatarURL: "sdf", labelList: [label1, label2], issueNumber: 1, createdAt: "sdf", milestoneTitle: "milsteon1") + + self.issues = [issue1, issue2] + } + + func filterIssuesWithSearchText(_ string: String) { + self.filteredIssues = issues.filter({ (issue: Issue) -> Bool in + return issue.title.lowercased().contains(string.lowercased()) + }) + } + + func deleteIssue(at index: Int, completionHandler: @escaping () -> Void) { + let issueId = issues[index].id + networkController?.deleteIssue(with: issueId, completion: { (res) in + if res.status == "success" { + self.issues.remove(at: index) + completionHandler() + } + }) + } + +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/SceneDelegate.swift b/iOS/issueTrackerApp/issueTrackerApp/SceneDelegate.swift new file mode 100644 index 000000000..61efacde2 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/SceneDelegate.swift @@ -0,0 +1,24 @@ +// +// SceneDelegate.swift +// issueTrackerApp +// +// Created by zombietux on 2021/06/07. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + var coordinator: MainFlowCoordinator? + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + + if let initialViewController = window?.rootViewController as? MainTabBarController { + coordinator = MainFlowCoordinator(mainViewController: initialViewController) + } + + guard let _ = (scene as? UIWindowScene) else { return } + } +} + diff --git a/iOS/issueTrackerApp/issueTrackerApp/Setting.storyboard b/iOS/issueTrackerApp/issueTrackerApp/Setting.storyboard new file mode 100644 index 000000000..fe50e1bbc --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/Setting.storyboard @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/issueTrackerApp/issueTrackerApp/UIColorExtension.swift b/iOS/issueTrackerApp/issueTrackerApp/UIColorExtension.swift new file mode 100644 index 000000000..b5762e7c3 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/UIColorExtension.swift @@ -0,0 +1,50 @@ +// +// UIColorExtension.swift +// issueTrackerApp +// +// Created by 조중윤 on 2021/06/09. +// + +import UIKit + +extension UIColor { + + static func splitHexColorCode(hexColorString: String) -> [String]? { + let array = Array(hexColorString) + var result = [String]() + + if array.count == 7 { + result.append(String(array[0])) + result.append("\(array[1])\(array[2])") + result.append("\(array[3])\(array[4])") + result.append("\(array[5])\(array[6])") + return result + + } else if array.count == 4 { + result.append(String(array[0])) + result.append("\(array[1])\(array[1])") + result.append("\(array[2])\(array[2])") + result.append("\(array[3])\(array[3])") + return result + } else { + return nil + } + } + + static func hexString2CGFloat(hexString: String) -> CGFloat { + let scanner = Scanner(string: hexString) + var intValue: UInt64 = 0 + scanner.scanHexInt64(&intValue) + return CGFloat(CGFloat(intValue) / 255.0) + } + + static func hexString2UIColor(hexString: String) -> UIColor? { + if let hexStrings = splitHexColorCode(hexColorString: hexString) { + let r = hexString2CGFloat(hexString: hexStrings[1]) + let g = hexString2CGFloat(hexString: hexStrings[2]) + let b = hexString2CGFloat(hexString: hexStrings[3]) + return UIColor(red: r, green: g, blue: b, alpha: 1.0) + } + return nil + } +} diff --git a/iOS/issueTrackerApp/issueTrackerApp/issueTrackerApp.entitlements b/iOS/issueTrackerApp/issueTrackerApp/issueTrackerApp.entitlements new file mode 100644 index 000000000..a812db506 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/issueTrackerApp.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.applesignin + + Default + + + diff --git a/iOS/issueTrackerApp/issueTrackerApp/issueTrackerApp/Base.lproj/Issue.storyboard b/iOS/issueTrackerApp/issueTrackerApp/issueTrackerApp/Base.lproj/Issue.storyboard new file mode 100644 index 000000000..ec0e48500 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/issueTrackerApp/Base.lproj/Issue.storyboard @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/issueTrackerApp/issueTrackerApp/issueTrackerApp/Base.lproj/Milestones.storyboard b/iOS/issueTrackerApp/issueTrackerApp/issueTrackerApp/Base.lproj/Milestones.storyboard new file mode 100644 index 000000000..ce0143b70 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerApp/issueTrackerApp/Base.lproj/Milestones.storyboard @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/issueTrackerApp/issueTrackerAppTests/Info.plist b/iOS/issueTrackerApp/issueTrackerAppTests/Info.plist new file mode 100644 index 000000000..64d65ca49 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerAppTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/iOS/issueTrackerApp/issueTrackerAppTests/issueTrackerAppTests.swift b/iOS/issueTrackerApp/issueTrackerAppTests/issueTrackerAppTests.swift new file mode 100644 index 000000000..12009eaca --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerAppTests/issueTrackerAppTests.swift @@ -0,0 +1,33 @@ +// +// issueTrackerAppTests.swift +// issueTrackerAppTests +// +// Created by zombietux on 2021/06/07. +// + +import XCTest +@testable import issueTrackerApp + +class issueTrackerAppTests: 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/iOS/issueTrackerApp/issueTrackerAppUITests/Info.plist b/iOS/issueTrackerApp/issueTrackerAppUITests/Info.plist new file mode 100644 index 000000000..64d65ca49 --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerAppUITests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/iOS/issueTrackerApp/issueTrackerAppUITests/issueTrackerAppUITests.swift b/iOS/issueTrackerApp/issueTrackerAppUITests/issueTrackerAppUITests.swift new file mode 100644 index 000000000..61a742afd --- /dev/null +++ b/iOS/issueTrackerApp/issueTrackerAppUITests/issueTrackerAppUITests.swift @@ -0,0 +1,42 @@ +// +// issueTrackerAppUITests.swift +// issueTrackerAppUITests +// +// Created by zombietux on 2021/06/07. +// + +import XCTest + +class issueTrackerAppUITests: 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() + } + } + } +}