diff --git a/pochak/pochak.xcodeproj/project.pbxproj b/pochak/pochak.xcodeproj/project.pbxproj index e6e6aac2..c19f4914 100644 --- a/pochak/pochak.xcodeproj/project.pbxproj +++ b/pochak/pochak.xcodeproj/project.pbxproj @@ -102,36 +102,51 @@ 2FCBFE6DE98CEA8938EF93DC /* (null) in Frameworks */ = {isa = PBXBuildFile; }; 371D33782B4E83E90084FF6A /* UserDefaultsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371D33772B4E83E90084FF6A /* UserDefaultsManager.swift */; }; 371D337A2B4E8D1F0084FF6A /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371D33792B4E8D1F0084FF6A /* KeychainManager.swift */; }; - 373235472B68F93E0042EF94 /* FollowListDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373235462B68F93E0042EF94 /* FollowListDataManager.swift */; }; - 373235492B68F9A60042EF94 /* FollowListDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373235482B68F9A60042EF94 /* FollowListDataModel.swift */; }; - 3732354E2B691E610042EF94 /* FollowToggleDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3732354D2B691E610042EF94 /* FollowToggleDataManager.swift */; }; - 373235502B691E6A0042EF94 /* FollowToggleDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3732354F2B691E6A0042EF94 /* FollowToggleDataModel.swift */; }; - 373235542B692C6D0042EF94 /* DeleteFollowerDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373235532B692C6D0042EF94 /* DeleteFollowerDataManager.swift */; }; - 373235562B692C790042EF94 /* DeleteFollowerDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373235552B692C790042EF94 /* DeleteFollowerDataModel.swift */; }; 373815162A89E6D30032066A /* SocialJoinViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373815152A89E6D30032066A /* SocialJoinViewController.swift */; }; - 3738151A2A8A09DA0032066A /* MakeProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373815192A8A09DA0032066A /* MakeProfileViewController.swift */; }; + 3738151A2A8A09DA0032066A /* SignUpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373815192A8A09DA0032066A /* SignUpViewController.swift */; }; 374261642C2FEF5F00B67886 /* ProfileMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374261632C2FEF5F00B67886 /* ProfileMenuViewController.swift */; }; - 3742616E2C30916C00B67886 /* BlockDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3742616D2C30916C00B67886 /* BlockDataManager.swift */; }; - 374261702C30917600B67886 /* BlockDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3742616F2C30917600B67886 /* BlockDataModel.swift */; }; - 374261722C3092B100B67886 /* UnblockDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374261712C3092B100B67886 /* UnblockDataManager.swift */; }; - 374261742C3092B900B67886 /* UnblockDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374261732C3092B900B67886 /* UnblockDataModel.swift */; }; 374261782C31C6ED00B67886 /* AppleLoginDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374261772C31C6ED00B67886 /* AppleLoginDataManager.swift */; }; 3742617A2C31C6F900B67886 /* AppleLoginDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374261792C31C6F900B67886 /* AppleLoginDataModel.swift */; }; - 3742617D2C359D6100B67886 /* CheckHandleDuplicationDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3742617C2C359D6100B67886 /* CheckHandleDuplicationDataManager.swift */; }; - 3742617F2C359D6D00B67886 /* CheckHandleDuplicationDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3742617E2C359D6D00B67886 /* CheckHandleDuplicationDataModel.swift */; }; 3747E9722BF3827500364BED /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3747E9712BF3827500364BED /* SettingsViewController.swift */; }; + 374E461F2CAABAF3008AE361 /* PochakPostRetrievalAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374E461E2CAABAF3008AE361 /* PochakPostRetrievalAPI.swift */; }; + 374E46232CAABB31008AE361 /* PochakPostRetrievalResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374E46222CAABB31008AE361 /* PochakPostRetrievalResponse.swift */; }; + 374E46252CAABE08008AE361 /* ProfileUpdateResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374E46242CAABE07008AE361 /* ProfileUpdateResponse.swift */; }; + 374E46292CAABE50008AE361 /* ProfileUpdateAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374E46282CAABE50008AE361 /* ProfileUpdateAPI.swift */; }; + 374E462B2CAABF7B008AE361 /* ProfileUpdateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374E462A2CAABF7A008AE361 /* ProfileUpdateRequest.swift */; }; + 374E462D2CAAC399008AE361 /* AuthenticationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374E462C2CAAC399008AE361 /* AuthenticationService.swift */; }; + 374E46302CAAC3F6008AE361 /* SignOutResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374E462F2CAAC3F6008AE361 /* SignOutResponse.swift */; }; + 374E46352CAAC445008AE361 /* SignOutAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374E46342CAAC445008AE361 /* SignOutAPI.swift */; }; + 374E46372CAAC602008AE361 /* LogOutAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374E46362CAAC602008AE361 /* LogOutAPI.swift */; }; + 374E46392CAAC646008AE361 /* LogOutResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374E46382CAAC646008AE361 /* LogOutResponse.swift */; }; + 374E463B2CAAC762008AE361 /* BlockAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374E463A2CAAC762008AE361 /* BlockAPI.swift */; }; + 374E463D2CAAC82E008AE361 /* BlockResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374E463C2CAAC82E008AE361 /* BlockResponse.swift */; }; + 374E463F2CAAC931008AE361 /* UnblockAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374E463E2CAAC931008AE361 /* UnblockAPI.swift */; }; + 374E46412CAAC9AE008AE361 /* UnblockRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374E46402CAAC9AE008AE361 /* UnblockRequest.swift */; }; + 374E46432CAAC9B5008AE361 /* UnblockResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374E46422CAAC9B5008AE361 /* UnblockResponse.swift */; }; + 374E46452CAACD33008AE361 /* BlockListAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374E46442CAACD33008AE361 /* BlockListAPI.swift */; }; + 374E46472CAACE4C008AE361 /* BlockListRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374E46462CAACE4C008AE361 /* BlockListRequest.swift */; }; + 374E46492CAACE53008AE361 /* BlockListResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374E46482CAACE53008AE361 /* BlockListResponse.swift */; }; + 3759CF2F2CAAE42600408A4E /* DeleteFollowerAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3759CF2E2CAAE42600408A4E /* DeleteFollowerAPI.swift */; }; + 3759CF312CAAE4C600408A4E /* DeleteFollowerRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3759CF302CAAE4C600408A4E /* DeleteFollowerRequest.swift */; }; + 3759CF332CAAE4CE00408A4E /* DeleteFollowerResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3759CF322CAAE4CE00408A4E /* DeleteFollowerResponse.swift */; }; + 3759CF352CAAE79000408A4E /* FollowerRetrievalAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3759CF342CAAE79000408A4E /* FollowerRetrievalAPI.swift */; }; + 3759CF372CAAE79F00408A4E /* FollowingRetrievalAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3759CF362CAAE79F00408A4E /* FollowingRetrievalAPI.swift */; }; + 3759CF392CAAE7E900408A4E /* FollowListRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3759CF382CAAE7E900408A4E /* FollowListRequest.swift */; }; + 3759CF3B2CAAE80700408A4E /* FollowListResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3759CF3A2CAAE80700408A4E /* FollowListResponse.swift */; }; + 3759CF3F2CAB145500408A4E /* CheckDuplicateHandleAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3759CF3E2CAB145500408A4E /* CheckDuplicateHandleAPI.swift */; }; + 3759CF412CAB150500408A4E /* CheckDuplicateHandleRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3759CF402CAB150500408A4E /* CheckDuplicateHandleRequest.swift */; }; + 3759CF432CAB152200408A4E /* CheckDuplicateHandleResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3759CF422CAB152200408A4E /* CheckDuplicateHandleResponse.swift */; }; + 3759CF452CAB16E600408A4E /* SignUpAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3759CF442CAB16E600408A4E /* SignUpAPI.swift */; }; + 3759CF492CAB178800408A4E /* SignUpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3759CF482CAB178800408A4E /* SignUpRequest.swift */; }; + 3759CF4B2CAB179100408A4E /* SignUpResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3759CF4A2CAB179100408A4E /* SignUpResponse.swift */; }; 3766F4022C3706DC001DD239 /* BlockedUserTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3766F4012C3706DC001DD239 /* BlockedUserTableViewCell.xib */; }; 3766F4032C3706DC001DD239 /* BlockedUserTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3766F4002C3706DC001DD239 /* BlockedUserTableViewCell.swift */; }; - 3766F4062C370A7C001DD239 /* BlockedUserListDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3766F4052C370A7C001DD239 /* BlockedUserListDataManager.swift */; }; - 3766F4082C370A82001DD239 /* BlockedUserListDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3766F4072C370A82001DD239 /* BlockedUserListDataModel.swift */; }; 3766F40A2C370CF5001DD239 /* BlockedUserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3766F4092C370CF5001DD239 /* BlockedUserViewController.swift */; }; 3766F40C2C3736F1001DD239 /* RequestInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3766F40B2C3736F1001DD239 /* RequestInterceptor.swift */; }; 3766F40F2C398AA8001DD239 /* NetworkResult400.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3766F40E2C398AA8001DD239 /* NetworkResult400.swift */; }; 3766F4132C3A69E5001DD239 /* TermsOfAgreeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3766F4122C3A69E5001DD239 /* TermsOfAgreeViewController.swift */; }; 3769D6AF2B3C2FD500294CD1 /* GoogleLoginDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3769D6AE2B3C2FD500294CD1 /* GoogleLoginDataManager.swift */; }; 3769D6B22B3C300D00294CD1 /* GoogleLoginModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3769D6B12B3C300D00294CD1 /* GoogleLoginModel.swift */; }; - 3769D6B42B3C301B00294CD1 /* JoinDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3769D6B32B3C301B00294CD1 /* JoinDataManager.swift */; }; - 3769D6B62B3C302C00294CD1 /* JoinDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3769D6B52B3C302C00294CD1 /* JoinDataModel.swift */; }; 3769D6B92B3C345800294CD1 /* MyProfileTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3769D6B82B3C345800294CD1 /* MyProfileTabViewController.swift */; }; 3769D6BB2B3C347000294CD1 /* FollowListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3769D6BA2B3C347000294CD1 /* FollowListViewController.swift */; }; 3769D6BD2B3C348800294CD1 /* FollowerListTabmanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3769D6BC2B3C348800294CD1 /* FollowerListTabmanViewController.swift */; }; @@ -145,23 +160,20 @@ 3769D6D22B3C56F100294CD1 /* PochakPostTabmanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3769D6D12B3C56F100294CD1 /* PochakPostTabmanViewController.swift */; }; 3769D6D52B3C59F900294CD1 /* ProfilePostCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3769D6D32B3C59F900294CD1 /* ProfilePostCollectionViewCell.swift */; }; 3769D6D62B3C59F900294CD1 /* ProfilePostCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3769D6D42B3C59F900294CD1 /* ProfilePostCollectionViewCell.xib */; }; - 376F125B2A8B7440005F469B /* SocialLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376F125A2A8B7440005F469B /* SocialLoginViewController.swift */; }; + 376EA1292CAAADCF00FEFB63 /* ProfileRetrievalAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376EA1282CAAADCF00FEFB63 /* ProfileRetrievalAPI.swift */; }; + 376EA12C2CAAAE5600FEFB63 /* ProfileRetrievalResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376EA12B2CAAAE5600FEFB63 /* ProfileRetrievalResponse.swift */; }; + 376EA12E2CAAAE6800FEFB63 /* ProfileRetrievalRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376EA12D2CAAAE6800FEFB63 /* ProfileRetrievalRequest.swift */; }; + 376EA1302CAAAF6900FEFB63 /* ProfileService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376EA12F2CAAAF6900FEFB63 /* ProfileService.swift */; }; 379A6A342B569DFA0087FAAC /* AuthenticationCredential.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379A6A332B569DFA0087FAAC /* AuthenticationCredential.swift */; }; 379A6A3A2B56BDBF0087FAAC /* Authenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379A6A392B56BDBF0087FAAC /* Authenticator.swift */; }; 379A6A3E2B5FA07D0087FAAC /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379A6A3D2B5FA07D0087FAAC /* NavigationController.swift */; }; 37A284C12B52FC910023EB9D /* UpdateProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A284C02B52FC910023EB9D /* UpdateProfileViewController.swift */; }; 37BBFDF12C2EB43D003D8DA5 /* Pretendard-ExtraBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 089C75852A51728A003AAB94 /* Pretendard-ExtraBold.ttf */; }; - 37C3AF482B3CB2B300B4C79C /* MyProfilePochakedPostModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C3AF472B3CB2B300B4C79C /* MyProfilePochakedPostModel.swift */; }; - 37C3AF4A2B3CB2C700B4C79C /* MyProfilePochakPostModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C3AF492B3CB2C700B4C79C /* MyProfilePochakPostModel.swift */; }; - 37C3AF4B2B3CB49E00B4C79C /* MyProfilePostDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C3AF452B3CB29A00B4C79C /* MyProfilePostDataManager.swift */; }; - 37D311D52B53056700A1D7C1 /* ProfileUpdateDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D311D42B53056700A1D7C1 /* ProfileUpdateDataManager.swift */; }; - 37D311D72B53057700A1D7C1 /* ProfileUpdateDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D311D62B53057700A1D7C1 /* ProfileUpdateDataModel.swift */; }; - 37D311DA2B53134200A1D7C1 /* LogoutDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D311D92B53134200A1D7C1 /* LogoutDataManager.swift */; }; - 37D311DC2B53135000A1D7C1 /* LogoutDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D311DB2B53135000A1D7C1 /* LogoutDataModel.swift */; }; - 37D311DF2B566C4D00A1D7C1 /* SignOutDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D311DE2B566C4D00A1D7C1 /* SignOutDataManager.swift */; }; - 37D311E12B566C5A00A1D7C1 /* SignOutDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D311E02B566C5900A1D7C1 /* SignOutDataModel.swift */; }; 37D311E32B56754D00A1D7C1 /* GetToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D311E22B56754D00A1D7C1 /* GetToken.swift */; }; 37D311E52B567AE000A1D7C1 /* OtherUserProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D311E42B567AE000A1D7C1 /* OtherUserProfileViewController.swift */; }; + 37EED79D2CFDDC3800254471 /* ProfileDataSingleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EED79C2CFDDC1F00254471 /* ProfileDataSingleton.swift */; }; + 37EED79F2CFDFE9100254471 /* Notification+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EED79E2CFDFE9100254471 /* Notification+.swift */; }; + 37EED7A12D07094F00254471 /* HandleDuplicationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EED7A02D07094F00254471 /* HandleDuplicationType.swift */; }; 46BA13362CBD551600C81729 /* SecretKeyInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 46BA13352CBD551600C81729 /* SecretKeyInfo.plist */; }; 46B0B41C2CFDFC6900950554 /* Tabman in Frameworks */ = {isa = PBXBuildFile; productRef = 46B0B41B2CFDFC6900950554 /* Tabman */; }; 46B0B41F2CFDFC7700950554 /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = 46B0B41E2CFDFC7700950554 /* GoogleSignIn */; }; @@ -335,7 +347,7 @@ 08C02EF52BF3945000627C21 /* UIFont+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+.swift"; sourceTree = ""; }; 08C02EF72BF394B800627C21 /* UIAlertController+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+.swift"; sourceTree = ""; }; 08C9151B2C6461B10044A9A8 /* PostService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostService.swift; sourceTree = ""; }; - 08D2B98C2C68C02D00BD7450 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "../../../Downloads/GoogleService-Info.plist"; sourceTree = ""; }; + 08D2B98C2C68C02D00BD7450 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "../../../../../Downloads/GoogleService-Info.plist"; sourceTree = ""; }; 08D98C492AFEE8D700E9A097 /* LikedUsersDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LikedUsersDataModel.swift; sourceTree = ""; }; 08E8DCDB2C38118000EC78C2 /* UILabel+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+.swift"; sourceTree = ""; }; 08EB5D242C280CFE00E35C16 /* UIImage+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+.swift"; sourceTree = ""; }; @@ -345,38 +357,53 @@ 08FD3A382B357E6400574880 /* LikedUsersDataService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LikedUsersDataService.swift; sourceTree = ""; }; 371D33772B4E83E90084FF6A /* UserDefaultsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsManager.swift; sourceTree = ""; }; 371D33792B4E8D1F0084FF6A /* KeychainManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManager.swift; sourceTree = ""; }; - 373235462B68F93E0042EF94 /* FollowListDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowListDataManager.swift; sourceTree = ""; }; - 373235482B68F9A60042EF94 /* FollowListDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowListDataModel.swift; sourceTree = ""; }; - 3732354D2B691E610042EF94 /* FollowToggleDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowToggleDataManager.swift; sourceTree = ""; }; - 3732354F2B691E6A0042EF94 /* FollowToggleDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowToggleDataModel.swift; sourceTree = ""; }; - 373235532B692C6D0042EF94 /* DeleteFollowerDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteFollowerDataManager.swift; sourceTree = ""; }; - 373235552B692C790042EF94 /* DeleteFollowerDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteFollowerDataModel.swift; sourceTree = ""; }; 373815152A89E6D30032066A /* SocialJoinViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialJoinViewController.swift; sourceTree = ""; }; - 373815192A8A09DA0032066A /* MakeProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MakeProfileViewController.swift; sourceTree = ""; }; + 373815192A8A09DA0032066A /* SignUpViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpViewController.swift; sourceTree = ""; }; 374261632C2FEF5F00B67886 /* ProfileMenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileMenuViewController.swift; sourceTree = ""; }; - 3742616D2C30916C00B67886 /* BlockDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockDataManager.swift; sourceTree = ""; }; - 3742616F2C30917600B67886 /* BlockDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockDataModel.swift; sourceTree = ""; }; - 374261712C3092B100B67886 /* UnblockDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnblockDataManager.swift; sourceTree = ""; }; - 374261732C3092B900B67886 /* UnblockDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnblockDataModel.swift; sourceTree = ""; }; 374261752C31C23800B67886 /* pochakDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = pochakDebug.entitlements; sourceTree = ""; }; 374261772C31C6ED00B67886 /* AppleLoginDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleLoginDataManager.swift; sourceTree = ""; }; 374261792C31C6F900B67886 /* AppleLoginDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleLoginDataModel.swift; sourceTree = ""; }; - 3742617C2C359D6100B67886 /* CheckHandleDuplicationDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckHandleDuplicationDataManager.swift; sourceTree = ""; }; - 3742617E2C359D6D00B67886 /* CheckHandleDuplicationDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckHandleDuplicationDataModel.swift; sourceTree = ""; }; 3747E9712BF3827500364BED /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; + 374E461E2CAABAF3008AE361 /* PochakPostRetrievalAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PochakPostRetrievalAPI.swift; sourceTree = ""; }; + 374E46222CAABB31008AE361 /* PochakPostRetrievalResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PochakPostRetrievalResponse.swift; sourceTree = ""; }; + 374E46242CAABE07008AE361 /* ProfileUpdateResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileUpdateResponse.swift; sourceTree = ""; }; + 374E46282CAABE50008AE361 /* ProfileUpdateAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileUpdateAPI.swift; sourceTree = ""; }; + 374E462A2CAABF7A008AE361 /* ProfileUpdateRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileUpdateRequest.swift; sourceTree = ""; }; + 374E462C2CAAC399008AE361 /* AuthenticationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationService.swift; sourceTree = ""; }; + 374E462F2CAAC3F6008AE361 /* SignOutResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignOutResponse.swift; sourceTree = ""; }; + 374E46342CAAC445008AE361 /* SignOutAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignOutAPI.swift; sourceTree = ""; }; + 374E46362CAAC602008AE361 /* LogOutAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogOutAPI.swift; sourceTree = ""; }; + 374E46382CAAC646008AE361 /* LogOutResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogOutResponse.swift; sourceTree = ""; }; + 374E463A2CAAC762008AE361 /* BlockAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockAPI.swift; sourceTree = ""; }; + 374E463C2CAAC82E008AE361 /* BlockResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockResponse.swift; sourceTree = ""; }; + 374E463E2CAAC931008AE361 /* UnblockAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnblockAPI.swift; sourceTree = ""; }; + 374E46402CAAC9AE008AE361 /* UnblockRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnblockRequest.swift; sourceTree = ""; }; + 374E46422CAAC9B5008AE361 /* UnblockResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnblockResponse.swift; sourceTree = ""; }; + 374E46442CAACD33008AE361 /* BlockListAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockListAPI.swift; sourceTree = ""; }; + 374E46462CAACE4C008AE361 /* BlockListRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockListRequest.swift; sourceTree = ""; }; + 374E46482CAACE53008AE361 /* BlockListResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockListResponse.swift; sourceTree = ""; }; 3750669E2C3BB1660036E264 /* pochak.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = pochak.entitlements; sourceTree = ""; }; + 3759CF2E2CAAE42600408A4E /* DeleteFollowerAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteFollowerAPI.swift; sourceTree = ""; }; + 3759CF302CAAE4C600408A4E /* DeleteFollowerRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteFollowerRequest.swift; sourceTree = ""; }; + 3759CF322CAAE4CE00408A4E /* DeleteFollowerResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteFollowerResponse.swift; sourceTree = ""; }; + 3759CF342CAAE79000408A4E /* FollowerRetrievalAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowerRetrievalAPI.swift; sourceTree = ""; }; + 3759CF362CAAE79F00408A4E /* FollowingRetrievalAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowingRetrievalAPI.swift; sourceTree = ""; }; + 3759CF382CAAE7E900408A4E /* FollowListRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowListRequest.swift; sourceTree = ""; }; + 3759CF3A2CAAE80700408A4E /* FollowListResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowListResponse.swift; sourceTree = ""; }; + 3759CF3E2CAB145500408A4E /* CheckDuplicateHandleAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckDuplicateHandleAPI.swift; sourceTree = ""; }; + 3759CF402CAB150500408A4E /* CheckDuplicateHandleRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckDuplicateHandleRequest.swift; sourceTree = ""; }; + 3759CF422CAB152200408A4E /* CheckDuplicateHandleResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckDuplicateHandleResponse.swift; sourceTree = ""; }; + 3759CF442CAB16E600408A4E /* SignUpAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpAPI.swift; sourceTree = ""; }; + 3759CF482CAB178800408A4E /* SignUpRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpRequest.swift; sourceTree = ""; }; + 3759CF4A2CAB179100408A4E /* SignUpResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpResponse.swift; sourceTree = ""; }; 3766F4002C3706DC001DD239 /* BlockedUserTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUserTableViewCell.swift; sourceTree = ""; }; 3766F4012C3706DC001DD239 /* BlockedUserTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BlockedUserTableViewCell.xib; sourceTree = ""; }; - 3766F4052C370A7C001DD239 /* BlockedUserListDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUserListDataManager.swift; sourceTree = ""; }; - 3766F4072C370A82001DD239 /* BlockedUserListDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUserListDataModel.swift; sourceTree = ""; }; 3766F4092C370CF5001DD239 /* BlockedUserViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUserViewController.swift; sourceTree = ""; }; 3766F40B2C3736F1001DD239 /* RequestInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestInterceptor.swift; sourceTree = ""; }; 3766F40E2C398AA8001DD239 /* NetworkResult400.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkResult400.swift; sourceTree = ""; }; 3766F4122C3A69E5001DD239 /* TermsOfAgreeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsOfAgreeViewController.swift; sourceTree = ""; }; 3769D6AE2B3C2FD500294CD1 /* GoogleLoginDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleLoginDataManager.swift; sourceTree = ""; }; 3769D6B12B3C300D00294CD1 /* GoogleLoginModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleLoginModel.swift; sourceTree = ""; }; - 3769D6B32B3C301B00294CD1 /* JoinDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinDataManager.swift; sourceTree = ""; }; - 3769D6B52B3C302C00294CD1 /* JoinDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinDataModel.swift; sourceTree = ""; }; 3769D6B82B3C345800294CD1 /* MyProfileTabViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyProfileTabViewController.swift; sourceTree = ""; }; 3769D6BA2B3C347000294CD1 /* FollowListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowListViewController.swift; sourceTree = ""; }; 3769D6BC2B3C348800294CD1 /* FollowerListTabmanViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowerListTabmanViewController.swift; sourceTree = ""; }; @@ -390,22 +417,19 @@ 3769D6D12B3C56F100294CD1 /* PochakPostTabmanViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PochakPostTabmanViewController.swift; sourceTree = ""; }; 3769D6D32B3C59F900294CD1 /* ProfilePostCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePostCollectionViewCell.swift; sourceTree = ""; }; 3769D6D42B3C59F900294CD1 /* ProfilePostCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ProfilePostCollectionViewCell.xib; sourceTree = ""; }; - 376F125A2A8B7440005F469B /* SocialLoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialLoginViewController.swift; sourceTree = ""; }; + 376EA1282CAAADCF00FEFB63 /* ProfileRetrievalAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileRetrievalAPI.swift; sourceTree = ""; }; + 376EA12B2CAAAE5600FEFB63 /* ProfileRetrievalResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileRetrievalResponse.swift; sourceTree = ""; }; + 376EA12D2CAAAE6800FEFB63 /* ProfileRetrievalRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileRetrievalRequest.swift; sourceTree = ""; }; + 376EA12F2CAAAF6900FEFB63 /* ProfileService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileService.swift; sourceTree = ""; }; 379A6A332B569DFA0087FAAC /* AuthenticationCredential.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationCredential.swift; sourceTree = ""; }; 379A6A392B56BDBF0087FAAC /* Authenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Authenticator.swift; sourceTree = ""; }; 379A6A3D2B5FA07D0087FAAC /* NavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationController.swift; sourceTree = ""; }; 37A284C02B52FC910023EB9D /* UpdateProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateProfileViewController.swift; sourceTree = ""; }; - 37C3AF452B3CB29A00B4C79C /* MyProfilePostDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyProfilePostDataManager.swift; sourceTree = ""; }; - 37C3AF472B3CB2B300B4C79C /* MyProfilePochakedPostModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyProfilePochakedPostModel.swift; sourceTree = ""; }; - 37C3AF492B3CB2C700B4C79C /* MyProfilePochakPostModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyProfilePochakPostModel.swift; sourceTree = ""; }; - 37D311D42B53056700A1D7C1 /* ProfileUpdateDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileUpdateDataManager.swift; sourceTree = ""; }; - 37D311D62B53057700A1D7C1 /* ProfileUpdateDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileUpdateDataModel.swift; sourceTree = ""; }; - 37D311D92B53134200A1D7C1 /* LogoutDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutDataManager.swift; sourceTree = ""; }; - 37D311DB2B53135000A1D7C1 /* LogoutDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutDataModel.swift; sourceTree = ""; }; - 37D311DE2B566C4D00A1D7C1 /* SignOutDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignOutDataManager.swift; sourceTree = ""; }; - 37D311E02B566C5900A1D7C1 /* SignOutDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignOutDataModel.swift; sourceTree = ""; }; 37D311E22B56754D00A1D7C1 /* GetToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetToken.swift; sourceTree = ""; }; 37D311E42B567AE000A1D7C1 /* OtherUserProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherUserProfileViewController.swift; sourceTree = ""; }; + 37EED79C2CFDDC1F00254471 /* ProfileDataSingleton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDataSingleton.swift; sourceTree = ""; }; + 37EED79E2CFDFE9100254471 /* Notification+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+.swift"; sourceTree = ""; }; + 37EED7A02D07094F00254471 /* HandleDuplicationType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandleDuplicationType.swift; sourceTree = ""; }; 46BA13352CBD551600C81729 /* SecretKeyInfo.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = SecretKeyInfo.plist; sourceTree = ""; }; EA0107952A59556800A3AE85 /* CommentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommentViewController.swift; sourceTree = ""; }; EA0107962A59556800A3AE85 /* HomeTabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeTabViewController.swift; sourceTree = ""; }; @@ -585,6 +609,12 @@ isa = PBXGroup; children = ( 0822A04E2CA1980200F87E2C /* FollowAPI.swift */, + 3759CF2E2CAAE42600408A4E /* DeleteFollowerAPI.swift */, + 3759CF342CAAE79000408A4E /* FollowerRetrievalAPI.swift */, + 3759CF362CAAE79F00408A4E /* FollowingRetrievalAPI.swift */, + 374E463A2CAAC762008AE361 /* BlockAPI.swift */, + 374E463E2CAAC931008AE361 /* UnblockAPI.swift */, + 374E46442CAACD33008AE361 /* BlockListAPI.swift */, ); path = User; sourceTree = ""; @@ -611,6 +641,15 @@ isa = PBXGroup; children = ( 0822A0592CA1983C00F87E2C /* FollowResponse.swift */, + 3759CF302CAAE4C600408A4E /* DeleteFollowerRequest.swift */, + 3759CF322CAAE4CE00408A4E /* DeleteFollowerResponse.swift */, + 3759CF382CAAE7E900408A4E /* FollowListRequest.swift */, + 3759CF3A2CAAE80700408A4E /* FollowListResponse.swift */, + 374E463C2CAAC82E008AE361 /* BlockResponse.swift */, + 374E46402CAAC9AE008AE361 /* UnblockRequest.swift */, + 374E46422CAAC9B5008AE361 /* UnblockResponse.swift */, + 374E46462CAACE4C008AE361 /* BlockListRequest.swift */, + 374E46482CAACE53008AE361 /* BlockListResponse.swift */, ); path = User; sourceTree = ""; @@ -760,6 +799,7 @@ 089E27B62C4E8C2D00828F4C /* AlarmType.swift */, 089E27B82C4E8CB800828F4C /* AlertType.swift */, 080FEF002C551C3200951460 /* ReportType.swift */, + 37EED7A02D07094F00254471 /* HandleDuplicationType.swift */, ); path = Types; sourceTree = ""; @@ -833,6 +873,7 @@ 08E8DCDB2C38118000EC78C2 /* UILabel+.swift */, 08EB99B32C53D76C00FAC08A /* UIScrollView+.swift */, 0889D1372C5CB69F00206C86 /* String+.swift */, + 37EED79E2CFDFE9100254471 /* Notification+.swift */, ); path = Extensions; sourceTree = ""; @@ -846,80 +887,50 @@ path = LikesDataManager; sourceTree = ""; }; - 373235452B68F9180042EF94 /* FollowAPI */ = { - isa = PBXGroup; - children = ( - 373235462B68F93E0042EF94 /* FollowListDataManager.swift */, - 373235482B68F9A60042EF94 /* FollowListDataModel.swift */, - 3732354D2B691E610042EF94 /* FollowToggleDataManager.swift */, - 3732354F2B691E6A0042EF94 /* FollowToggleDataModel.swift */, - 373235532B692C6D0042EF94 /* DeleteFollowerDataManager.swift */, - 373235552B692C790042EF94 /* DeleteFollowerDataModel.swift */, - ); - path = FollowAPI; - sourceTree = ""; - }; 373815142A89E6BE0032066A /* Login */ = { isa = PBXGroup; children = ( EAAACDB42A5168280041B1B9 /* Login.storyboard */, 373815152A89E6D30032066A /* SocialJoinViewController.swift */, 3766F4122C3A69E5001DD239 /* TermsOfAgreeViewController.swift */, - 376F125A2A8B7440005F469B /* SocialLoginViewController.swift */, + 373815192A8A09DA0032066A /* SignUpViewController.swift */, 374261762C31C6CD00B67886 /* AppleLogin */, - 3769D6AD2B3C2FA800294CD1 /* GoogleLoginDataManager */, - 3769D6AC2B3C2F9900294CD1 /* SignUp */, + 3769D6AD2B3C2FA800294CD1 /* GoogleLogin */, ); path = Login; sourceTree = ""; }; - 3742616A2C3075B400B67886 /* Setting */ = { - isa = PBXGroup; - children = ( - 3747E9712BF3827500364BED /* SettingsViewController.swift */, - 3766F4042C370A5D001DD239 /* BlockedUserList */, - 37D311D82B53131D00A1D7C1 /* LogoutAPI */, - 37D311DD2B566C3D00A1D7C1 /* SignoutAPI */, - ); - path = Setting; - sourceTree = ""; - }; - 3742616B2C30911A00B67886 /* ProfileMenu */ = { - isa = PBXGroup; - children = ( - 374261632C2FEF5F00B67886 /* ProfileMenuViewController.swift */, - 3742616C2C30914100B67886 /* BlockAPI */, - ); - path = ProfileMenu; - sourceTree = ""; - }; - 3742616C2C30914100B67886 /* BlockAPI */ = { + 374261762C31C6CD00B67886 /* AppleLogin */ = { isa = PBXGroup; children = ( - 3742616D2C30916C00B67886 /* BlockDataManager.swift */, - 3742616F2C30917600B67886 /* BlockDataModel.swift */, - 374261712C3092B100B67886 /* UnblockDataManager.swift */, - 374261732C3092B900B67886 /* UnblockDataModel.swift */, + 374261772C31C6ED00B67886 /* AppleLoginDataManager.swift */, + 374261792C31C6F900B67886 /* AppleLoginDataModel.swift */, ); - path = BlockAPI; + path = AppleLogin; sourceTree = ""; }; - 374261762C31C6CD00B67886 /* AppleLogin */ = { + 374E462E2CAAC3CB008AE361 /* Authentication */ = { isa = PBXGroup; children = ( - 374261772C31C6ED00B67886 /* AppleLoginDataManager.swift */, - 374261792C31C6F900B67886 /* AppleLoginDataModel.swift */, + 3759CF482CAB178800408A4E /* SignUpRequest.swift */, + 3759CF4A2CAB179100408A4E /* SignUpResponse.swift */, + 374E462F2CAAC3F6008AE361 /* SignOutResponse.swift */, + 374E46382CAAC646008AE361 /* LogOutResponse.swift */, + 3759CF402CAB150500408A4E /* CheckDuplicateHandleRequest.swift */, + 3759CF422CAB152200408A4E /* CheckDuplicateHandleResponse.swift */, ); - path = AppleLogin; + path = Authentication; sourceTree = ""; }; - 3742617B2C359D3700B67886 /* CheckHandle */ = { + 374E46332CAAC437008AE361 /* Authentication */ = { isa = PBXGroup; children = ( - 3742617C2C359D6100B67886 /* CheckHandleDuplicationDataManager.swift */, - 3742617E2C359D6D00B67886 /* CheckHandleDuplicationDataModel.swift */, + 3759CF442CAB16E600408A4E /* SignUpAPI.swift */, + 374E46342CAAC445008AE361 /* SignOutAPI.swift */, + 374E46362CAAC602008AE361 /* LogOutAPI.swift */, + 3759CF3E2CAB145500408A4E /* CheckDuplicateHandleAPI.swift */, ); - path = CheckHandle; + path = Authentication; sourceTree = ""; }; 3766F3FF2C3706C7001DD239 /* cell */ = { @@ -931,51 +942,37 @@ path = cell; sourceTree = ""; }; - 3766F4042C370A5D001DD239 /* BlockedUserList */ = { + 3766F4042C370A5D001DD239 /* BlockList */ = { isa = PBXGroup; children = ( - 3766F4092C370CF5001DD239 /* BlockedUserViewController.swift */, - 3766F40D2C373CFF001DD239 /* BlockListAPI */, 3766F3FF2C3706C7001DD239 /* cell */, + 3766F4092C370CF5001DD239 /* BlockedUserViewController.swift */, ); - path = BlockedUserList; - sourceTree = ""; - }; - 3766F40D2C373CFF001DD239 /* BlockListAPI */ = { - isa = PBXGroup; - children = ( - 3766F4072C370A82001DD239 /* BlockedUserListDataModel.swift */, - 3766F4052C370A7C001DD239 /* BlockedUserListDataManager.swift */, - ); - path = BlockListAPI; - sourceTree = ""; - }; - 3769D6AC2B3C2F9900294CD1 /* SignUp */ = { - isa = PBXGroup; - children = ( - 3742617B2C359D3700B67886 /* CheckHandle */, - 373815192A8A09DA0032066A /* MakeProfileViewController.swift */, - 3769D6B32B3C301B00294CD1 /* JoinDataManager.swift */, - 3769D6B52B3C302C00294CD1 /* JoinDataModel.swift */, - ); - path = SignUp; + path = BlockList; sourceTree = ""; }; - 3769D6AD2B3C2FA800294CD1 /* GoogleLoginDataManager */ = { + 3769D6AD2B3C2FA800294CD1 /* GoogleLogin */ = { isa = PBXGroup; children = ( 3769D6AE2B3C2FD500294CD1 /* GoogleLoginDataManager.swift */, 3769D6B12B3C300D00294CD1 /* GoogleLoginModel.swift */, ); - path = GoogleLoginDataManager; + path = GoogleLogin; sourceTree = ""; }; 3769D6B72B3C342900294CD1 /* Profile */ = { isa = PBXGroup; children = ( + 37EED79C2CFDDC1F00254471 /* ProfileDataSingleton.swift */, EAFB4B862A5147A300E0F2FA /* ProfileTab.storyboard */, - 37D311E72B567B3F00A1D7C1 /* OtherProfile */, - 3769D6C92B3C50D200294CD1 /* MyProfile */, + 3769D6B82B3C345800294CD1 /* MyProfileTabViewController.swift */, + 37D311E42B567AE000A1D7C1 /* OtherUserProfileViewController.swift */, + 37A284C02B52FC910023EB9D /* UpdateProfileViewController.swift */, + 374261632C2FEF5F00B67886 /* ProfileMenuViewController.swift */, + 3747E9712BF3827500364BED /* SettingsViewController.swift */, + 3766F4042C370A5D001DD239 /* BlockList */, + 3769D6CC2B3C560200294CD1 /* PostList */, + 3769D6CB2B3C55E500294CD1 /* FollowList */, ); path = Profile; sourceTree = ""; @@ -991,33 +988,18 @@ path = cell; sourceTree = ""; }; - 3769D6C92B3C50D200294CD1 /* MyProfile */ = { - isa = PBXGroup; - children = ( - 3769D6B82B3C345800294CD1 /* MyProfileTabViewController.swift */, - 37A284C02B52FC910023EB9D /* UpdateProfileViewController.swift */, - 3742616A2C3075B400B67886 /* Setting */, - 37D311D32B53053C00A1D7C1 /* ProfileUpdateAPI */, - 37C3AF442B3CB26000B4C79C /* ProfileAPI */, - 3769D6CC2B3C560200294CD1 /* PostListTabman */, - 3769D6CB2B3C55E500294CD1 /* FollowListTabman */, - ); - path = MyProfile; - sourceTree = ""; - }; - 3769D6CB2B3C55E500294CD1 /* FollowListTabman */ = { + 3769D6CB2B3C55E500294CD1 /* FollowList */ = { isa = PBXGroup; children = ( 3769D6C02B3C39EA00294CD1 /* cell */, - 373235452B68F9180042EF94 /* FollowAPI */, 3769D6BA2B3C347000294CD1 /* FollowListViewController.swift */, 3769D6BC2B3C348800294CD1 /* FollowerListTabmanViewController.swift */, 3769D6BE2B3C349D00294CD1 /* FollowingListTabmanViewController.swift */, ); - path = FollowListTabman; + path = FollowList; sourceTree = ""; }; - 3769D6CC2B3C560200294CD1 /* PostListTabman */ = { + 3769D6CC2B3C560200294CD1 /* PostList */ = { isa = PBXGroup; children = ( 37F1487A2C62382300D9B117 /* cell */, @@ -1025,53 +1007,29 @@ 3769D6CF2B3C569C00294CD1 /* PochakedPostTabmanViewController.swift */, 3769D6D12B3C56F100294CD1 /* PochakPostTabmanViewController.swift */, ); - path = PostListTabman; - sourceTree = ""; - }; - 37C3AF442B3CB26000B4C79C /* ProfileAPI */ = { - isa = PBXGroup; - children = ( - 37C3AF472B3CB2B300B4C79C /* MyProfilePochakedPostModel.swift */, - 37C3AF492B3CB2C700B4C79C /* MyProfilePochakPostModel.swift */, - 37C3AF452B3CB29A00B4C79C /* MyProfilePostDataManager.swift */, - ); - path = ProfileAPI; - sourceTree = ""; - }; - 37D311D32B53053C00A1D7C1 /* ProfileUpdateAPI */ = { - isa = PBXGroup; - children = ( - 37D311D62B53057700A1D7C1 /* ProfileUpdateDataModel.swift */, - 37D311D42B53056700A1D7C1 /* ProfileUpdateDataManager.swift */, - ); - path = ProfileUpdateAPI; + path = PostList; sourceTree = ""; }; - 37D311D82B53131D00A1D7C1 /* LogoutAPI */ = { + 376EA1272CAAAD3500FEFB63 /* Profile */ = { isa = PBXGroup; children = ( - 37D311D92B53134200A1D7C1 /* LogoutDataManager.swift */, - 37D311DB2B53135000A1D7C1 /* LogoutDataModel.swift */, + 376EA1282CAAADCF00FEFB63 /* ProfileRetrievalAPI.swift */, + 374E461E2CAABAF3008AE361 /* PochakPostRetrievalAPI.swift */, + 374E46282CAABE50008AE361 /* ProfileUpdateAPI.swift */, ); - path = LogoutAPI; - sourceTree = ""; - }; - 37D311DD2B566C3D00A1D7C1 /* SignoutAPI */ = { - isa = PBXGroup; - children = ( - 37D311DE2B566C4D00A1D7C1 /* SignOutDataManager.swift */, - 37D311E02B566C5900A1D7C1 /* SignOutDataModel.swift */, - ); - path = SignoutAPI; + path = Profile; sourceTree = ""; }; - 37D311E72B567B3F00A1D7C1 /* OtherProfile */ = { + 376EA12A2CAAAE3600FEFB63 /* Profile */ = { isa = PBXGroup; children = ( - 37D311E42B567AE000A1D7C1 /* OtherUserProfileViewController.swift */, - 3742616B2C30911A00B67886 /* ProfileMenu */, + 376EA12D2CAAAE6800FEFB63 /* ProfileRetrievalRequest.swift */, + 376EA12B2CAAAE5600FEFB63 /* ProfileRetrievalResponse.swift */, + 374E46222CAABB31008AE361 /* PochakPostRetrievalResponse.swift */, + 374E462A2CAABF7A008AE361 /* ProfileUpdateRequest.swift */, + 374E46242CAABE07008AE361 /* ProfileUpdateResponse.swift */, ); - path = OtherProfile; + path = Profile; sourceTree = ""; }; 37F1487A2C62382300D9B117 /* cell */ = { @@ -1192,6 +1150,8 @@ 08C9151B2C6461B10044A9A8 /* PostService.swift */, EA7E146A2C8E9360001DA769 /* AlarmService.swift */, EA7E14602C858EC6001DA769 /* CameraService.swift */, + 376EA12F2CAAAF6900FEFB63 /* ProfileService.swift */, + 374E462C2CAAC399008AE361 /* AuthenticationService.swift */, ); path = Services; sourceTree = ""; @@ -1199,6 +1159,8 @@ EA2CD6332C5F458900644EFE /* Models */ = { isa = PBXGroup; children = ( + 374E462E2CAAC3CB008AE361 /* Authentication */, + 376EA12A2CAAAE3600FEFB63 /* Profile */, 0822A05A2CA1983C00F87E2C /* Post */, 0822A0552CA1983C00F87E2C /* Search */, 0822A0582CA1983C00F87E2C /* User */, @@ -1225,6 +1187,8 @@ EA2CD6352C5F45A100644EFE /* APIs */ = { isa = PBXGroup; children = ( + 374E46332CAAC437008AE361 /* Authentication */, + 376EA1272CAAAD3500FEFB63 /* Profile */, 0822A04D2CA1980200F87E2C /* User */, 0822A04A2CA197F800F87E2C /* Search */, 0822A0382CA197E700F87E2C /* Post */, @@ -1644,31 +1608,37 @@ buildActionMask = 2147483647; files = ( EA7EE1D82BF38921009486CC /* PochakAlarmTableViewCell.swift in Sources */, + 376EA1302CAAAF6900FEFB63 /* ProfileService.swift in Sources */, EA01079E2A59556800A3AE85 /* PostViewController.swift in Sources */, + 3759CF432CAB152200408A4E /* CheckDuplicateHandleResponse.swift in Sources */, 3766F40F2C398AA8001DD239 /* NetworkResult400.swift in Sources */, 0822A0532CA1983100F87E2C /* ExploreRequest.swift in Sources */, 3769D6AF2B3C2FD500294CD1 /* GoogleLoginDataManager.swift in Sources */, EA0107AE2A5EC23900A3AE85 /* OtherTableViewCell.swift in Sources */, EA2CD6482C5F5B0700644EFE /* HomeRequest.swift in Sources */, - 37C3AF4B2B3CB49E00B4C79C /* MyProfilePostDataManager.swift in Sources */, EA7E14672C858F0F001DA769 /* CameraAPI.swift in Sources */, + 3759CF392CAAE7E900408A4E /* FollowListRequest.swift in Sources */, 0822A02D2CA196DF00F87E2C /* PostCollectionViewCell.swift in Sources */, EA2CD63F2C5F585400644EFE /* HTTPHeaderType.swift in Sources */, 3769D6CE2B3C563A00294CD1 /* PostListViewController.swift in Sources */, 0822A0542CA1983100F87E2C /* ExploreResponse.swift in Sources */, + 374E46492CAACE53008AE361 /* BlockListResponse.swift in Sources */, + 374E46352CAAC445008AE361 /* SignOutAPI.swift in Sources */, 0822A0662CA1983C00F87E2C /* SearchRequest.swift in Sources */, 0822A0712CA1983C00F87E2C /* PostDetailResponse.swift in Sources */, + 376EA12C2CAAAE5600FEFB63 /* ProfileRetrievalResponse.swift in Sources */, EA0107A72A5CF73900A3AE85 /* AlarmViewController.swift in Sources */, 08EB99B72C53EA8500FAC08A /* TaggedUsersTableViewCell.swift in Sources */, + 3759CF332CAAE4CE00408A4E /* DeleteFollowerResponse.swift in Sources */, EACF0F6E2C3044C000C0016C /* TagSearchTableViewCell.swift in Sources */, 08B679632B528D7500F8D585 /* CommentTableViewFooterView.swift in Sources */, EA2CD6442C5F5A0E00644EFE /* HomeAPI.swift in Sources */, - 373235502B691E6A0042EF94 /* FollowToggleDataModel.swift in Sources */, 0802AE032C31D3E00090FB92 /* UIImageView+.swift in Sources */, 3769D6C72B3C4F0900294CD1 /* FollowingCollectionViewCell.swift in Sources */, 08E8DCDC2C38118000EC78C2 /* UILabel+.swift in Sources */, - 373235472B68F93E0042EF94 /* FollowListDataManager.swift in Sources */, + 374E462D2CAAC399008AE361 /* AuthenticationService.swift in Sources */, 08EB5D252C280CFE00E35C16 /* UIImage+.swift in Sources */, + 374E463B2CAAC762008AE361 /* BlockAPI.swift in Sources */, 08D98C4A2AFEE8D700E9A097 /* LikedUsersDataModel.swift in Sources */, EA2CD6462C5F5A1E00644EFE /* HomeResponse.swift in Sources */, EA7E14632C858EEE001DA769 /* CameraUploadResponse.swift in Sources */, @@ -1681,12 +1651,12 @@ 0822A0312CA196DF00F87E2C /* RecentSearchRealmManager.swift in Sources */, 08D98C472AFEE8B300E9A097 /* (null) in Sources */, 3769D6C32B3C3A1600294CD1 /* FollowerCollectionViewCell.swift in Sources */, - 374261722C3092B100B67886 /* UnblockDataManager.swift in Sources */, EA0F23692C32DA780014F94E /* UIViewController+.swift in Sources */, 379A6A3A2B56BDBF0087FAAC /* Authenticator.swift in Sources */, EA17182C2A5912A7009168E6 /* UploadViewController.swift in Sources */, 089E27B72C4E8C2D00828F4C /* AlarmType.swift in Sources */, 3769D6B22B3C300D00294CD1 /* GoogleLoginModel.swift in Sources */, + 3759CF2F2CAAE42600408A4E /* DeleteFollowerAPI.swift in Sources */, EA96D04C2C8F10C700D80562 /* TagPreviewResponse.swift in Sources */, 0822A06B2CA1983C00F87E2C /* ChildCommentGetResponse.swift in Sources */, 0822A0352CA196DF00F87E2C /* ExploreTabViewController.swift in Sources */, @@ -1696,49 +1666,50 @@ 3769D6BF2B3C349D00294CD1 /* FollowingListTabmanViewController.swift in Sources */, 08C02EEA2BF3798F00627C21 /* ReportViewController.swift in Sources */, 0822A0722CA1983C00F87E2C /* PostDeleteResponse.swift in Sources */, - 373235562B692C790042EF94 /* DeleteFollowerDataModel.swift in Sources */, 3766F40C2C3736F1001DD239 /* RequestInterceptor.swift in Sources */, EA0F23662C319C030014F94E /* PreviewAlarmViewController.swift in Sources */, 0822A0422CA197E700F87E2C /* PostReportAPI.swift in Sources */, + 374E46292CAABE50008AE361 /* ProfileUpdateAPI.swift in Sources */, 0844D7EF2BF219F6001154AC /* DeleteViewCell.swift in Sources */, - 374261742C3092B900B67886 /* UnblockDataModel.swift in Sources */, + 374E463D2CAAC82E008AE361 /* BlockResponse.swift in Sources */, + 3759CF3B2CAAE80700408A4E /* FollowListResponse.swift in Sources */, EA2CD63B2C5F463700644EFE /* NetworkService.swift in Sources */, - 373235542B692C6D0042EF94 /* DeleteFollowerDataManager.swift in Sources */, - 3742617D2C359D6100B67886 /* CheckHandleDuplicationDataManager.swift in Sources */, 371D33782B4E83E90084FF6A /* UserDefaultsManager.swift in Sources */, 3742617A2C31C6F900B67886 /* AppleLoginDataModel.swift in Sources */, + 37EED79D2CFDDC3800254471 /* ProfileDataSingleton.swift in Sources */, 373815162A89E6D30032066A /* SocialJoinViewController.swift in Sources */, EA96D0542C8F192000D80562 /* TagApproveRequest.swift in Sources */, 3766F40A2C370CF5001DD239 /* BlockedUserViewController.swift in Sources */, 374261782C31C6ED00B67886 /* AppleLoginDataManager.swift in Sources */, 0844D7EB2BF218A5001154AC /* ReportViewCell.swift in Sources */, + 374E46472CAACE4C008AE361 /* BlockListRequest.swift in Sources */, 379A6A342B569DFA0087FAAC /* AuthenticationCredential.swift in Sources */, 0844649C2BF0F25900949850 /* PostMenuViewController.swift in Sources */, 0822A0702CA1983C00F87E2C /* PostReportResponse.swift in Sources */, 3766F4132C3A69E5001DD239 /* TermsOfAgreeViewController.swift in Sources */, 0822A0472CA197E700F87E2C /* PostLikeAPI.swift in Sources */, - 37D311DC2B53135000A1D7C1 /* LogoutDataModel.swift in Sources */, + 374E46432CAAC9B5008AE361 /* UnblockResponse.swift in Sources */, 37A284C12B52FC910023EB9D /* UpdateProfileViewController.swift in Sources */, - 3738151A2A8A09DA0032066A /* MakeProfileViewController.swift in Sources */, + 3738151A2A8A09DA0032066A /* SignUpViewController.swift in Sources */, 0822A0442CA197E700F87E2C /* CommentDeleteAPI.swift in Sources */, EA17182B2A5912A7009168E6 /* CameraViewController.swift in Sources */, + 374E46412CAAC9AE008AE361 /* UnblockRequest.swift in Sources */, 080FEF012C551C3200951460 /* ReportType.swift in Sources */, 0822A0782CA1986000F87E2C /* UserService.swift in Sources */, EA7E14712C8EA26D001DA769 /* AlarmListRequest.swift in Sources */, - 37D311DF2B566C4D00A1D7C1 /* SignOutDataManager.swift in Sources */, EA817AB62B3C984000E741E4 /* LeftAlignedCollectionViewFlowLayout.swift in Sources */, - 3742616E2C30916C00B67886 /* BlockDataManager.swift in Sources */, 379A6A3E2B5FA07D0087FAAC /* NavigationController.swift in Sources */, 0822A0462CA197E700F87E2C /* CommentPostAPI.swift in Sources */, - 3766F4062C370A7C001DD239 /* BlockedUserListDataManager.swift in Sources */, + 374E46372CAAC602008AE361 /* LogOutAPI.swift in Sources */, 3769D6D52B3C59F900294CD1 /* ProfilePostCollectionViewCell.swift in Sources */, + 376EA12E2CAAAE6800FEFB63 /* ProfileRetrievalRequest.swift in Sources */, EA96D0562C8F192B00D80562 /* TagApproveResponse.swift in Sources */, - 3769D6B62B3C302C00294CD1 /* JoinDataModel.swift in Sources */, + 374E461F2CAABAF3008AE361 /* PochakPostRetrievalAPI.swift in Sources */, 084AF4C72A5D831100C6454A /* HomeCollectionViewCell.swift in Sources */, - 37C3AF4A2B3CB2C700B4C79C /* MyProfilePochakPostModel.swift in Sources */, 08C02EF82BF394B800627C21 /* UIAlertController+.swift in Sources */, 089E27B92C4E8CB800828F4C /* AlertType.swift in Sources */, EAE69F442C315009005762B7 /* UIVIew+.swift in Sources */, + 3759CF312CAAE4C600408A4E /* DeleteFollowerRequest.swift in Sources */, 0876E23E2C274256008DE88F /* NoPostCollectionViewCell.swift in Sources */, EA96D0502C8F15E000D80562 /* TagPreviewAPI.swift in Sources */, 0822A0482CA197E700F87E2C /* PostDeleteAPI.swift in Sources */, @@ -1751,28 +1722,27 @@ 0801765E2A74F4B0005E6953 /* UITextView+Extension.swift in Sources */, 081228C62A69624000DFA094 /* PostLikesViewController.swift in Sources */, EA01079B2A59556800A3AE85 /* HomeTabViewController.swift in Sources */, - 373235492B68F9A60042EF94 /* FollowListDataModel.swift in Sources */, + 3759CF352CAAE79000408A4E /* FollowerRetrievalAPI.swift in Sources */, 0822A0332CA196DF00F87E2C /* RecentSearchViewController.swift in Sources */, 0822A06E2CA1983C00F87E2C /* CommentPostResponse.swift in Sources */, EA7E14612C858EC6001DA769 /* CameraService.swift in Sources */, EA6306D82C2BFE53000DB0A7 /* CustomTabBarController.swift in Sources */, 0822A06A2CA1983C00F87E2C /* PostLikeResponse.swift in Sources */, + 3759CF492CAB178800408A4E /* SignUpRequest.swift in Sources */, 0820B1902A63A18100604DA8 /* ReplyTableViewCell.swift in Sources */, - 374261702C30917600B67886 /* BlockDataModel.swift in Sources */, + 374E46392CAAC646008AE361 /* LogOutResponse.swift in Sources */, 083249782C295E4000A77C03 /* CustomAlertViewController.swift in Sources */, - 37C3AF482B3CB2B300B4C79C /* MyProfilePochakedPostModel.swift in Sources */, + 374E46252CAABE08008AE361 /* ProfileUpdateResponse.swift in Sources */, 0822A04F2CA1980200F87E2C /* FollowAPI.swift in Sources */, EA2CD6392C5F45E300644EFE /* BaseAPI.swift in Sources */, - 37D311E12B566C5A00A1D7C1 /* SignOutDataModel.swift in Sources */, - 376F125B2A8B7440005F469B /* SocialLoginViewController.swift in Sources */, - 37D311D72B53057700A1D7C1 /* ProfileUpdateDataModel.swift in Sources */, + 3759CF4B2CAB179100408A4E /* SignUpResponse.swift in Sources */, + 374E46302CAAC3F6008AE361 /* SignOutResponse.swift in Sources */, EA4EF8D82C2EC5F7009549FF /* UITextField+.swift in Sources */, EAF918512AA468670037C270 /* APIConstants.swift in Sources */, 0822A0322CA196DF00F87E2C /* RecentSearchModel.swift in Sources */, - 3766F4082C370A82001DD239 /* BlockedUserListDataModel.swift in Sources */, 0822A06D2CA1983C00F87E2C /* CommentPostRequest.swift in Sources */, - 3732354E2B691E610042EF94 /* FollowToggleDataManager.swift in Sources */, 0822A0692CA1983C00F87E2C /* PostReportRequest.swift in Sources */, + 3759CF412CAB150500408A4E /* CheckDuplicateHandleRequest.swift in Sources */, 3769D6BD2B3C348800294CD1 /* FollowerListTabmanViewController.swift in Sources */, 0822A0672CA1983C00F87E2C /* SearchResponse.swift in Sources */, EA1B0B5B2A49D23A00F86FBC /* AppDelegate.swift in Sources */, @@ -1781,7 +1751,7 @@ 0889D1382C5CB69F00206C86 /* String+.swift in Sources */, 0844D7F32BF21A80001154AC /* CancelViewCell.swift in Sources */, 3766F4032C3706DC001DD239 /* BlockedUserTableViewCell.swift in Sources */, - 37D311D52B53056700A1D7C1 /* ProfileUpdateDataManager.swift in Sources */, + 374E46452CAACD33008AE361 /* BlockListAPI.swift in Sources */, 3769D6B92B3C345800294CD1 /* MyProfileTabViewController.swift in Sources */, 0822A0682CA1983C00F87E2C /* FollowResponse.swift in Sources */, EA2CD63D2C5F4C2B00644EFE /* NetworkError.swift in Sources */, @@ -1789,24 +1759,30 @@ 0822A07A2CA1986000F87E2C /* ExploreService.swift in Sources */, 08FD3A392B357E6400574880 /* LikedUsersDataService.swift in Sources */, 081228CA2A699B5A00DFA094 /* LikedPeopleListTableViewCell.swift in Sources */, + 374E463F2CAAC931008AE361 /* UnblockAPI.swift in Sources */, 0822A06C2CA1983C00F87E2C /* CommentGetResponse.swift in Sources */, 3769D6D02B3C569C00294CD1 /* PochakedPostTabmanViewController.swift in Sources */, 374261642C2FEF5F00B67886 /* ProfileMenuViewController.swift in Sources */, - 3742617F2C359D6D00B67886 /* CheckHandleDuplicationDataModel.swift in Sources */, + 3759CF372CAAE79F00408A4E /* FollowingRetrievalAPI.swift in Sources */, EAC582742C3074EA006F44AF /* UIColor+.swift in Sources */, + 37EED79F2CFDFE9100254471 /* Notification+.swift in Sources */, 3769D6BB2B3C347000294CD1 /* FollowListViewController.swift in Sources */, - 3769D6B42B3C301B00294CD1 /* JoinDataManager.swift in Sources */, + 376EA1292CAAADCF00FEFB63 /* ProfileRetrievalAPI.swift in Sources */, EA96D0522C8F186900D80562 /* TagApproveAPI.swift in Sources */, 0822A07B2CA1992B00F87E2C /* PostService.swift in Sources */, - 37D311DA2B53134200A1D7C1 /* LogoutDataManager.swift in Sources */, + 3759CF452CAB16E600408A4E /* SignUpAPI.swift in Sources */, EA1B0B5D2A49D23A00F86FBC /* SceneDelegate.swift in Sources */, 3747E9722BF3827500364BED /* SettingsViewController.swift in Sources */, 3769D6D22B3C56F100294CD1 /* PochakPostTabmanViewController.swift in Sources */, 371D337A2B4E8D1F0084FF6A /* KeychainManager.swift in Sources */, + 37EED7A12D07094F00254471 /* HandleDuplicationType.swift in Sources */, EAF918542AA4692E0037C270 /* NetworkResult.swift in Sources */, 0822A07D2CA19A9A00F87E2C /* UICommentModel.swift in Sources */, + 3759CF3F2CAB145500408A4E /* CheckDuplicateHandleAPI.swift in Sources */, + 374E462B2CAABF7B008AE361 /* ProfileUpdateRequest.swift in Sources */, EA7E146D2C8EA119001DA769 /* AlarmListResponse.swift in Sources */, 08EB99B42C53D76C00FAC08A /* UIScrollView+.swift in Sources */, + 374E46232CAABB31008AE361 /* PochakPostRetrievalResponse.swift in Sources */, EA6306DA2C2BFE83000DB0A7 /* CustomTabbar.swift in Sources */, EA2CD6422C5F5A0500644EFE /* HomeService.swift in Sources */, EA7E14652C858EFA001DA769 /* CameraUploadRequest.swift in Sources */, @@ -1989,7 +1965,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = JU9BM2NXCW; + DEVELOPMENT_TEAM = 2285AK4JY4; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = pochak/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Pochak; @@ -2028,7 +2004,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = JU9BM2NXCW; + DEVELOPMENT_TEAM = 2285AK4JY4; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = pochak/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Pochak; diff --git a/pochak/pochak/App/AppDelegate.swift b/pochak/pochak/App/AppDelegate.swift index 954e5785..c5710f9c 100644 --- a/pochak/pochak/App/AppDelegate.swift +++ b/pochak/pochak/App/AppDelegate.swift @@ -12,7 +12,7 @@ import FirebaseCore @main class AppDelegate: UIResponder, UIApplicationDelegate { - + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. sleep(2) @@ -64,22 +64,69 @@ class AppDelegate: UIResponder, UIApplicationDelegate { removeKeychainAtFirstLaunch() return true } - + private func removeKeychainAtFirstLaunch() { guard UserDefaults.isFirstLaunch() else { return } + do { + try KeychainManager.delete(account: "accessToken") + try KeychainManager.delete(account: "refreshToken") + } catch { + print(error) + } + } + + // 리프레시 토큰 유효성 검사 + func applicationDidBecomeActive(_ application: UIApplication) { + handleRefreshToken() + } + + private func handleRefreshToken() { + if !isRefreshTokenValid() { + print("Refresh Token 만료됨.. 재로그인 필요") + AuthenticationService.logOut { data, failed in + guard let data = data else { + return + } + let message = data.message + print(message) + } + deleteUserData() + moveToMainPage() + } + } + + private func isRefreshTokenValid() -> Bool { + guard let issuedAt = UserDefaultsManager.getData(type: Date.self, forKey: .refreshTokenIssuedAt) else { + return false // 발급 시점을 알 수 없으면 토큰이 유효하지 않음 + } + let validityPeriod: TimeInterval = 30 * 24 * 60 * 60 // 1달(30일)을 초 단위로 + let expirationDate = issuedAt.addingTimeInterval(validityPeriod) - // 첫 실행이라면 keyChain 정보를 삭제 + return Date() < expirationDate // 현재 시간과 만료 시간을 비교 + } + + private func deleteUserData() { do { + // Keychain 삭제 try KeychainManager.delete(account: "accessToken") try KeychainManager.delete(account: "refreshToken") + + // UserDefaults 삭제 + UserDefaultsManager.UserDefaultsKeys.allCases.forEach { key in + UserDefaultsManager.removeData(key: key) + } } catch { print(error) } } - - // MARK: UISceneSession Lifecycle + + private func moveToMainPage() { + let mainVCBundle = UIStoryboard.init(name: "Login", bundle: nil) + guard let mainVC = mainVCBundle.instantiateViewController(withIdentifier: "NavigationVC") as? NavigationController else { return } + (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.changeRootViewController(mainVC, animated: false) + } func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { // Called when a new scene session is being created. @@ -93,7 +140,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } - // Google 로그인 + // Google 로그인 등록 func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool { return GIDSignIn.sharedInstance.handle(url) } diff --git a/pochak/pochak/Global/Extensions/Notification+.swift b/pochak/pochak/Global/Extensions/Notification+.swift new file mode 100644 index 00000000..11fe574d --- /dev/null +++ b/pochak/pochak/Global/Extensions/Notification+.swift @@ -0,0 +1,13 @@ +// +// Notification.swift +// pochak +// +// Created by Seo Cindy on 12/2/24. +// + +import Foundation + +extension Notification.Name { + static let didUpdateTotalHeight = Notification.Name("didUpdateTotalHeight") + static let didHitBottom = Notification.Name("didHitBottom") +} diff --git a/pochak/pochak/Global/Types/HandleDuplicationType.swift b/pochak/pochak/Global/Types/HandleDuplicationType.swift new file mode 100644 index 00000000..271bfb96 --- /dev/null +++ b/pochak/pochak/Global/Types/HandleDuplicationType.swift @@ -0,0 +1,25 @@ +// +// HandleDuplicationType.swift +// pochak +// +// Created by Seo Cindy on 12/9/24. +// + +import Foundation + +enum MemberCode: String { + case success = "MEMBER2001" // 중복 검사 성공 + case duplicationError = "MEMBER4002" // 중복된 아이디 + case unknown // 알 수 없는 코드 + + init(rawValue: String) { + switch rawValue { + case "MEMBER2001": + self = .success + case "MEMBER4002": + self = .duplicationError + default: + self = .unknown + } + } +} diff --git a/pochak/pochak/Network/APIs/Authentication/CheckDuplicateHandleAPI.swift b/pochak/pochak/Network/APIs/Authentication/CheckDuplicateHandleAPI.swift new file mode 100644 index 00000000..3203170b --- /dev/null +++ b/pochak/pochak/Network/APIs/Authentication/CheckDuplicateHandleAPI.swift @@ -0,0 +1,36 @@ +// +// CheckDuplicateHandleAPI.swift +// pochak +// +// Created by Seo Cindy on 10/1/24. +// + +import Foundation +import Alamofire + +enum CheckDuplicateHandleAPI { + case checkDuplicateHandle(CheckDuplicateHandleRequest) +} + +extension CheckDuplicateHandleAPI: BaseAPI { + + typealias Response = CheckDuplicateHandleResponse + + var method: HTTPMethod { + switch self { + case .checkDuplicateHandle: return .get + } + } + + var path: String { + switch self { + case .checkDuplicateHandle: return "/v2/members/duplicate" + } + } + + var parameters: RequestParams? { + switch self { + case .checkDuplicateHandle(let request): return .query(request) + } + } +} diff --git a/pochak/pochak/Network/APIs/Authentication/LogOutAPI.swift b/pochak/pochak/Network/APIs/Authentication/LogOutAPI.swift new file mode 100644 index 00000000..83679c09 --- /dev/null +++ b/pochak/pochak/Network/APIs/Authentication/LogOutAPI.swift @@ -0,0 +1,30 @@ +// +// LogOutAPI.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation +import Alamofire + +enum LogOutAPI { + case logOut +} + +extension LogOutAPI: BaseAPI { + + typealias Response = LogOutResponse + + var method: HTTPMethod { + switch self { + case .logOut: return .get + } + } + + var path: String { + switch self { + case .logOut: return "/v2/logout" + } + } +} diff --git a/pochak/pochak/Network/APIs/Authentication/SignOutAPI.swift b/pochak/pochak/Network/APIs/Authentication/SignOutAPI.swift new file mode 100644 index 00000000..558c9632 --- /dev/null +++ b/pochak/pochak/Network/APIs/Authentication/SignOutAPI.swift @@ -0,0 +1,30 @@ +// +// SignOutAPI.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation +import Alamofire + +enum SignOutAPI { + case signOut +} + +extension SignOutAPI: BaseAPI { + + typealias Response = SignOutResponse + + var method: HTTPMethod { + switch self { + case .signOut: return .delete + } + } + + var path: String { + switch self { + case .signOut: return "/v2/signout" + } + } +} diff --git a/pochak/pochak/Network/APIs/Authentication/SignUpAPI.swift b/pochak/pochak/Network/APIs/Authentication/SignUpAPI.swift new file mode 100644 index 00000000..d32e7962 --- /dev/null +++ b/pochak/pochak/Network/APIs/Authentication/SignUpAPI.swift @@ -0,0 +1,36 @@ +// +// SignUpAPI.swift +// pochak +// +// Created by Seo Cindy on 10/1/24. +// + +import Foundation +import Alamofire + +enum SignUpAPI { + case signUp(SignUpRequest) +} + +extension SignUpAPI: BaseAPI { + + typealias Response = SignUpResponse + + var method: HTTPMethod { + switch self { + case .signUp: return .post + } + } + + var path: String { + switch self { + case .signUp: return "/v2/signup" + } + } + + var parameters: RequestParams? { + switch self { + case .signUp(let request): return .query(request) + } + } +} diff --git a/pochak/pochak/Network/APIs/Profile/PochakPostRetrievalAPI.swift b/pochak/pochak/Network/APIs/Profile/PochakPostRetrievalAPI.swift new file mode 100644 index 00000000..a41203d7 --- /dev/null +++ b/pochak/pochak/Network/APIs/Profile/PochakPostRetrievalAPI.swift @@ -0,0 +1,37 @@ +// +// PochakPostRetrievalAPI.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation +import Alamofire + +enum PochakPostRetrievalAPI { + case getPochakPost(handle: String, request: ProfileRetrievalRequest) +} + +extension PochakPostRetrievalAPI: BaseAPI { + + typealias Response = PochakPostRetrievalResponse + + var method: HTTPMethod { + switch self { + case .getPochakPost: return .get + } + } + + var path: String { + switch self { + case .getPochakPost(let handle, _): return "/v2/members/\(handle)/upload" + } + } + + var parameters: RequestParams? { + switch self { + case .getPochakPost(_, let request): return .query(request) + } + } +} + diff --git a/pochak/pochak/Network/APIs/Profile/ProfileRetrievalAPI.swift b/pochak/pochak/Network/APIs/Profile/ProfileRetrievalAPI.swift new file mode 100644 index 00000000..d473431d --- /dev/null +++ b/pochak/pochak/Network/APIs/Profile/ProfileRetrievalAPI.swift @@ -0,0 +1,37 @@ +// +// ProfileRetrievalAPI.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation +import Alamofire + +enum ProfileRetrievalAPI { + case getProfile(handle: String, request: ProfileRetrievalRequest) +} + +extension ProfileRetrievalAPI: BaseAPI { + + typealias Response = ProfileRetrievalResponse + + var method: HTTPMethod { + switch self { + case .getProfile: return .get + } + } + + var path: String { + switch self { + case .getProfile(let handle, _): return "/v2/members/\(handle)" + } + } + + var parameters: RequestParams? { + switch self { + case .getProfile(_, let request): return .query(request) + } + } +} + diff --git a/pochak/pochak/Network/APIs/Profile/ProfileUpdateAPI.swift b/pochak/pochak/Network/APIs/Profile/ProfileUpdateAPI.swift new file mode 100644 index 00000000..5af31122 --- /dev/null +++ b/pochak/pochak/Network/APIs/Profile/ProfileUpdateAPI.swift @@ -0,0 +1,37 @@ +// +// ProfileUpdateAPI.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation +import Alamofire + + +enum ProfileUpdateAPI { + case updateProfile(handle: String, request: ProfileUpdateRequest) +} + +extension ProfileUpdateAPI: BaseAPI { + + typealias Response = ProfileUpdateResponse + + var method: HTTPMethod { + switch self { + case .updateProfile: return .put + } + } + + var path: String { + switch self { + case .updateProfile(let handle, _): return "/v2/members/\(handle)" + } + } + + var parameters: RequestParams? { + switch self { + case .updateProfile(_, let request): return .query(request) + } + } +} diff --git a/pochak/pochak/Network/APIs/User/BlockAPI.swift b/pochak/pochak/Network/APIs/User/BlockAPI.swift new file mode 100644 index 00000000..c39c4632 --- /dev/null +++ b/pochak/pochak/Network/APIs/User/BlockAPI.swift @@ -0,0 +1,30 @@ +// +// BlockAPI.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation +import Alamofire + +enum BlockAPI { + case blockUser(String) +} + +extension BlockAPI: BaseAPI { + + typealias Response = BlockResponse + + var method: HTTPMethod { + switch self { + case .blockUser: return .post + } + } + + var path: String { + switch self { + case .blockUser(let handle): return "/v2/members/\(handle)/block" + } + } +} diff --git a/pochak/pochak/Network/APIs/User/BlockListAPI.swift b/pochak/pochak/Network/APIs/User/BlockListAPI.swift new file mode 100644 index 00000000..1e988bd1 --- /dev/null +++ b/pochak/pochak/Network/APIs/User/BlockListAPI.swift @@ -0,0 +1,36 @@ +// +// BlockListAPI.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation +import Alamofire + +enum BlockListAPI { + case getBlockUserList(handle: String, request: BlockListRequest) +} + +extension BlockListAPI: BaseAPI { + + typealias Response = BlockListResponse + + var method: HTTPMethod { + switch self { + case .getBlockUserList: return .get + } + } + + var path: String { + switch self { + case .getBlockUserList(let handle, _): return "/v2/members/\(handle)/block" + } + } + + var parameters: RequestParams? { + switch self { + case .getBlockUserList(_, let request): return .query(request) + } + } +} diff --git a/pochak/pochak/Network/APIs/User/DeleteFollowerAPI.swift b/pochak/pochak/Network/APIs/User/DeleteFollowerAPI.swift new file mode 100644 index 00000000..58c249cc --- /dev/null +++ b/pochak/pochak/Network/APIs/User/DeleteFollowerAPI.swift @@ -0,0 +1,36 @@ +// +// DeleteFollowerAPI.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation +import Alamofire + +enum DeleteFollowerAPI { + case deleteFollower(handle: String, request: DeleteFollowerRequest) +} + +extension DeleteFollowerAPI: BaseAPI { + + typealias Response = DeleteFollowerResponse + + var method: HTTPMethod { + switch self { + case .deleteFollower: return .delete + } + } + + var path: String { + switch self { + case .deleteFollower(let handle, _): return "/v2/members/\(handle)/follower" + } + } + + var parameters: RequestParams? { + switch self { + case .deleteFollower(_, let request): return .query(request) + } + } +} diff --git a/pochak/pochak/Network/APIs/User/FollowAPI.swift b/pochak/pochak/Network/APIs/User/FollowAPI.swift index 031efc2c..127ee484 100644 --- a/pochak/pochak/Network/APIs/User/FollowAPI.swift +++ b/pochak/pochak/Network/APIs/User/FollowAPI.swift @@ -15,13 +15,13 @@ enum FollowAPI { extension FollowAPI: BaseAPI { typealias Response = FollowResponse - + var method: HTTPMethod { switch self { case .postFollowRequest: return .post } } - + var path: String { switch self { case .postFollowRequest(let handle): return "/v2/members/\(handle)/follow" diff --git a/pochak/pochak/Network/APIs/User/FollowerRetrievalAPI.swift b/pochak/pochak/Network/APIs/User/FollowerRetrievalAPI.swift new file mode 100644 index 00000000..0bc47fc5 --- /dev/null +++ b/pochak/pochak/Network/APIs/User/FollowerRetrievalAPI.swift @@ -0,0 +1,36 @@ +// +// FollowerRetrievalAPI.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation +import Alamofire + +enum FollowerRetrievalAPI { + case getFollowers(handle: String, request: FollowListRequest) +} + +extension FollowerRetrievalAPI: BaseAPI { + + typealias Response = FollowListResponse + + var method: HTTPMethod { + switch self { + case .getFollowers: return .get + } + } + + var path: String { + switch self { + case .getFollowers(let handle, _): return "/v2/members/\(handle)/follower" + } + } + + var parameters: RequestParams? { + switch self { + case .getFollowers(_, let request): return .query(request) + } + } +} diff --git a/pochak/pochak/Network/APIs/User/FollowingRetrievalAPI.swift b/pochak/pochak/Network/APIs/User/FollowingRetrievalAPI.swift new file mode 100644 index 00000000..78dc4d79 --- /dev/null +++ b/pochak/pochak/Network/APIs/User/FollowingRetrievalAPI.swift @@ -0,0 +1,36 @@ +// +// FollowingRetrievalAPI.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation +import Alamofire + +enum FollowingRetrievalAPI { + case getFollowings(handle: String, request: FollowListRequest) +} + +extension FollowingRetrievalAPI: BaseAPI { + + typealias Response = FollowListResponse + + var method: HTTPMethod { + switch self { + case .getFollowings: return .get + } + } + + var path: String { + switch self { + case .getFollowings(let handle, _): return "/v2/members/\(handle)/following" + } + } + + var parameters: RequestParams? { + switch self { + case .getFollowings(_, let request): return .query(request) + } + } +} diff --git a/pochak/pochak/Network/APIs/User/UnblockAPI.swift b/pochak/pochak/Network/APIs/User/UnblockAPI.swift new file mode 100644 index 00000000..22f694e4 --- /dev/null +++ b/pochak/pochak/Network/APIs/User/UnblockAPI.swift @@ -0,0 +1,36 @@ +// +// UnblockAPI.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation +import Alamofire + +enum UnblockAPI { + case unblockUser(handle: String, request: UnblockRequest) +} + +extension UnblockAPI: BaseAPI { + + typealias Response = UnblockResponse + + var method: HTTPMethod { + switch self { + case .unblockUser: return .delete + } + } + + var path: String { + switch self { + case .unblockUser(let handle, _): return "/v2/members/\(handle)/block" + } + } + + var parameters: RequestParams? { + switch self { + case .unblockUser(_, let request): return .query(request) + } + } +} diff --git a/pochak/pochak/Network/Authentication/AuthenticationCredential.swift b/pochak/pochak/Network/Authentication/AuthenticationCredential.swift index 30fc4f28..e930de90 100644 --- a/pochak/pochak/Network/Authentication/AuthenticationCredential.swift +++ b/pochak/pochak/Network/Authentication/AuthenticationCredential.swift @@ -4,6 +4,7 @@ // // Created by Seo Cindy on 1/16/24. // + import Foundation import Alamofire @@ -11,7 +12,5 @@ struct MyAuthenticationCredential: AuthenticationCredential, Codable { let accessToken: String let refreshToken: String let expiredAt: Date - - // 유효시간이 앞으로 5분 이하 남았다면 refresh가 필요하다고 true를 리턴 (false를 리턴하면 refresh 필요x) var requiresRefresh: Bool { Date(timeIntervalSinceNow: 60 * 10) > expiredAt } } diff --git a/pochak/pochak/Network/Authentication/Authenticator.swift b/pochak/pochak/Network/Authentication/Authenticator.swift index 03aff5af..a6e5fb42 100644 --- a/pochak/pochak/Network/Authentication/Authenticator.swift +++ b/pochak/pochak/Network/Authentication/Authenticator.swift @@ -8,88 +8,61 @@ import Foundation import Alamofire -// MARK: - Token Refresh Data Model struct TokenRefreshResponse: Codable { let isSuccess: Bool let code: String let message: String let result: TokenRefreshDataModel } + struct TokenRefreshDataModel: Codable { let accessToken : String } class MyAuthenticator : Authenticator { typealias Credential = MyAuthenticationCredential - + // 1. api요청 시 AuthenticatorIndicator객체가 존재하면, 요청 전에 가로채서 apply에서 Header에 bearerToken 추가 func apply(_ credential: Credential, to urlRequest: inout URLRequest) { - print("--------------- 1. apply Function 실행 중 ---------------") - print(">>>>> apply 현재 토큰 : \(credential.accessToken)") urlRequest.addValue(credential.accessToken, forHTTPHeaderField: "Authorization") -// urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type") - print(">>>>> apply 현재 urlRequest : \(urlRequest)") - - print(">>>> requiresRefresh?? : \(credential.requiresRefresh)") - print(">>>> expiredAt?? : \(credential.expiredAt)") } // 2. api요청 후 error가 떨어진 경우, 401에러(인증에러)인 경우만 refresh가 되도록 필터링 func didRequest(_ urlRequest: URLRequest, with response: HTTPURLResponse, failDueToAuthenticationError error: Error) -> Bool { - print("--------------- 2. didRequest Function 실행 중 ---------------") -// if let data = response.data, let errorMessage = String(data: data, encoding: .utf8) { -// print("Failure Data: \(errorMessage)") -// } - print(">>>>> didRequest 현재 response : \(response)") - print(">>>>> didRequest 현재 statusCode : \(response.statusCode)") - print(">>>>> didRequest 현재 error : \(error)") return response.statusCode == 401 } // 3. 인증이 필요한 urlRequest에 대해서만 refresh가 되도록, 이 경우에만 true를 리턴하여 refresh 요청 func isRequest(_ urlRequest: URLRequest, authenticatedWith credential: MyAuthenticationCredential) -> Bool { - // bearerToken의 urlRequest대해서만 refresh를 시도 (true) - print("--------------- 3. isRequest Function 실행 중 ---------------") let bearerToken = HTTPHeader.authorization(bearerToken: credential.accessToken).value let startIndex = bearerToken.index(bearerToken.startIndex, offsetBy: 7) - let newBearerToken = String(bearerToken[startIndex...]) // 12:00:00 - print(">>>>> headers bearerToken: \(urlRequest.headers["Authorization"])") - print(">>>>> bearerToken : \(newBearerToken)") - print(">>>>> bearerToken이 맞는건지 확인합니다 : \(urlRequest.headers["Authorization"] == newBearerToken)") + let newBearerToken = String(bearerToken[startIndex...]) return urlRequest.headers["Authorization"] == newBearerToken } - // 4. accesToken을 refresh 하는 부분 - // 추가 할 것 :: refreshtoken 만료 시 로그아웃 하도록 처리(유효기간 1달) + // 4. accesToken을 refresh func refresh(_ credential: MyAuthenticationCredential, for session: Alamofire.Session, completion: @escaping (Result) -> Void) { - print("--------------- 4. refresh Function 실행 중 ---------------") let url = "\(APIConstants.baseURL)/api/v2/refresh" - let header : HTTPHeaders = ["Authorization": credential.accessToken, "RefreshToken" : credential.refreshToken, "Content-type": "application/json"] + let header : HTTPHeaders = ["Authorization": credential.accessToken, + "RefreshToken" : credential.refreshToken, + "Content-type": "application/json"] - print(">>>>> url : \(url)") - print(">>>>> header : \(header)") AF.request(url, method: .post, headers: header).validate().responseDecodable(of: TokenRefreshResponse.self) { response in - switch response.result { - case .success(let result): - print(result.message) - print("inside Refresh Success!!!!") - let newAccessToken = result.result.accessToken - print(">>>>> newAccessToken : \(newAccessToken)") - do { - try KeychainManager.update(account: "accessToken", value: newAccessToken) - } catch { - print(error) - } - let credential = Credential(accessToken: GetToken.getAccessToken(), refreshToken: credential.refreshToken, expiredAt: Date(timeIntervalSinceNow: 60 * 30)) - completion(.success(credential)) - // 새로운 토큰으로 API 재요청 - case .failure(let error): - print("inside Refresh Fail!!!!") - if let data = response.data, let errorMessage = String(data: data, encoding: .utf8) { - print("Failure Data: \(errorMessage)") - } - print(error) - } - } + switch response.result { + case .success(let result): + let newAccessToken = result.result.accessToken + do { + try KeychainManager.update(account: "accessToken", value: newAccessToken) + } catch { + print(error) + } + let credential = Credential(accessToken: GetToken.getAccessToken(), refreshToken: credential.refreshToken, expiredAt: Date(timeIntervalSinceNow: 60 * 30)) + completion(.success(credential)) + case .failure(let error): + if let data = response.data, let errorMessage = String(data: data, encoding: .utf8) { + print("Failure Data: \(errorMessage)") + } + } + } } } diff --git a/pochak/pochak/Network/Models/Authentication/CheckDuplicateHandleRequest.swift b/pochak/pochak/Network/Models/Authentication/CheckDuplicateHandleRequest.swift new file mode 100644 index 00000000..7734e6cd --- /dev/null +++ b/pochak/pochak/Network/Models/Authentication/CheckDuplicateHandleRequest.swift @@ -0,0 +1,12 @@ +// +// CheckDuplicateHandleRequest.swift +// pochak +// +// Created by Seo Cindy on 10/1/24. +// + +import Foundation + +struct CheckDuplicateHandleRequest : Codable { + let handle: String +} diff --git a/pochak/pochak/Network/Models/Authentication/CheckDuplicateHandleResponse.swift b/pochak/pochak/Network/Models/Authentication/CheckDuplicateHandleResponse.swift new file mode 100644 index 00000000..0d108b3d --- /dev/null +++ b/pochak/pochak/Network/Models/Authentication/CheckDuplicateHandleResponse.swift @@ -0,0 +1,14 @@ +// +// CheckDuplicateHandleResponse.swift +// pochak +// +// Created by Seo Cindy on 10/1/24. +// + +import Foundation + +struct CheckDuplicateHandleResponse: Codable { + let isSuccess: Bool + let code: String + let message: String +} diff --git a/pochak/pochak/Network/Models/Authentication/LogOutResponse.swift b/pochak/pochak/Network/Models/Authentication/LogOutResponse.swift new file mode 100644 index 00000000..c9f67d25 --- /dev/null +++ b/pochak/pochak/Network/Models/Authentication/LogOutResponse.swift @@ -0,0 +1,14 @@ +// +// LogOutResponse.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation + +struct LogOutResponse : Codable { + var isSuccess: Bool + var code: String + var message: String +} diff --git a/pochak/pochak/Network/Models/Authentication/SignOutResponse.swift b/pochak/pochak/Network/Models/Authentication/SignOutResponse.swift new file mode 100644 index 00000000..4ac56bc2 --- /dev/null +++ b/pochak/pochak/Network/Models/Authentication/SignOutResponse.swift @@ -0,0 +1,14 @@ +// +// SignOutResponse.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation + +struct SignOutResponse : Codable { + var isSuccess: Bool + var code: String + var message: String +} diff --git a/pochak/pochak/Network/Models/Authentication/SignUpRequest.swift b/pochak/pochak/Network/Models/Authentication/SignUpRequest.swift new file mode 100644 index 00000000..141c6ee5 --- /dev/null +++ b/pochak/pochak/Network/Models/Authentication/SignUpRequest.swift @@ -0,0 +1,17 @@ +// +// SignUpRequest.swift +// pochak +// +// Created by Seo Cindy on 10/1/24. +// + +import Foundation + +struct SignUpRequest : Codable { + let name: String + let email: String + let handle: String + let message: String + let socialId: String + let socialType: String +} diff --git a/pochak/pochak/UI/Login/SignUp/JoinDataModel.swift b/pochak/pochak/Network/Models/Authentication/SignUpResponse.swift similarity index 63% rename from pochak/pochak/UI/Login/SignUp/JoinDataModel.swift rename to pochak/pochak/Network/Models/Authentication/SignUpResponse.swift index 199a6939..a787a48b 100644 --- a/pochak/pochak/UI/Login/SignUp/JoinDataModel.swift +++ b/pochak/pochak/Network/Models/Authentication/SignUpResponse.swift @@ -1,18 +1,20 @@ // -// JoinDataModel.swift +// SignUpResponse.swift // pochak // -// Created by Seo Cindy on 12/27/23. +// Created by Seo Cindy on 10/1/24. // -struct JoinAPIResponse: Codable { +import Foundation + +struct SignUpResponse: Codable { let isSuccess: Bool let code: String let message: String - let result: JoinDataModel + let result: SignUpResult } -struct JoinDataModel : Codable { +struct SignUpResult : Codable { var id : Int? var socialId : String? var name : String? diff --git a/pochak/pochak/Network/Models/Profile/PochakPostRetrievalResponse.swift b/pochak/pochak/Network/Models/Profile/PochakPostRetrievalResponse.swift new file mode 100644 index 00000000..87286140 --- /dev/null +++ b/pochak/pochak/Network/Models/Profile/PochakPostRetrievalResponse.swift @@ -0,0 +1,20 @@ +// +// PochakPostRetrievalResponse.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation + +struct PochakPostRetrievalResponse: Codable { + let isSuccess: Bool + let code: String + let message: String + let result: PochakPostRetrievalResult +} + +struct PochakPostRetrievalResult : Codable { + var pageInfo : ProfilePageInfo + var postList : [ProfilePostList] +} diff --git a/pochak/pochak/Network/Models/Profile/ProfileRetrievalRequest.swift b/pochak/pochak/Network/Models/Profile/ProfileRetrievalRequest.swift new file mode 100644 index 00000000..349902ad --- /dev/null +++ b/pochak/pochak/Network/Models/Profile/ProfileRetrievalRequest.swift @@ -0,0 +1,12 @@ +// +// ProfileRetrievalRequest.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation + +struct ProfileRetrievalRequest: Encodable { + let page: Int +} diff --git a/pochak/pochak/UI/Profile/MyProfile/ProfileAPI/MyProfilePochakPostModel.swift b/pochak/pochak/Network/Models/Profile/ProfileRetrievalResponse.swift similarity index 55% rename from pochak/pochak/UI/Profile/MyProfile/ProfileAPI/MyProfilePochakPostModel.swift rename to pochak/pochak/Network/Models/Profile/ProfileRetrievalResponse.swift index 489b7b3a..51f82494 100644 --- a/pochak/pochak/UI/Profile/MyProfile/ProfileAPI/MyProfilePochakPostModel.swift +++ b/pochak/pochak/Network/Models/Profile/ProfileRetrievalResponse.swift @@ -1,18 +1,20 @@ // -// MyProfilePochakPostModel.swift +// ProfileRetrievalResponse.swift // pochak // -// Created by Seo Cindy on 12/28/23. +// Created by Seo Cindy on 9/30/24. // -struct MyProfileUserAndPochakedPostResponse: Codable { +import Foundation + +struct ProfileRetrievalResponse: Codable { let isSuccess: Bool let code: String let message: String - let result: MyProfileUserAndPochakedPostModel + let result: ProfileRetrievalResult } -struct MyProfileUserAndPochakedPostModel : Codable { +struct ProfileRetrievalResult : Codable { var handle: String? var profileImage: String? var name: String? @@ -21,18 +23,18 @@ struct MyProfileUserAndPochakedPostModel : Codable { var followerCount: Int? var followingCount: Int? var isFollow: Bool? - var pageInfo : PageDataModel - var postList : [PostDataModel] + var pageInfo : ProfilePageInfo + var postList : [ProfilePostList] } -struct PageDataModel : Codable { +struct ProfilePageInfo : Codable { var lastPage : Bool var totalPages : Int var totalElements : Int var size : Int } -struct PostDataModel : Codable { +struct ProfilePostList : Codable { var postId : Int var postImage : String } diff --git a/pochak/pochak/Network/Models/Profile/ProfileUpdateRequest.swift b/pochak/pochak/Network/Models/Profile/ProfileUpdateRequest.swift new file mode 100644 index 00000000..05139547 --- /dev/null +++ b/pochak/pochak/Network/Models/Profile/ProfileUpdateRequest.swift @@ -0,0 +1,13 @@ +// +// ProfileUpdateRequest.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation + +struct ProfileUpdateRequest: Encodable { + let name: String + let message: String +} diff --git a/pochak/pochak/UI/Profile/MyProfile/ProfileUpdateAPI/ProfileUpdateDataModel.swift b/pochak/pochak/Network/Models/Profile/ProfileUpdateResponse.swift similarity index 78% rename from pochak/pochak/UI/Profile/MyProfile/ProfileUpdateAPI/ProfileUpdateDataModel.swift rename to pochak/pochak/Network/Models/Profile/ProfileUpdateResponse.swift index bf6978a9..fbbf5e38 100644 --- a/pochak/pochak/UI/Profile/MyProfile/ProfileUpdateAPI/ProfileUpdateDataModel.swift +++ b/pochak/pochak/Network/Models/Profile/ProfileUpdateResponse.swift @@ -1,10 +1,12 @@ // -// MyProfileUpdateDataModel.swift +// ProfileUpdateResponse.swift // pochak // -// Created by Seo Cindy on 1/14/24. +// Created by Seo Cindy on 9/30/24. // +import Foundation + struct ProfileUpdateResponse: Codable { let isSuccess: Bool let code: String diff --git a/pochak/pochak/Network/Models/User/BlockListRequest.swift b/pochak/pochak/Network/Models/User/BlockListRequest.swift new file mode 100644 index 00000000..52a688b7 --- /dev/null +++ b/pochak/pochak/Network/Models/User/BlockListRequest.swift @@ -0,0 +1,12 @@ +// +// BlockListRequest.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation + +struct BlockListRequest: Encodable { + let page: Int +} diff --git a/pochak/pochak/Network/Models/User/BlockListResponse.swift b/pochak/pochak/Network/Models/User/BlockListResponse.swift new file mode 100644 index 00000000..172d08aa --- /dev/null +++ b/pochak/pochak/Network/Models/User/BlockListResponse.swift @@ -0,0 +1,33 @@ +// +// BlockListResponse.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation + +struct BlockListResponse : Codable { + let isSuccess: Bool + let code: String + let message: String + let result : BlockListResult +} + +struct BlockListResult : Codable { + let pageInfo: BlockListPageInfo + let blockList: [BlockList] +} + +struct BlockListPageInfo : Codable { + let lastPage : Bool + let totalPages: Int + let totalElements: Int + let size: Int +} + +struct BlockList : Codable { + let profileImage: String + let handle: String + let name: String +} diff --git a/pochak/pochak/UI/Profile/OtherProfile/ProfileMenu/BlockAPI/BlockDataModel.swift b/pochak/pochak/Network/Models/User/BlockResponse.swift similarity index 53% rename from pochak/pochak/UI/Profile/OtherProfile/ProfileMenu/BlockAPI/BlockDataModel.swift rename to pochak/pochak/Network/Models/User/BlockResponse.swift index 59287069..aedc94d4 100644 --- a/pochak/pochak/UI/Profile/OtherProfile/ProfileMenu/BlockAPI/BlockDataModel.swift +++ b/pochak/pochak/Network/Models/User/BlockResponse.swift @@ -1,13 +1,13 @@ // -// BlockDataModel.swift +// BlockResponse.swift // pochak // -// Created by Seo Cindy on 6/30/24. +// Created by Seo Cindy on 9/30/24. // import Foundation -struct BlockDataResponse: Codable { +struct BlockResponse: Codable { let isSuccess: Bool let code: String let message: String diff --git a/pochak/pochak/Network/Models/User/DeleteFollowerRequest.swift b/pochak/pochak/Network/Models/User/DeleteFollowerRequest.swift new file mode 100644 index 00000000..1c802f11 --- /dev/null +++ b/pochak/pochak/Network/Models/User/DeleteFollowerRequest.swift @@ -0,0 +1,12 @@ +// +// DeleteFollowerRequest.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation + +struct DeleteFollowerRequest: Encodable { + let followerHandle: String +} diff --git a/pochak/pochak/Network/Models/User/DeleteFollowerResponse.swift b/pochak/pochak/Network/Models/User/DeleteFollowerResponse.swift new file mode 100644 index 00000000..4a877d43 --- /dev/null +++ b/pochak/pochak/Network/Models/User/DeleteFollowerResponse.swift @@ -0,0 +1,14 @@ +// +// DeleteFollowerResponse.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation + +struct DeleteFollowerResponse: Codable { + let isSuccess: Bool + let code: String + let message: String +} diff --git a/pochak/pochak/Network/Models/User/FollowListRequest.swift b/pochak/pochak/Network/Models/User/FollowListRequest.swift new file mode 100644 index 00000000..006f5b45 --- /dev/null +++ b/pochak/pochak/Network/Models/User/FollowListRequest.swift @@ -0,0 +1,12 @@ +// +// FollowListRequest.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation + +struct FollowListRequest: Encodable { + let page: Int +} diff --git a/pochak/pochak/Network/Models/User/FollowListResponse.swift b/pochak/pochak/Network/Models/User/FollowListResponse.swift new file mode 100644 index 00000000..41d1d58d --- /dev/null +++ b/pochak/pochak/Network/Models/User/FollowListResponse.swift @@ -0,0 +1,35 @@ +// +// FollowListResponse.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation + +struct FollowListResponse: Codable { + let isSuccess: Bool + let code: String + let message: String + let result: FollowListResult +} + +struct FollowListResult: Codable { + let pageInfo: FollowListPageInfo + let memberList: [MemberListData] +} + +struct FollowListPageInfo: Codable { + let lastPage : Bool + let totalPages : Int + let totalElements : Int + let size : Int +} + +struct MemberListData: Codable { + let memberId: Int + let profileImage: String + let handle: String + let name: String + let isFollow: Bool? +} diff --git a/pochak/pochak/Network/Models/User/UnblockRequest.swift b/pochak/pochak/Network/Models/User/UnblockRequest.swift new file mode 100644 index 00000000..1330549d --- /dev/null +++ b/pochak/pochak/Network/Models/User/UnblockRequest.swift @@ -0,0 +1,12 @@ +// +// UnblockRequest.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation + +struct UnblockRequest: Encodable { + let blockedMemberHandle: String +} diff --git a/pochak/pochak/UI/Profile/OtherProfile/ProfileMenu/BlockAPI/UnblockDataModel.swift b/pochak/pochak/Network/Models/User/UnblockResponse.swift similarity index 52% rename from pochak/pochak/UI/Profile/OtherProfile/ProfileMenu/BlockAPI/UnblockDataModel.swift rename to pochak/pochak/Network/Models/User/UnblockResponse.swift index bdaa5028..45bae087 100644 --- a/pochak/pochak/UI/Profile/OtherProfile/ProfileMenu/BlockAPI/UnblockDataModel.swift +++ b/pochak/pochak/Network/Models/User/UnblockResponse.swift @@ -1,13 +1,13 @@ // -// UnblockDataModel.swift +// UnblockResponse.swift // pochak // -// Created by Seo Cindy on 6/30/24. +// Created by Seo Cindy on 9/30/24. // import Foundation -struct UnBlockDataResponse: Codable { +struct UnblockResponse: Codable { let isSuccess: Bool let code: String let message: String diff --git a/pochak/pochak/Network/NetworkResult400.swift b/pochak/pochak/Network/NetworkResult400.swift index fd13fb40..4de7ae0d 100644 --- a/pochak/pochak/Network/NetworkResult400.swift +++ b/pochak/pochak/Network/NetworkResult400.swift @@ -5,7 +5,6 @@ // Created by Seo Cindy on 7/6/24. // -enum NetworkResult400{ +enum NetworkResult400 { case success(T) // 서버 통신 성공 - case MEMBER4002 // 유효하지 않은 멤버의 handle인 경우 } diff --git a/pochak/pochak/Network/Services/AuthenticationService.swift b/pochak/pochak/Network/Services/AuthenticationService.swift new file mode 100644 index 00000000..7d61b707 --- /dev/null +++ b/pochak/pochak/Network/Services/AuthenticationService.swift @@ -0,0 +1,82 @@ +// +// AuthenticationService.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation + +struct AuthenticationService { + + /// - Parameters: + /// - request: 가입하는 유저의 name, email, handle, message, socialId, socialType 정보 + /// - files : 프로필 이미지 + /// - completion: 통신 후 핸들러 (뷰컨트롤러에 있음) + static func signUp( + request: SignUpRequest, + files: [(Data, String, String)], + completion: @escaping (_ succeed: SignUpResponse?, _ failed: NetworkError?) -> Void) { + NetworkService.shared.uploadMultipart(SignUpAPI.signUp(request), files: files) { response in + switch response { + case .success(let data): + completion(data, nil) + case .failure(let error): + print("=== signUp error ===") + print(error.localizedDescription) + completion(nil, error) + } + } + } + + /// - Parameters: + /// - completion: 통신 후 핸들러 (뷰컨트롤러에 있음) + static func signOut( + completion: @escaping (_ succeed: SignOutResponse?, _ failed: NetworkError?) -> Void) { + NetworkService.shared.request(SignOutAPI.signOut) { response in + switch response { + case .success(let data): + completion(data, nil) + case .failure(let error): + print("=== signOut error ===") + print(error.localizedDescription) + completion(nil, error) + } + } + } + + /// - Parameters: + /// - completion: 통신 후 핸들러 (뷰컨트롤러에 있음) + static func logOut( + completion: @escaping (_ succeed: LogOutResponse?, _ failed: NetworkError?) -> Void) { + NetworkService.shared.request(LogOutAPI.logOut) { response in + switch response { + case .success(let data): + completion(data, nil) + case .failure(let error): + print("=== logOut error ===") + print(error.localizedDescription) + completion(nil, error) + } + } + } + + /// 핸들 중복 검사 + /// - Parameters: + /// - request : 중복검사하고자 하는 핸들 + /// - completion: 통신 후 핸들러 (뷰컨트롤러) + static func checkDuplicateHandle( + request: CheckDuplicateHandleRequest, + completion: @escaping (_ succeed: CheckDuplicateHandleResponse?, _ failed: NetworkError?) -> Void) { + NetworkService.shared.request(CheckDuplicateHandleAPI.checkDuplicateHandle(request)) { response in + switch response { + case .success(let data): + completion(data, nil) + case .failure(let error): + print("=== checkDuplicateHandle service error ===") + print(error.localizedDescription) + completion(nil, error) + } + } + } +} diff --git a/pochak/pochak/Network/Services/ProfileService.swift b/pochak/pochak/Network/Services/ProfileService.swift new file mode 100644 index 00000000..bf2d1c52 --- /dev/null +++ b/pochak/pochak/Network/Services/ProfileService.swift @@ -0,0 +1,73 @@ +// +// ProfileService.swift +// pochak +// +// Created by Seo Cindy on 9/30/24. +// + +import Foundation + +struct ProfileService { + + /// - Parameters: + /// - handle : 조회하는 프로필 유저의 핸들 + /// - request: page 정보 + /// - completion: 통신 후 핸들러 (뷰컨트롤러에 있음) + static func getProfile( + handle: String, + request: ProfileRetrievalRequest, + completion: @escaping (_ succeed: ProfileRetrievalResponse?, _ failed: NetworkError?) -> Void) { + NetworkService.shared.request(ProfileRetrievalAPI.getProfile(handle: handle, request: request)) { response in + switch response { + case .success(let data): + completion(data, nil) + case .failure(let error): + print("=== getProfile error ===") + print(error.localizedDescription) + completion(nil, error) + } + } + } + + /// - Parameters: + /// - handle : 조회하는 프로필 유저의 핸들 + /// - request: page 정보 + /// - completion: 통신 후 핸들러 (뷰컨트롤러에 있음) + static func getProfilePochakPosts( + handle: String, + request: ProfileRetrievalRequest, + completion: @escaping (_ succeed: PochakPostRetrievalResponse?, _ failed: NetworkError?) -> Void) { + NetworkService.shared.request(PochakPostRetrievalAPI.getPochakPost(handle: handle, request: request)) { response in + switch response { + case .success(let data): + completion(data, nil) + case .failure(let error): + print("=== getProfilePochakPosts error ===") + print(error.localizedDescription) + completion(nil, error) + } + } + } + + /// - Parameters: + /// - request: 현재 로그인한 유저의 핸들 + /// - files : 수정한 프로필 이미지 + /// - request : 수정한 name, message 정보 + /// - completion: 통신 후 핸들러 (뷰컨트롤러에 있음) + static func profileUpdate( + handle: String, + files: [(Data, String, String)], + request: ProfileUpdateRequest, + completion: @escaping (_ succeed: ProfileUpdateResponse?, _ failed: NetworkError?) -> Void) { + NetworkService.shared.uploadMultipart(ProfileUpdateAPI.updateProfile(handle: handle, request: request), files: files) { response in + switch response { + case .success(let data): + completion(data, nil) + case .failure(let error): + print("=== profileUpdate error ===") + print(error.localizedDescription) + completion(nil, error) + } + } + } +} diff --git a/pochak/pochak/Network/Services/UserService.swift b/pochak/pochak/Network/Services/UserService.swift index 6563dcf6..bd91374a 100644 --- a/pochak/pochak/Network/Services/UserService.swift +++ b/pochak/pochak/Network/Services/UserService.swift @@ -27,4 +27,128 @@ struct UserService { } } } + + /// 팔로우 삭제하기 + /// - Parameters: + /// - handle: 현재 로그인한 유저의 핸들 + /// - request : 삭제하려는 팔로워의 핸들 + /// - completion: 통신 후 핸들러 (뷰컨트롤러) + static func deleteFollower( + handle: String, + request: DeleteFollowerRequest, + completion: @escaping (_ succeed: DeleteFollowerResponse?, _ failed: NetworkError?) -> Void) { + NetworkService.shared.request(DeleteFollowerAPI.deleteFollower(handle: handle, request: request)) { response in + switch response { + case .success(let data): + completion(data, nil) + case .failure(let error): + print("=== deleteFollower service error ===") + print(error.localizedDescription) + completion(nil, error) + } + } + } + + /// 팔로워 리스트 조회하기 + /// - Parameters: + /// - handle: 해당하는 핸들 유저의 팔로워 리스트 조회 + /// - request : page 정보 + /// - completion: 통신 후 핸들러 (뷰컨트롤러) + static func getFollowers( + handle: String, + request: FollowListRequest, + completion: @escaping (_ succeed: FollowListResponse?, _ failed: NetworkError?) -> Void) { + NetworkService.shared.request(FollowerRetrievalAPI.getFollowers(handle: handle, request: request)) { response in + switch response { + case .success(let data): + completion(data, nil) + case .failure(let error): + print("=== getFollowers service error ===") + print(error.localizedDescription) + completion(nil, error) + } + } + } + + /// 팔로잉 리스트 조회하기 + /// - Parameters: + /// - handle: 해당하는 핸들 유저의 팔로잉 리스트 조회 + /// - request : page 정보 + /// - completion: 통신 후 핸들러 (뷰컨트롤러) + static func getFollowings( + handle: String, + request: FollowListRequest, + completion: @escaping (_ succeed: FollowListResponse?, _ failed: NetworkError?) -> Void) { + NetworkService.shared.request(FollowingRetrievalAPI.getFollowings(handle: handle, request: request)) { response in + switch response { + case .success(let data): + completion(data, nil) + case .failure(let error): + print("=== getFollowings service error ===") + print(error.localizedDescription) + completion(nil, error) + } + } + } + + /// 유저 차단하기 + /// - Parameters: + /// - handle: 차단하고자 하는 사용자의 핸들 (아이디) + /// - completion: 통신 후 핸들러 (뷰컨트롤러) + static func blockUser( + handle: String, + completion: @escaping (_ succeed: BlockResponse?, _ failed: NetworkError?) -> Void) { + NetworkService.shared.request(BlockAPI.blockUser(handle)) { response in + switch response { + case .success(let data): + completion(data, nil) + case .failure(let error): + print("=== blockUser service error ===") + print(error.localizedDescription) + completion(nil, error) + } + } + } + + /// 유저 차단 해제하기 + /// - Parameters: + /// - handle : 현재 로그인한 유저의 핸들 + /// - request : 차단을 해제하고자 하는 사용자의 핸들 (아이디) + /// - completion: 통신 후 핸들러 (뷰컨트롤러) + static func unblockUser( + handle: String, + request: UnblockRequest, + completion: @escaping (_ succeed: UnblockResponse?, _ failed: NetworkError?) -> Void) { + NetworkService.shared.request(UnblockAPI.unblockUser(handle: handle, request: request)) { response in + switch response { + case .success(let data): + completion(data, nil) + case .failure(let error): + print("=== unblockUser service error ===") + print(error.localizedDescription) + completion(nil, error) + } + } + } + + /// 차단 유저 리스트 조회 + /// - Parameters: + /// - handle: 현재 로그인한 유저의 핸들 + /// - request : page 정보 + /// - completion: 통신 후 핸들러 (뷰컨트롤러) + static func getBlockUserList( + handle: String, + request: BlockListRequest, + completion: @escaping (_ succeed: BlockListResponse?, _ failed: NetworkError?) -> Void) { + NetworkService.shared.request(BlockListAPI.getBlockUserList(handle: handle, request: request)) { response in + switch response { + case .success(let data): + completion(data, nil) + case .failure(let error): + print("=== getBlockUserList service error ===") + print(error.localizedDescription) + completion(nil, error) + } + } + } } diff --git a/pochak/pochak/Network/UserDefaultsManager.swift b/pochak/pochak/Network/UserDefaultsManager.swift index fc4d9c07..a48e1011 100644 --- a/pochak/pochak/Network/UserDefaultsManager.swift +++ b/pochak/pochak/Network/UserDefaultsManager.swift @@ -19,8 +19,7 @@ class UserDefaultsManager { case handle case message case profileImgUrl - case followerCount - case followingCount + case refreshTokenIssuedAt } static func setData(value: T, key: UserDefaultsKeys) { diff --git a/pochak/pochak/UI/Login/AppleLogin/AppleLoginDataManager.swift b/pochak/pochak/UI/Login/AppleLogin/AppleLoginDataManager.swift index fe30c404..ac576b02 100644 --- a/pochak/pochak/UI/Login/AppleLogin/AppleLoginDataManager.swift +++ b/pochak/pochak/UI/Login/AppleLogin/AppleLoginDataManager.swift @@ -12,25 +12,20 @@ class AppleLoginDataManager { static let shared = AppleLoginDataManager() - func appleLoginDataManager(_ IdentityToken : String, _ authorizationCode : String, _ completion: @escaping (AppleLoginModel) -> Void) { + func appleLoginDataManager(_ IdentityToken: String, _ authorizationCode: String, _ completion: @escaping (AppleLoginModel) -> Void) { let url = "\(APIConstants.baseURL)/apple/login" - let header : HTTPHeaders = ["IdentityToken": IdentityToken, "AuthorizationCode" : authorizationCode] - - print(">>>>> appleLoginDataManager url == \(url)") - print(">>>>> appleLoginDataManager header == \(header)") + let header: HTTPHeaders = ["IdentityToken": IdentityToken, "AuthorizationCode": authorizationCode] + AF.request(url, method: .post, headers: header).validate().responseDecodable(of: AppleLoginResponse.self) { response in - print(response.result) - switch response.result { - case .success(let result): - let resultData = result.result - print(result) - completion(resultData) - case .failure(let error): - print("appleLoginDataManager error : \(error.localizedDescription)") - if let data = response.data, let errorMessage = String(data: data, encoding: .utf8) { - print("Failure Data: \(errorMessage)") - } - } - } + switch response.result { + case .success(let result): + let resultData = result.result + completion(resultData) + case .failure(let error): + if let data = response.data, let errorMessage = String(data: data, encoding: .utf8) { + print("Failure Data: \(errorMessage)") + } + } + } } } diff --git a/pochak/pochak/UI/Login/AppleLogin/AppleLoginDataModel.swift b/pochak/pochak/UI/Login/AppleLogin/AppleLoginDataModel.swift index e45941e6..d64df1d6 100644 --- a/pochak/pochak/UI/Login/AppleLogin/AppleLoginDataModel.swift +++ b/pochak/pochak/UI/Login/AppleLogin/AppleLoginDataModel.swift @@ -14,13 +14,13 @@ struct AppleLoginResponse: Codable { let result: AppleLoginModel } -struct AppleLoginModel : Codable { - var socialId : String? - var name : String? - var email : String? - var handle : String? - var socialType : String? - var accessToken : String? - var refreshToken : String? - var isNewMember : Bool? +struct AppleLoginModel: Codable { + let socialId: String? + let name: String? + let email: String? + let handle: String? + let socialType: String? + let accessToken: String? + let refreshToken: String? + let isNewMember: Bool? } diff --git a/pochak/pochak/UI/Login/Base.lproj/Login.storyboard b/pochak/pochak/UI/Login/Base.lproj/Login.storyboard index ccab4f0d..8d691a0e 100644 --- a/pochak/pochak/UI/Login/Base.lproj/Login.storyboard +++ b/pochak/pochak/UI/Login/Base.lproj/Login.storyboard @@ -1,9 +1,9 @@ - + - + @@ -14,9 +14,15 @@ Pretendard-Bold + + Pretendard-Light + Pretendard-Medium + + Pretendard-SemiBold + @@ -209,10 +215,10 @@ - + - + diff --git a/pochak/pochak/UI/Login/GoogleLogin/GoogleLoginDataManager.swift b/pochak/pochak/UI/Login/GoogleLogin/GoogleLoginDataManager.swift new file mode 100644 index 00000000..35d1a775 --- /dev/null +++ b/pochak/pochak/UI/Login/GoogleLogin/GoogleLoginDataManager.swift @@ -0,0 +1,29 @@ +// +// GoogleLoginDataManager.swift +// pochak +// +// Created by Seo Cindy on 12/27/23. +// + +import Alamofire + +class GoogleLoginDataManager { + + static let shared = GoogleLoginDataManager() + + func googleLoginDataManager(_ accessToken: String, _ completion: @escaping (GoogleLoginModel) -> Void) { + let url = "\(APIConstants.baseURL)/google/login/\(accessToken)" + + AF.request(url, method: .get).validate().responseDecodable(of: GoogleLoginResponse.self) { response in + switch response.result { + case .success(let result): + let resultData = result.result + completion(resultData) + case .failure(let error): + if let data = response.data, let errorMessage = String(data: data, encoding: .utf8) { + print("Failure Data: \(errorMessage)") + } + } + } + } +} diff --git a/pochak/pochak/UI/Login/GoogleLoginDataManager/GoogleLoginModel.swift b/pochak/pochak/UI/Login/GoogleLogin/GoogleLoginModel.swift similarity index 100% rename from pochak/pochak/UI/Login/GoogleLoginDataManager/GoogleLoginModel.swift rename to pochak/pochak/UI/Login/GoogleLogin/GoogleLoginModel.swift diff --git a/pochak/pochak/UI/Login/GoogleLoginDataManager/GoogleLoginDataManager.swift b/pochak/pochak/UI/Login/GoogleLoginDataManager/GoogleLoginDataManager.swift deleted file mode 100644 index 0b438d1c..00000000 --- a/pochak/pochak/UI/Login/GoogleLoginDataManager/GoogleLoginDataManager.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// GoogleLoginDataManager.swift -// pochak -// -// Created by Seo Cindy on 12/27/23. -// - -import Alamofire - -class GoogleLoginDataManager { - - static let shared = GoogleLoginDataManager() - - func googleLoginDataManager(_ accessToken : String, _ completion: @escaping (GoogleLoginModel) -> Void) { - let url = "\(APIConstants.baseURL)/google/login/\(accessToken)" - - AF.request(url, method: .get).validate().responseDecodable(of: GoogleLoginResponse.self) { response in - switch response.result { - case .success(let result): - let resultData = result.result - completion(resultData) - case .failure(let error): - print("googleLoginDataManager error : \(error)") - if let data = response.data, let errorMessage = String(data: data, encoding: .utf8) { - print("Failure Data: \(errorMessage)") - } - } - } - } -} diff --git a/pochak/pochak/UI/Login/SignUp/CheckHandle/CheckHandleDuplicationDataManager.swift b/pochak/pochak/UI/Login/SignUp/CheckHandle/CheckHandleDuplicationDataManager.swift deleted file mode 100644 index 4332829d..00000000 --- a/pochak/pochak/UI/Login/SignUp/CheckHandle/CheckHandleDuplicationDataManager.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// CheckHandleDuplicationDataManager.swift -// pochak -// -// Created by Seo Cindy on 7/3/24. -// - -import Foundation -import Alamofire - -class CheckHandleDuplicationDataManager{ - - static let shared = CheckHandleDuplicationDataManager() - - func checkHandleDuplicationDataManager(_ handle : String, _ completion: @escaping (CheckHandleDuplicationResponse) -> Void) { - let url = "\(APIConstants.baseURL)/api/v2/members/duplicate?handle=\(handle)" - - print("url ; \(url)") - AF.request(url, method: .get).validate().responseDecodable(of: CheckHandleDuplicationResponse.self) { response in - switch response.result { - case .success(let result): - let resultData = result - print(result) - completion(resultData) - case .failure(let error): - print("checkHandleDuplicationDataManager error : \(error)") - if let data = response.data, let errorMessage = String(data: data, encoding: .utf8) { - print("Failure Data: \(errorMessage)") - } - } - } - } -} - diff --git a/pochak/pochak/UI/Login/SignUp/CheckHandle/CheckHandleDuplicationDataModel.swift b/pochak/pochak/UI/Login/SignUp/CheckHandle/CheckHandleDuplicationDataModel.swift deleted file mode 100644 index 67c377be..00000000 --- a/pochak/pochak/UI/Login/SignUp/CheckHandle/CheckHandleDuplicationDataModel.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// CheckHandleDuplicationDataModel.swift -// pochak -// -// Created by Seo Cindy on 7/3/24. -// - -import Foundation - -struct CheckHandleDuplicationResponse: Codable { - let isSuccess: Bool - let code: String - let message: String -} diff --git a/pochak/pochak/UI/Login/SignUp/JoinDataManager.swift b/pochak/pochak/UI/Login/SignUp/JoinDataManager.swift deleted file mode 100644 index bd9a6fa7..00000000 --- a/pochak/pochak/UI/Login/SignUp/JoinDataManager.swift +++ /dev/null @@ -1,73 +0,0 @@ -// -// JoinDataManager.swift -// pochak -// -// Created by Seo Cindy on 12/27/23. -// - -import UIKit -import Alamofire - -struct JoinDataManager { - - static let shared = JoinDataManager() - let url = "\(APIConstants.baseURL)/api/v2/signup" - let header : HTTPHeaders = ["Content-Type": "multipart/form-data", "charset" : "UTF-8"] - - func joinDataManager(_ name : String, - _ email : String, - _ handle : String, - _ message : String, - _ socialId : String, - _ socialType : String, - _ socialRefreshToken : String, - _ profileImage : UIImage?, - _ completion: @escaping (JoinDataModel) -> Void) { - - var requestBody : [String : String] = [ - "name" : name, - "email" : email, - "handle" : handle, - "message" : message, - "socialId" : socialId, - "socialType" : socialType - ] - - if socialRefreshToken == "NOTAPPLELOGINUSER" { - } else { - requestBody.updateValue(socialRefreshToken, forKey: "socialRefreshToken") - } - - print("requestBody : \(requestBody)") - print("join url : \(url)") - - - AF.upload(multipartFormData: { multipartFormData in - // requestBody 추가 - for (key, value) in requestBody { - if let valueData = value.data(using: .utf8) { - multipartFormData.append(valueData, withName: key) - } - } - // profileImage 추가 - if let image = profileImage?.jpegData(compressionQuality: 0.2) { - multipartFormData.append(image, withName: "profileImage", fileName: "profileImage.jpg", mimeType: "image/jpeg") - print("image : \(image)") - } - - }, to: url, method: .post, headers: header).validate().responseDecodable(of: JoinAPIResponse.self) { response in - print("joindataManager respose: \(response)") - print("joindataManager response result: \(response.result)") - switch response.result { - case .success(let result): - let resultData = result.result - completion(resultData) - case .failure(let error): - print("joindataManager error : \(error.localizedDescription)") - if let data = response.data, let errorMessage = String(data: data, encoding: .utf8) { - print("Failure Data: \(errorMessage)") - } - } - } - } -} diff --git a/pochak/pochak/UI/Login/SignUp/MakeProfileViewController.swift b/pochak/pochak/UI/Login/SignUpViewController.swift similarity index 57% rename from pochak/pochak/UI/Login/SignUp/MakeProfileViewController.swift rename to pochak/pochak/UI/Login/SignUpViewController.swift index 24e04171..438f493b 100644 --- a/pochak/pochak/UI/Login/SignUp/MakeProfileViewController.swift +++ b/pochak/pochak/UI/Login/SignUpViewController.swift @@ -1,5 +1,5 @@ // -// MakeProfileViewController.swift +// SignUpViewController.swift // pochak // // Created by Seo Cindy on 2023/08/14. @@ -7,16 +7,20 @@ import UIKit -class MakeProfileViewController: UIViewController { +final class SignUpViewController: UIViewController { - // MARK: - Data - let textViewPlaceHolder = "소개를 입력해주세요.\n(최대 50자, 3줄)" - let email = UserDefaultsManager.getData(type: String.self, forKey: .email) ?? "email not found" - let socialType = UserDefaultsManager.getData(type: String.self, forKey: .socialType) ?? "socialType not found" - let socialId = UserDefaultsManager.getData(type: String.self, forKey: .socialId) ?? "socialId not found" - let socialRefreshToken = UserDefaultsManager.getData(type: String.self, forKey: .socialRefreshToken) ?? "NOTAPPLELOGINUSER" - var backBtnPressed : Bool = false - var handleDuplicationChecked : Bool = false + // MARK: - Properties + + private var backBtnPressed: Bool = false + private var handleDuplicationChecked: Bool = false + private let textViewPlaceHolder = "소개를 입력해주세요.\n(최대 50자, 3줄)" + private let email = UserDefaultsManager.getData(type: String.self, forKey: .email) ?? "email not found" + private let socialType = UserDefaultsManager.getData(type: String.self, forKey: .socialType) ?? "socialType not found" + private let socialId = UserDefaultsManager.getData(type: String.self, forKey: .socialId) ?? "socialId not found" + private let socialRefreshToken = UserDefaultsManager.getData(type: String.self, forKey: .socialRefreshToken) ?? "NOTAPPLELOGINUSER" + private let imagePickerController = UIImagePickerController() + + // MARK: - Views @IBOutlet weak var profileImg: UIButton! @IBOutlet weak var nameTextField: UITextField! @@ -24,60 +28,58 @@ class MakeProfileViewController: UIViewController { @IBOutlet weak var messageTextView: UITextView! @IBOutlet weak var checkHandleDuplicationBtn: UIButton! - // MARK: - View LifeCycle + // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() + setUpViewController() + setUpNavigationBar() + } + + // MARK: - Actions + + @IBAction func checkHandleDuplication(_ sender: Any) { + guard let handle = handleTextField.text else { return } + let request = CheckDuplicateHandleRequest(handle: handle) - // UINavigationController 스와이프해서 뒤로가기 막기 - self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false - - // 네비게이션바 완료 버튼 커스텀 - let button = UIButton() - button.setTitle("완료", for: .normal) - button.setTitleColor(UIColor(named: "yellow00"), for: .normal) - button.titleLabel?.font = UIFont(name: "Pretendard-Bold", size: 16) - button.addTarget(self, action: #selector(doneBtnTapped), for: .touchUpInside) - let barButton = UIBarButtonItem(customView: button) - self.navigationItem.rightBarButtonItem = barButton - - // 네비게이션바 title 커스텀 - self.navigationController?.navigationBar.tintColor = .black - self.navigationItem.title = "프로필 설정" - self.navigationController?.navigationBar.titleTextAttributes = [ NSAttributedString.Key.foregroundColor : UIColor.black, NSAttributedString.Key.font : UIFont(name: "Pretendard-Bold", size: 20) ?? UIFont.systemFont(ofSize: 20, weight: .bold)] - - // 네비게이션바 Back 버튼 커스텀 - let backBarButtonItem = UIBarButtonItem(image: UIImage(named: "ChevronLeft")?.withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector(backbuttonPressed)) - backBarButtonItem.imageInsets = UIEdgeInsets(top: 0, left: 2, bottom: 0, right: 0) - self.navigationItem.leftBarButtonItem = backBarButtonItem - // self.navigationItem.backBarButtonItem = UIBarButtonItem(title: nil, style: .plain, target: nil, action: nil) - - - // 프로필 image 레이아웃 - profileImg.setImage(UIImage(named: "chooseProfileIcon"), for: .normal) - profileImg.imageView?.contentMode = .scaleAspectFill - profileImg.layer.masksToBounds = true - profileImg.layer.cornerRadius = 58 - - // textView 레이아웃 설정 - messageTextView.delegate = self - messageTextView.textContainer.lineFragmentPadding = 0 // textView 기본 마진 제거 - messageTextView.textContainerInset = .zero // textView 기본 마진 제거 - messageTextView.text = textViewPlaceHolder // PlaceHolder 커스텀 - messageTextView.textColor = UIColor(named: "gray03") // PlaceHolder 커스텀 - - // 중복확인 버튼 기본 이미지 세팅 - checkHandleDuplicationBtn.setImage(UIImage(named: "checkHandle"), for: .normal) - - // 핸들 입력 중이면 중복확인 버튼 및 텍스트필드 글자 색 세팅 원래대로 변경 -// handleTextField.placeholder = "아이디를 입력해주세요.\n (대문자, 소문자" - handleTextField.addTarget(self, action: #selector(self.textFieldDidChange(_:)), for: .editingChanged) - handleTextField.delegate = self + AuthenticationService.checkDuplicateHandle(request: request) { [weak self] data, failed in + guard let data = data else { return } + + let code = data.code + let memberCode = MemberCode(rawValue: code) + + switch memberCode { + case .success: + self?.checkHandleDuplicationBtn.setImage(UIImage(named: "checkedHandle"), for: .normal) + self?.handleTextField.textColor = UIColor(named: "yellow00") + self?.checkHandleDuplicationBtn.isEnabled = false + self?.handleDuplicationChecked = true + case .duplicationError: + self?.showAlert(alertType: .confirmOnly, + titleText: "중복된 아이디입니다", + messageText: "다른 아이디를 입력해주세요.", + cancelButtonText: "", + confirmButtonText: "확인") + case .unknown: + print("Unknown code: \(code)") + } + } } - // MARK: - Function + // 프로필 사진 설정 + /* + 1. 권한 설정: Info.plist > Photo Library Usage 권한 추가 + 2. UIImagePickerController 선언 + 3. @IBAction 정의 + 4. 프로토콜 채택 + */ + @IBAction func profileBtnTapped(_ sender: Any) { + self.imagePickerController.delegate = self + self.imagePickerController.sourceType = .photoLibrary + present(self.imagePickerController, animated: true, completion: nil) + } - @objc private func backbuttonPressed(_ sender: Any) {// 뒤로가기 버튼 클릭시 어디로 이동할지 + @objc private func backbuttonPressed(_ sender: Any) { backBtnPressed = true showAlert(alertType: .confirmAndCancel, titleText: "프로필 설정을 취소하고\n페이지를 나갈까요?", @@ -87,50 +89,22 @@ class MakeProfileViewController: UIViewController { ) } - @objc func textFieldDidChange(_ sender: Any?) { + @objc private func textFieldDidChange(_ sender: Any?) { handleDuplicationChecked = false checkHandleDuplicationBtn.isEnabled = true handleTextField.textColor = .black checkHandleDuplicationBtn.setImage(UIImage(named: "checkHandle"), for: .normal) } - - @IBAction func checkHandleDuplication(_ sender: Any) { - guard let handle = handleTextField.text else {return} - print(">>>>> checkHandleDuplication handle : \(handle)") - - // API request : Get - CheckHandleDuplicationDataManager.shared.checkHandleDuplicationDataManager(handle) { resultData in - if resultData.code == "MEMBER2001" { - // 버튼 변경 - self.checkHandleDuplicationBtn.setImage(UIImage(named: "checkedHandle"), for: .normal) - self.handleTextField.textColor = UIColor(named: "yellow00") - // 버튼 disable 시키기 - self.checkHandleDuplicationBtn.isEnabled = false - self.handleDuplicationChecked = true - } else if resultData.code == "MEMBER4002" { - // Alert 창 - self.showAlert(alertType: .confirmOnly, - titleText: "중복된 아이디입니다", - messageText: "다른 아이디를 입력해주세요.", - cancelButtonText: "", - confirmButtonText: "확인" - ) - } - } - } - @objc private func doneBtnTapped(_ sender: Any) { + guard let name = nameTextField.text else { return } + guard let handle = handleTextField.text else { return } + guard let message = messageTextView.text else { return } + guard let profileImage = profileImg.currentImage else { return } + let profileImageData: Data? = profileImg.currentImage?.jpegData(compressionQuality: 0.2) - // 새로운 유저 정보 UserDefaults에 저장 : name / handle / message - guard let name = nameTextField.text else {return} - guard let handle = handleTextField.text else {return} - guard let message = messageTextView.text else {return} - guard let profileImage = profileImg.currentImage else {return} - - if (name == "" || handle == "" || message == textViewPlaceHolder || message == "" || profileImage == UIImage(named: "chooseProfileIcon")){ - print("true!") - // Alert창 + if (name == "" || handle == "" || message == textViewPlaceHolder || message == "" + || profileImage == UIImage(named: "chooseProfileIcon")) { showAlert(alertType: .confirmOnly, titleText: "프로필 정보를 모두 입력해주세요.", messageText: "", @@ -145,83 +119,116 @@ class MakeProfileViewController: UIViewController { confirmButtonText: "확인") return } else { - // API request : POST - JoinDataManager.shared.joinDataManager(name, - email, - handle, - message, - socialId, - socialType, - socialRefreshToken, - profileImage, - {resultData in - - print("JoinDataManager resultData : \(resultData)") + let request = SignUpRequest(name: name, + email: email, + handle: handle, + message: message, + socialId: socialId, + socialType: socialType) + + var files: [(Data, String, String)] = [] + if let profileImage = profileImageData { + let fileTuple: (Data, String, String) = (profileImage, "profileImage", "image/jpeg") + files.append(fileTuple) + } + + AuthenticationService.signUp(request: request, files: files) { [weak self] data, failed in + guard let data = data else { return } - // 새로운 유저 정보 UserDefaults에 저장 : id / name / handle / message / IsNewMember - UserDefaultsManager.setData(value: resultData.name, key: .name) - UserDefaultsManager.setData(value: resultData.id, key: .memberId) + // 새로운 유저 정보 UserDefaults에 저장: id, name, handle, message, isNewMember + UserDefaultsManager.setData(value: data.result.name, key: .name) + UserDefaultsManager.setData(value: data.result.id, key: .memberId) UserDefaultsManager.setData(value: handle, key: .handle) UserDefaultsManager.setData(value: message, key: .message) - UserDefaultsManager.setData(value: resultData.isNewMember, key: .isNewMember) - - // 유저 토큰 정보 저장 @KeyChainManager - guard let accountAccessToken = resultData.accessToken else { return } - guard let accountRefreshToken = resultData.refreshToken else { return } - print("JoinDataManager accountAccessToken = \(accountAccessToken)") - print("JoinDataManager accountRefreshToken = \(accountRefreshToken)") + UserDefaultsManager.setData(value: data.result.isNewMember, key: .isNewMember) + // 유저 토큰 정보 KeyChainManager에 저장 + guard let accountAccessToken = data.result.accessToken else { return } + guard let accountRefreshToken = data.result.refreshToken else { return } do { try KeychainManager.save(account: "accessToken", value: accountAccessToken, isForce: true) try KeychainManager.save(account: "refreshToken", value: accountRefreshToken, isForce: true) } catch { print(error) } - - // 회원가입 성공 시 홈 화면으로 전환 - self.toHomeTabPage() - }) + self?.saveRefreshTokenIssuedAt() + self?.toHomeTabPage() + } } - } - // 프로필 사진 설정 - /* - 1. 권한 설정 : Info.plist > Photo Library Usage 권한 추가 - 2. UIImagePickerController 선언 - 3. @IBAction 정의 - 4. 프로토콜 채택 - */ - let imagePickerController = UIImagePickerController() - @IBAction func profileBtnTapped(_ sender: Any) { - self.imagePickerController.delegate = self - self.imagePickerController.sourceType = .photoLibrary - present(self.imagePickerController, animated: true, completion: nil) + // MARK: - Functions + + private func setUpViewController() { + // 프로필 image 레이아웃 + profileImg.setImage(UIImage(named: "chooseProfileIcon"), for: .normal) + profileImg.imageView?.contentMode = .scaleAspectFill + profileImg.layer.masksToBounds = true + profileImg.layer.cornerRadius = 58 + + // textView 레이아웃 설정 + messageTextView.delegate = self + messageTextView.textContainer.lineFragmentPadding = 0 // textView 기본 마진 제거 + messageTextView.textContainerInset = .zero // textView 기본 마진 제거 + messageTextView.text = textViewPlaceHolder // PlaceHolder 커스텀 + messageTextView.textColor = UIColor(named: "gray03") // PlaceHolder 커스텀 + + // 중복확인 버튼 기본 이미지 세팅 + checkHandleDuplicationBtn.setImage(UIImage(named: "checkHandle"), for: .normal) + + // 핸들 입력 중이면 중복확인 버튼 및 텍스트필드 글자 색 세팅 원래대로 변경 + handleTextField.addTarget(self, action: #selector(self.textFieldDidChange(_:)), for: .editingChanged) + handleTextField.delegate = self } - private func toHomeTabPage(){ + private func setUpNavigationBar() { + self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false - let tabBarController = CustomTabBarController() + // 네비게이션바 완료 버튼 커스텀 + let button = UIButton() + button.setTitle("완료", for: .normal) + button.setTitleColor(UIColor(named: "yellow00"), for: .normal) + button.titleLabel?.font = UIFont(name: "Pretendard-Bold", size: 16) + button.addTarget(self, action: #selector(doneBtnTapped), for: .touchUpInside) + let barButton = UIBarButtonItem(customView: button) + self.navigationItem.rightBarButtonItem = barButton + // 네비게이션바 title 커스텀 + self.navigationController?.navigationBar.tintColor = .black + self.navigationItem.title = "프로필 설정" + self.navigationController?.navigationBar.titleTextAttributes = [ NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font: UIFont(name: "Pretendard-Bold", size: 20) ?? UIFont.systemFont(ofSize: 20, weight: .bold)] + + // 네비게이션바 Back 버튼 커스텀 + let backBarButtonItem = UIBarButtonItem(image: UIImage(named: "ChevronLeft")?.withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector(backbuttonPressed)) + backBarButtonItem.imageInsets = UIEdgeInsets(top: 0, left: 2, bottom: 0, right: 0) + self.navigationItem.leftBarButtonItem = backBarButtonItem + } + + private func toHomeTabPage() { + let tabBarController = CustomTabBarController() let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate - guard let delegate = sceneDelegate else { - return - } + guard let delegate = sceneDelegate else { return } delegate.window?.rootViewController = tabBarController } + + private func saveRefreshTokenIssuedAt() { + let issuedAt = Date() // 현재 시간 저장 + UserDefaultsManager.setData(value: issuedAt, key: .refreshTokenIssuedAt) + } } -// MARK: - Extension +// MARK: - Extension: UIImagePickerControllerDelegate, UINavigationControllerDelegate // 앨범 사진 선택 프로토콜 채택 -extension MakeProfileViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { +extension SignUpViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { // 선택한 사진 사용 - func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + func imagePickerController(_ picker: UIImagePickerController, + didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage { profileImg.setImage(image, for: .normal) } - picker.dismiss(animated: true, completion: nil) // 주의점 : picker 숨기기 위한 dismiss를 직접 해야함 + picker.dismiss(animated: true, completion: nil) // 주의점: picker 숨기기 위한 dismiss를 직접 해야함 } // 취소 @@ -230,8 +237,9 @@ extension MakeProfileViewController: UIImagePickerControllerDelegate, UINavigati } } -// TextView 기본 속성 설정 -extension MakeProfileViewController: UITextViewDelegate { +// MARK: - Extension: UITextViewDelegate, UITextFieldDelegate + +extension SignUpViewController: UITextViewDelegate { func textViewDidBeginEditing(_ textView: UITextView) { if textView.text == textViewPlaceHolder { @@ -239,7 +247,7 @@ extension MakeProfileViewController: UITextViewDelegate { textView.textColor = .black } } - + func textViewDidEndEditing(_ textView: UITextView) { if textView.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { textView.text = textViewPlaceHolder @@ -250,7 +258,7 @@ extension MakeProfileViewController: UITextViewDelegate { // 최대 글자 수 50자 제한 func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { let currentText = textView.text ?? "" - guard let stringRange = Range(range, in: currentText) else {return false} + guard let stringRange = Range(range, in: currentText) else { return false } let changedText = currentText.replacingCharacters(in: stringRange, with: text) return changedText.count <= 50 } @@ -259,52 +267,35 @@ extension MakeProfileViewController: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { guard let text = textView.text else { return } -// // 글자수 제한 -// let maxLength = 50 -// if text.count > maxLength { -//// textView.text = String(text.prefix(maxLength)) -// textView.text.removeLast() -// } -// // 줄바꿈(들여쓰기) 제한 let maxNumberOfLines = 3 let lineBreakCharacter = "\n" let lines = text.components(separatedBy: lineBreakCharacter) var consecutiveLineBreakCount = 0 // 연속된 줄 바꿈 횟수 - + print("lines == \(lines)") - for line in lines { -// if line.isEmpty { // 빈 줄이면 연속된 줄 바꿈으로 간주 - consecutiveLineBreakCount += 1 -// } -// } else { -// consecutiveLineBreakCount = 0 -// } - - + for _ in lines { + consecutiveLineBreakCount += 1 if consecutiveLineBreakCount > maxNumberOfLines { textView.text = String(text.dropLast()) // 마지막 입력 문자를 제거 - -// textView.text.removeLast() break } } } } -extension MakeProfileViewController : UITextFieldDelegate { +extension SignUpViewController: UITextFieldDelegate { func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { let utf8Char = string.cString(using: .utf8) let isBackSpace = strcmp(utf8Char, "\\b") - if string.hasCharacters() || isBackSpace == -92{ - return true - } + if string.hasCharacters() || isBackSpace == -92 { return true } return false } } -// Alert 창 -extension MakeProfileViewController : CustomAlertDelegate { +// MARK: - Extension: CustomAlertDelegate + +extension SignUpViewController: CustomAlertDelegate { func cancel() { if backBtnPressed { self.navigationController?.popViewController(animated: true) @@ -313,21 +304,24 @@ extension MakeProfileViewController : CustomAlertDelegate { } } - func confirmAction() { print("confirmed") } } -// 아이디 허용 가능한 문자 : 대문자, 소문자, 숫자, _(언더바), .(마침표) +// MARK: - Extension: String + +// 아이디 허용 가능한 문자 제한: 대문자, 소문자, 숫자, _(언더바), .(마침표) extension String { - func hasCharacters() -> Bool{ - do{ + func hasCharacters() -> Bool { + do { let regex = try NSRegularExpression(pattern: "^[0-9a-zA-Z_.]$", options: .caseInsensitive) - if let _ = regex.firstMatch(in: self, options: NSRegularExpression.MatchingOptions.reportCompletion, range: NSMakeRange(0, self.count)){ + if let _ = regex.firstMatch(in: self, + options: NSRegularExpression.MatchingOptions.reportCompletion, + range: NSMakeRange(0, self.count)) { return true } - }catch{ + } catch { print(error.localizedDescription) return false } diff --git a/pochak/pochak/UI/Login/SocialJoinViewController.swift b/pochak/pochak/UI/Login/SocialJoinViewController.swift index 472a23fa..04b9618d 100644 --- a/pochak/pochak/UI/Login/SocialJoinViewController.swift +++ b/pochak/pochak/UI/Login/SocialJoinViewController.swift @@ -10,50 +10,25 @@ import GoogleSignIn import AuthenticationServices // 애플 로그인 protocol SendDelegate { - func sendAgreed(agree : Bool) + func sendAgreed(agree: Bool) } -class SocialJoinViewController: UIViewController, SendDelegate { - func sendAgreed(agree: Bool) { - if agree { - guard let makeProfileVC = self.storyboard?.instantiateViewController(withIdentifier: "MakeProfileVC") as? MakeProfileViewController else {return} - self.navigationController?.pushViewController(makeProfileVC, animated: true) - } else { - print("not agreed yet!") - } - } +final class SocialJoinViewController: UIViewController { - - // MARK: - Data + // MARK: - Views @IBOutlet weak var startPochak: UILabel! @IBOutlet weak var googleLoginBtn: UIButton! @IBOutlet weak var appleLoginBtn: UIButton! + // MARK: - Lifecycle - // MARK: - View LifeCycle override func viewDidLoad() { super.viewDidLoad() - - // Title - // let attrs : [NSAttributedString.Key : Any] = [ - // NSAttributedString.Key.foregroundColor: UIColor.black, - // NSAttributedString.Key.font: UIFont(name: "Pretendard-Bold", size: 20)! - //// ] - // - // UINavigationBar.appearance().titleTextAttributes = attrs - // self.navigationController?.navigationBar.titleTextAttributes = attrs - - // 네비게이션 바 Back Button 커스텀 - /// 주의점 : backBarButtonItem 사용 시 FirstViewController에서 지정! -// let backBarButtonItem = UIBarButtonItem(title: nil, style: .plain, target: nil, action: nil) -// backBarButtonItem.tintColor = .black -// self.navigationItem.backBarButtonItem = backBarButtonItem - - // 로그인 버튼 디자인 커스텀 btnLayout() } - override func viewWillAppear(_ animated: Bool){ + + override func viewWillAppear(_ animated: Bool) { // 뷰가 나타날 때에는 네비게이션 바 숨기기 self.navigationController?.setNavigationBarHidden(true, animated: true) } @@ -63,7 +38,7 @@ class SocialJoinViewController: UIViewController, SendDelegate { self.navigationController?.setNavigationBarHidden(false, animated: true) } - // MARK: - Google Login + // MARK: - Actions @IBAction func googleLoginAction(_ sender: Any) { GIDSignIn.sharedInstance.signIn(withPresenting: self) { signInResult, error in @@ -73,15 +48,11 @@ class SocialJoinViewController: UIViewController, SendDelegate { // Get User Info let user = signInResult.user let accessToken = user.accessToken.tokenString - print("googleLogin 시스템 안의 accessToken : \(accessToken)") - // 로딩모달 self.showProgressBar() - - GoogleLoginDataManager.shared.googleLoginDataManager(accessToken, {resultData in - - print("GoogleLoginDataManager 안의 resultData : \(resultData)") - // 사용자 기본 데이터 저장 : socialId / email / socialType + + GoogleLoginDataManager.shared.googleLoginDataManager(accessToken, { resultData in + // 사용자 기본 데이터 저장: socialId, email, socialType UserDefaultsManager.setData(value: resultData.socialId, key: .socialId) UserDefaultsManager.setData(value: resultData.email, key: .email) UserDefaultsManager.setData(value: resultData.socialType, key: .socialType) @@ -89,14 +60,11 @@ class SocialJoinViewController: UIViewController, SendDelegate { guard let isNewMember = resultData.isNewMember else { return } self.changeViewControllerAccordingToisNewMemeberStateForGoogle(isNewMember, resultData) - // 로딩 숨김 self.hideProgressBar() }) } } - // MARK: - Apple Login - @IBAction func appleLoginAction(_ sender: Any) { let appleIDProvider = ASAuthorizationAppleIDProvider() let request = appleIDProvider.createRequest() @@ -108,23 +76,22 @@ class SocialJoinViewController: UIViewController, SendDelegate { authorizationController.performRequests() } - // MARK: - Function + // MARK: - Functions - private func btnLayout(){ + private func btnLayout() { googleLoginBtn.layer.cornerRadius = 30 googleLoginBtn.layer.borderWidth = 1 googleLoginBtn.layer.borderColor = UIColor(named: "gray02")?.cgColor appleLoginBtn.layer.cornerRadius = 30 } - private func changeViewControllerAccordingToisNewMemeberStateForGoogle(_ isNewMember : Bool, _ resultDataForGoogle : GoogleLoginModel){ + private func changeViewControllerAccordingToisNewMemeberStateForGoogle(_ isNewMember: Bool, _ resultDataForGoogle: GoogleLoginModel) { if isNewMember == true { - guard let termsOfAgreeVC = self.storyboard?.instantiateViewController(withIdentifier: "TermsOfAgreeVC") as? TermsOfAgreeViewController else {return} - termsOfAgreeVC.modalPresentationStyle = .overCurrentContext // 투명도가 있으면 투명도에 맞춰서 나오게 해주는 코드(뒤에있는 배경이 보일 수 있게) + guard let termsOfAgreeVC = self.storyboard?.instantiateViewController(withIdentifier: "TermsOfAgreeVC") as? TermsOfAgreeViewController else { return } + termsOfAgreeVC.modalPresentationStyle = .overCurrentContext // 투명도가 있으면 투명도에 맞춰서 나오게 해주는 코드(뒤에있는 배경이 보일 수 있게) termsOfAgreeVC.delegate = self self.present(termsOfAgreeVC, animated: false, completion: nil) } else { - // 이미 회원인 유저의 경우 토큰 정보 저장 @KeyChainManager guard let accountAccessToken = resultDataForGoogle.accessToken else { return } guard let accountRefreshToken = resultDataForGoogle.refreshToken else { return } do { @@ -133,23 +100,18 @@ class SocialJoinViewController: UIViewController, SendDelegate { } catch { print(error) } - UserDefaultsManager.setData(value: resultDataForGoogle.handle, key: .handle) - // 홈탭으로 이동 toHomeTabPage() } } - private func changeViewControllerAccordingToisNewMemeberStateForApple(_ isNewMember : Bool, _ resultDataForApple : AppleLoginModel){ + private func changeViewControllerAccordingToisNewMemeberStateForApple(_ isNewMember: Bool, _ resultDataForApple: AppleLoginModel) { if isNewMember == true { - print("inside changeVCForApple") - // 프로필 설정 페이지로 이동 - guard let termsOfAgreeVC = self.storyboard?.instantiateViewController(withIdentifier: "TermsOfAgreeVC") as? TermsOfAgreeViewController else {return} - termsOfAgreeVC.modalPresentationStyle = .overCurrentContext // 투명도가 있으면 투명도에 맞춰서 나오게 해주는 코드(뒤에있는 배경이 보일 수 있게) + guard let termsOfAgreeVC = self.storyboard?.instantiateViewController(withIdentifier: "TermsOfAgreeVC") as? TermsOfAgreeViewController else { return } + termsOfAgreeVC.modalPresentationStyle = .overCurrentContext termsOfAgreeVC.delegate = self self.present(termsOfAgreeVC, animated: false, completion: nil) } else { - // 이미 회원인 유저의 경우 토큰 정보 저장 @KeyChainManager guard let accountAccessToken = resultDataForApple.accessToken else { return } guard let accountRefreshToken = resultDataForApple.refreshToken else { return } do { @@ -159,31 +121,28 @@ class SocialJoinViewController: UIViewController, SendDelegate { print(error) } UserDefaultsManager.setData(value: resultDataForApple.handle, key: .handle) - // 홈탭으로 이동 toHomeTabPage() } } - private func toHomeTabPage(){ - + private func toHomeTabPage() { let tabBarController = CustomTabBarController() - let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate - guard let delegate = sceneDelegate else { - return - } + guard let delegate = sceneDelegate else { return } delegate.window?.rootViewController = tabBarController } } -// MARK: - Apple Login Extension -extension SocialJoinViewController: ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding{ - func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { +// MARK: - Extension: ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding + +extension SocialJoinViewController: ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding { + + 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: // You can create an account in your system. @@ -192,22 +151,16 @@ extension SocialJoinViewController: ASAuthorizationControllerDelegate, ASAuthori let email = appleIDCredential.email - if let authorizationCode = appleIDCredential.authorizationCode, - let identityToken = appleIDCredential.identityToken, - let authCodeString = String(data: authorizationCode, encoding: .utf8), - let identifyTokenString = String(data: identityToken, encoding: .utf8) { - print("authorizationCode: \(authorizationCode)") - print("identityToken: \(identityToken)") - print("authCodeString: \(authCodeString)") - print("identifyTokenString: \(identifyTokenString)") + if let authorizationCode = appleIDCredential.authorizationCode, + let identityToken = appleIDCredential.identityToken, + let authCodeString = String(data: authorizationCode, encoding: .utf8), + let identifyTokenString = String(data: identityToken, encoding: .utf8) { - // 로딩모달 self.showProgressBar() - // API request : POST - AppleLoginDataManager.shared.appleLoginDataManager(identifyTokenString, authCodeString, {resultData in - - // 사용자 기본 데이터 저장 : id / email / socialType / isNewMember + // API request: POST + AppleLoginDataManager.shared.appleLoginDataManager(identifyTokenString, authCodeString, { resultData in + // 사용자 기본 데이터 저장: id, email, socialType, isNewMember UserDefaultsManager.setData(value: resultData.socialId, key: .socialId) UserDefaultsManager.setData(value: resultData.email, key: .email) UserDefaultsManager.setData(value: resultData.socialType, key: .socialType) @@ -215,28 +168,34 @@ extension SocialJoinViewController: ASAuthorizationControllerDelegate, ASAuthori guard let isNewMember = resultData.isNewMember else { return } self.changeViewControllerAccordingToisNewMemeberStateForApple(isNewMember, resultData) - // 로딩 숨김 self.hideProgressBar() }) - } - case let passwordCredential as ASPasswordCredential: // Sign in using an existing iCloud Keychain credential. let username = passwordCredential.user let password = passwordCredential.password - - print("username: \(username)") - print("password: \(password)") - default: break } } - func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { - // 로그인 실패(유저의 취소도 포함) + // 로그인 실패 시 (유저의 취소도 포함) print("login failed - \(error.localizedDescription)") } } + +// MARK: - Extension: SendDelegate + +extension SocialJoinViewController: SendDelegate { + func sendAgreed(agree: Bool) { + if agree { + guard let signUpVC = self.storyboard?.instantiateViewController(withIdentifier: "SignUpVC") + as? SignUpViewController else { return } + self.navigationController?.pushViewController(signUpVC, animated: true) + } else { + print("not agreed yet") + } + } +} diff --git a/pochak/pochak/UI/Login/SocialLoginViewController.swift b/pochak/pochak/UI/Login/SocialLoginViewController.swift deleted file mode 100644 index ce73e457..00000000 --- a/pochak/pochak/UI/Login/SocialLoginViewController.swift +++ /dev/null @@ -1,128 +0,0 @@ -//// -//// SocialLoginViewController.swift -//// pochak -//// -//// Created by Seo Cindy on 2023/08/15. -//// -// -//import UIKit -//import GoogleSignIn -// -//class SocialLoginViewController: UIViewController { -// -// -// @IBOutlet weak var googleLoginBtn: UIButton! -// @IBOutlet weak var appleLoginBtn: UIButton! -// @IBOutlet weak var goToJoinBtn: UIButton! -// -// override func viewDidLoad() { -// super.viewDidLoad() -// btnLayout() -// } -// -// // MARK: - Button Design -// private func btnLayout(){ -//// 소셜 로그인 버튼 디자인 -// googleLoginBtn.layer.cornerRadius = 30 -// ///테두리 두께 설정해야 함! -// googleLoginBtn.layer.borderWidth = 1 -// googleLoginBtn.layer.borderColor = UIColor(named: "gray02")?.cgColor -// appleLoginBtn.layer.cornerRadius = 30 -// -// -// // 아직 계정이 없으신가요? -// // String Custom : 이미 계정이 있으신가요? -// if let font = UIFont(name: "Pretendard-Bold", size: 16) { -// let customAttributes: [NSAttributedString.Key: Any] = [ -// .font: font, -// .foregroundColor: UIColor.black, -// .underlineStyle: 1 -// ] -// let attributedString = NSAttributedString(string: "아직 계정이 없으신가요?", attributes: customAttributes) -// goToJoinBtn.setAttributedTitle(attributedString, for: .normal) -// } else {return} -// -// // Hide Back Button -// self.navigationItem.setHidesBackButton(true, animated: true) -// -// } -// -// // MARK: - Google Login -// @IBAction func googleLoginAction(_ sender: Any) { -// GIDSignIn.sharedInstance.signIn(withPresenting: self) { signInResult, error in -// guard error == nil else { return } -// guard let signInResult = signInResult else { return } -// -// // Get User Info -// let user = signInResult.user -// let accessToken = user.accessToken.tokenString -// GoogleLoginDataManager.shared.googleLoginDataManager(accessToken, {resultData in -// guard let isNewMember = resultData.isNewMember else { return } -// guard let name = resultData.name else { return } -// guard let email = resultData.email else { return } -// guard let socialType = resultData.socialType else { return } -// guard let socialId = resultData.id else { return } -// -// // 사용자 기본 데이터 저장 -// UserDefaultsManager.setData(value: name, key: .name) -// UserDefaultsManager.setData(value: socialId, key: .socialId) -// UserDefaultsManager.setData(value: email, key: .email) -// UserDefaultsManager.setData(value: socialType, key: .socialType) -// UserDefaultsManager.setData(value: isNewMember, key: .isNewMember) -// print("This is google LOGIN~~~~~~~~~~~") -// print(isNewMember) -// -// self.changeViewControllerAccordingToisNewMemeberState(isNewMember, resultData) -// }) -// } -// } -// -// // MARK: - profileSettingPage or HomeTabPage로 전환하기 -// private func changeViewControllerAccordingToisNewMemeberState(_ isNewMember : Bool, _ resultData : GoogleLoginModel){ -// if isNewMember == true { -// // 알람! : 회원정보가 없습니다 회원가입하시겠습니까? -// let alert = UIAlertController(title:"이런..회원정보가 없어요! 회원가입하시겠습니까?",message: "",preferredStyle: UIAlertController.Style.alert) -// let cancle = UIAlertAction(title: "취소", style: .destructive, handler: nil) -// let ok = UIAlertAction(title: "확인", style: .default, handler: { -// action in -// self.toMakeProfilePage() -// }) -// alert.addAction(cancle) -// alert.addAction(ok) -// present(alert,animated: true,completion: nil) -// } else if isNewMember == false{ -// // 토큰 정보 저장 @KeyChainManager -// guard let accountAccessToken = resultData.accessToken else { return } -// guard let accountRefreshToken = resultData.refreshToken else { return } -// print("Login Again!!!!!") -// print(accountAccessToken) -// print(accountRefreshToken) -// do { -// try KeychainManager.save(account: "accessToken", value: accountAccessToken, isForce: false) -// try KeychainManager.save(account: "refreshToken", value: accountRefreshToken, isForce: false) -// } catch { -// print(error) -// } -// // 홈탭으로 이동 -// toHomeTabPage() -// } -// } -// -// private func toHomeTabPage(){ -// guard let tabbarVC = self.storyboard?.instantiateViewController(withIdentifier: "TabbarVC") as? CustomTabBarController else { return } -// (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.changeRootViewController(tabbarVC, animated: false) -// } -// -// private func toMakeProfilePage(){ -// guard let makeProfileVC = self.storyboard?.instantiateViewController(withIdentifier: "MakeProfileVC") as? MakeProfileViewController else {return} -// self.navigationController?.pushViewController(makeProfileVC, animated: true) -// } -// -// // MARK: - Back Button Action -// // PopVC -// @IBAction func goToJoinBtnTapped(_ sender: UIButton) { -// navigationController?.popViewController(animated: true) -// } -// -// -//} diff --git a/pochak/pochak/UI/Login/TermsOfAgreeViewController.swift b/pochak/pochak/UI/Login/TermsOfAgreeViewController.swift index 4695cfb4..56718ec0 100644 --- a/pochak/pochak/UI/Login/TermsOfAgreeViewController.swift +++ b/pochak/pochak/UI/Login/TermsOfAgreeViewController.swift @@ -8,8 +8,16 @@ import UIKit import SafariServices -class TermsOfAgreeViewController: UIViewController, UIViewControllerTransitioningDelegate { - +final class TermsOfAgreeViewController: UIViewController, UIViewControllerTransitioningDelegate { + + // MARK: - Properties + + var delegate: SendDelegate? + private var didAgreeForPrivacyPolicy: Bool = false + private var didAgreeForTermsOfUse: Bool = false + + // MARK: - Views + @IBOutlet weak var pochakLabel: UILabel! @IBOutlet weak var pochakCorpLabel: UILabel! @IBOutlet weak var titleLabel: UILabel! @@ -19,14 +27,9 @@ class TermsOfAgreeViewController: UIViewController, UIViewControllerTransitionin @IBOutlet weak var seePrivacyPolicy: UIButton! @IBOutlet weak var seeTermsOfUse: UIButton! @IBOutlet weak var agreeAndContinueButton: UIButton! - @IBOutlet weak var backgroundView: UIView! - - var didAgreeForPrivacyPolicy : Bool = false - var didAgreeForTermsOfUse : Bool = false - var delegate : SendDelegate? - + // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() @@ -48,23 +51,22 @@ class TermsOfAgreeViewController: UIViewController, UIViewControllerTransitionin titleLabel.font = UIFont(name: "Pretendard-SemiBold", size: 18) titleLabel.textColor = .black - agreeForPrivacyPolicy.setTitle(" [필수] 개인정보 제3자 제공 동의", for: .normal) agreeForPrivacyPolicy.setImage(UIImage(systemName: "checkmark.circle"), for: .normal) agreeForPrivacyPolicy.titleLabel?.font = UIFont(name: "Pretendard-Medium", size: 15) agreeForPrivacyPolicy.setTitleColor(UIColor(named: "gray04"), for: .normal) agreeForPrivacyPolicy.tintColor = UIColor(named: "gray04") - + agreeForTermsOfUSe.setTitle(" [필수] 이용약관 항목", for: .normal) agreeForTermsOfUSe.setImage(UIImage(systemName: "checkmark.circle"), for: .normal) agreeForTermsOfUSe.titleLabel?.font = UIFont(name: "Pretendard-Medium", size: 15) agreeForTermsOfUSe.setTitleColor(UIColor(named: "gray04"), for: .normal) agreeForTermsOfUSe.tintColor = UIColor(named: "gray04") - - // Do any additional setup after loading the view. } + // MARK: - Actions + @IBAction func pressAgreeForPrivacyPolicy(_ sender: Any) { if !didAgreeForPrivacyPolicy { didAgreeForPrivacyPolicy = true @@ -77,7 +79,6 @@ class TermsOfAgreeViewController: UIViewController, UIViewControllerTransitionin agreeForPrivacyPolicy.setImage(UIImage(systemName: "checkmark.circle"), for: .normal) agreeForPrivacyPolicy.tintColor = UIColor(named: "gray04") } - } @IBAction func pressAgreeForTermsOfUSe(_ sender: Any) { @@ -94,25 +95,19 @@ class TermsOfAgreeViewController: UIViewController, UIViewControllerTransitionin } } - @IBAction func openPrivacyPolicy(_ sender: Any) { guard let url = URL(string: "https://pochak.notion.site/e365e34f018949b88543adbe6b0b3746") else { return } let safariVC = SFSafariViewController(url: url) - // delegate 지정 및 presentation style 설정. safariVC.transitioningDelegate = self safariVC.modalPresentationStyle = .pageSheet - present(safariVC, animated: true, completion: nil) } - @IBAction func openTermsOfUSe(_ sender: Any) { guard let url = URL(string: "https://pochak.notion.site/6520996186464c36a8b3a04bc17fa000?pvs=74") else { return } let safariVC = SFSafariViewController(url: url) - // delegate 지정 및 presentation style 설정. safariVC.transitioningDelegate = self safariVC.modalPresentationStyle = .pageSheet - present(safariVC, animated: true, completion: nil) } @@ -124,17 +119,17 @@ class TermsOfAgreeViewController: UIViewController, UIViewControllerTransitionin print("not agreed yet") } } - } +// MARK: - Extension + extension UIButton { func setUnderline() { guard let title = title(for: .normal) else { return } let attributedString = NSMutableAttributedString(string: title) attributedString.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, - range: NSRange(location: 0, length: title.count) - ) + range: NSRange(location: 0, length: title.count)) setAttributedTitle(attributedString, for: .normal) } } diff --git a/pochak/pochak/UI/Profile/MyProfile/Setting/BlockedUserList/BlockedUserViewController.swift b/pochak/pochak/UI/Profile/BlockList/BlockedUserViewController.swift similarity index 63% rename from pochak/pochak/UI/Profile/MyProfile/Setting/BlockedUserList/BlockedUserViewController.swift rename to pochak/pochak/UI/Profile/BlockList/BlockedUserViewController.swift index 8ec0efbc..2de0077a 100644 --- a/pochak/pochak/UI/Profile/MyProfile/Setting/BlockedUserList/BlockedUserViewController.swift +++ b/pochak/pochak/UI/Profile/BlockList/BlockedUserViewController.swift @@ -12,24 +12,26 @@ protocol RemoveCellDelegate: AnyObject { func removeCell(at indexPath: IndexPath, _ handle: String) } -class BlockedUserViewController: UIViewController { +final class BlockedUserViewController: UIViewController { // MARK: - Properties - var blockedUserList: [BlockedUserListDataModel] = [] - var cellIndexPath: IndexPath? - var cellHandle: String? + + private var cellIndexPath: IndexPath? + private var cellHandle: String? + private var blockedUserList: [BlockList] = [] private var isLastPage: Bool = false private var isCurrentlyFetching: Bool = false private var currentFetchingPage: Int = 0 // MARK: - Views + @IBOutlet weak var tableView: UITableView! // MARK: - LifeCycle + override func viewDidLoad() { super.viewDidLoad() currentFetchingPage = 0 - setUpNavigationBar() setUpTableView() setUpRefreshControl() @@ -42,7 +44,7 @@ class BlockedUserViewController: UIViewController { blockedUserList = [] currentFetchingPage = 0 setUpData() - DispatchQueue.main.async { + DispatchQueue.main.async() { self.tableView.refreshControl?.endRefreshing() } } @@ -53,7 +55,7 @@ class BlockedUserViewController: UIViewController { self.navigationController?.navigationBar.backgroundColor = UIColor.clear self.navigationController?.navigationBar.tintColor = .black self.navigationItem.title = "차단관리" - self.navigationController?.navigationBar.titleTextAttributes = [ NSAttributedString.Key.foregroundColor : UIColor.black, NSAttributedString.Key.font : UIFont(name: "Pretendard-Bold", size: 18) ?? UIFont.systemFont(ofSize: 18, weight: .bold)] + self.navigationController?.navigationBar.titleTextAttributes = [ NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font: UIFont(name: "Pretendard-Bold", size: 18) ?? UIFont.systemFont(ofSize: 18, weight: .bold)] } private func setUpTableView() { @@ -70,15 +72,31 @@ class BlockedUserViewController: UIViewController { } private func setUpData() { - let handle = UserDefaultsManager.getData(type: String.self, forKey: .handle) ?? "handle not found" isCurrentlyFetching = true - BlockedUserListDataManager.shared.blockedUserListDataManager(handle, currentFetchingPage, { resultData in - let newBlockedUsers = resultData.blockList - let startIndex = resultData.blockList.count + let handle = UserDefaultsManager.getData(type: String.self, forKey: .handle) ?? "handle not found" + let request = BlockListRequest(page: currentFetchingPage) + + UserService.getBlockUserList(handle: handle, request: request) { data, failed in + guard let data = data else { + switch failed { + case .disconnected: + self.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .serverError: + self.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .unknownError: + self.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + default: + self.present(UIAlertController.networkErrorAlert(title: "요청에 실패하였습니다."), animated: true) + } + return + } + + let newBlockedUsers = data.result.blockList + let startIndex = data.result.blockList.count let endIndex = startIndex + newBlockedUsers.count let newIndexPaths = (startIndex.. (tableView.contentSize.height - tableView.bounds.size.height)){ + if (tableView.contentOffset.y > (tableView.contentSize.height - tableView.bounds.size.height)) { if (!isLastPage && !isCurrentlyFetching) { print("스크롤에 의해 새 데이터 가져오는 중, page: \(currentFetchingPage)") isCurrentlyFetching = true diff --git a/pochak/pochak/UI/Profile/MyProfile/Setting/BlockedUserList/cell/BlockedUserTableViewCell.swift b/pochak/pochak/UI/Profile/BlockList/cell/BlockedUserTableViewCell.swift similarity index 73% rename from pochak/pochak/UI/Profile/MyProfile/Setting/BlockedUserList/cell/BlockedUserTableViewCell.swift rename to pochak/pochak/UI/Profile/BlockList/cell/BlockedUserTableViewCell.swift index e6ef4779..4f5fc221 100644 --- a/pochak/pochak/UI/Profile/MyProfile/Setting/BlockedUserList/cell/BlockedUserTableViewCell.swift +++ b/pochak/pochak/UI/Profile/BlockList/cell/BlockedUserTableViewCell.swift @@ -7,7 +7,7 @@ import UIKit -class BlockedUserTableViewCell: UITableViewCell { +final class BlockedUserTableViewCell: UITableViewCell { // MARK: - Properties @@ -41,17 +41,16 @@ class BlockedUserTableViewCell: UITableViewCell { print("superview is not a UICollectionView - getIndexPath") return } - guard let indexPath = superView.indexPath(for: self) else {return} + guard let indexPath = superView.indexPath(for: self) else { return } delegate?.removeCell(at: indexPath, cellHandle ?? "") } // MARK: - Functions - func setUpCell() { + private func setUpCell() { profileImg.contentMode = .scaleAspectFill profileImg.clipsToBounds = true profileImg.layer.cornerRadius = 26 - unblockButton.setTitle("차단해제", for: .normal) unblockButton.backgroundColor = UIColor(named: "gray03") unblockButton.setTitleColor(UIColor.white, for: .normal) @@ -59,17 +58,10 @@ class BlockedUserTableViewCell: UITableViewCell { unblockButton.layer.cornerRadius = 5 } - func setUpCellData(_ blockedUserList : BlockedUserListDataModel) { - let imageURL = blockedUserList.profileImage - if let url = URL(string: imageURL) { - profileImg.kf.setImage(with: url, completionHandler: { result in - switch result { - case .success(let value): - print("Image successfully loaded: \(value.image)") - case .failure(let error): - print("Image failed to load with error: \(error.localizedDescription)") - } - }) + func setUpCellData(_ blockedUserList: BlockList) { + var imageURL = blockedUserList.profileImage + if let url = URL(string: (imageURL)) { + profileImg.load(with: url) } userHandle.text = blockedUserList.handle userName.text = blockedUserList.name diff --git a/pochak/pochak/UI/Profile/MyProfile/Setting/BlockedUserList/cell/BlockedUserTableViewCell.xib b/pochak/pochak/UI/Profile/BlockList/cell/BlockedUserTableViewCell.xib similarity index 100% rename from pochak/pochak/UI/Profile/MyProfile/Setting/BlockedUserList/cell/BlockedUserTableViewCell.xib rename to pochak/pochak/UI/Profile/BlockList/cell/BlockedUserTableViewCell.xib diff --git a/pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowListViewController.swift b/pochak/pochak/UI/Profile/FollowList/FollowListViewController.swift similarity index 85% rename from pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowListViewController.swift rename to pochak/pochak/UI/Profile/FollowList/FollowListViewController.swift index 78841023..35b5bb04 100644 --- a/pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowListViewController.swift +++ b/pochak/pochak/UI/Profile/FollowList/FollowListViewController.swift @@ -9,27 +9,24 @@ import Tabman import Pageboy import UIKit -class FollowListViewController: TabmanViewController { +final class FollowListViewController: TabmanViewController { // MARK: - Properties - - var viewControllers: [UIViewController] = [] - var index: Int = 0 + var handle: String? - var followerCount = UserDefaultsManager.getData(type: Int.self, forKey: .followerCount) - var followingCount = UserDefaultsManager.getData(type: Int.self, forKey: .followingCount) - + var index: Int = 0 + private var viewControllers: [UIViewController] = [] + // MARK: - LifeCycle - + override func viewDidLoad() { super.viewDidLoad() setUpTabman() setUpNavigationBar() } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - // View Controller 생길 때 네비게이션 바 숨김 self.navigationController?.isNavigationBarHidden = false self.navigationController?.navigationBar.backgroundColor = UIColor.clear } @@ -41,7 +38,7 @@ class FollowListViewController: TabmanViewController { self.navigationController?.navigationBar.backgroundColor = UIColor.clear self.navigationItem.title = handle ?? "handle not found" } - + private func setUpTabman() { // Tabman 사용 /* 1. tab에 보여질 VC 추가 */ @@ -81,7 +78,7 @@ class FollowListViewController: TabmanViewController { } } -// MARK: - Extension : PageboyViewControllerDataSource, TMBarDataSource +// MARK: - Extension: PageboyViewControllerDataSource, TMBarDataSource extension FollowListViewController: PageboyViewControllerDataSource, TMBarDataSource { @@ -95,11 +92,9 @@ extension FollowListViewController: PageboyViewControllerDataSource, TMBarDataSo } func defaultPage(for pageboyViewController: PageboyViewController) -> PageboyViewController.Page? { - // index를 통해 처음에 보이는 탭을 설정 - return .at(index: index) + return .at(index: index) // index를 통해 처음에 보이는 탭을 설정 } - // 팔로워 페이지 혹은 팔로잉 페이지인지에 따라 defualtPage 다르게 하기 func barItem(for bar: TMBar, at index: Int) -> TMBarItemable { switch index { case 0: diff --git a/pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowerListTabmanViewController.swift b/pochak/pochak/UI/Profile/FollowList/FollowerListTabmanViewController.swift similarity index 50% rename from pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowerListTabmanViewController.swift rename to pochak/pochak/UI/Profile/FollowList/FollowerListTabmanViewController.swift index 8d0b2627..c2931ccf 100644 --- a/pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowerListTabmanViewController.swift +++ b/pochak/pochak/UI/Profile/FollowList/FollowerListTabmanViewController.swift @@ -1,5 +1,5 @@ // -// FirstTabmanViewController.swift +// FollowerListTabmanViewController.swift // pochak // // Created by Seo Cindy on 12/27/23. @@ -12,18 +12,18 @@ protocol RemoveImageDelegate: AnyObject { func removeFromCollectionView(at indexPath: IndexPath, _ handle: String) } -class FollowerListTabmanViewController: UIViewController { +final class FollowerListTabmanViewController: UIViewController { // MARK: - Properties - var imageArray: [MemberListDataModel] = [] + var imageArray: [MemberListData] = [] var receivedHandle: String? - var cellIndexPath: IndexPath? - var cellHandle: String? - var loginUserHandle = UserDefaultsManager.getData(type: String.self, forKey: .handle) + private var cellIndexPath: IndexPath? + private var cellHandle: String? private var isLastPage: Bool = false private var isCurrentlyFetching: Bool = false private var currentFetchingPage: Int = 0 + private var loginUserHandle = UserDefaultsManager.getData(type: String.self, forKey: .handle) // MARK: - Views @@ -34,7 +34,6 @@ class FollowerListTabmanViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() currentFetchingPage = 0 - setUpCollectionView() setUpRefreshControl() setUpData() @@ -43,12 +42,10 @@ class FollowerListTabmanViewController: UIViewController { // MARK: - Actions @objc private func refreshData(_ sender: Any) { - // 데이터 새로고침 완료 후 UIRefreshControl을 종료 - print("refresh") imageArray = [] currentFetchingPage = 0 setUpData() - DispatchQueue.main.async { + DispatchQueue.main.async() { self.followerCollectionView.refreshControl?.endRefreshing() } } @@ -59,7 +56,7 @@ class FollowerListTabmanViewController: UIViewController { followerCollectionView.delegate = self followerCollectionView.dataSource = self followerCollectionView.register( - UINib(nibName: HomeCollectionViewCell.identifier, bundle: nil), + UINib(nibName: FollowerCollectionViewCell.identifier, bundle: nil), forCellWithReuseIdentifier: FollowerCollectionViewCell.identifier) } @@ -70,37 +67,49 @@ class FollowerListTabmanViewController: UIViewController { } private func setUpData() { - FollowListDataManager.shared.followerDataManager(receivedHandle ?? "",currentFetchingPage,{resultData in - - let newMembers = resultData.memberList - let startIndex = resultData.memberList.count - print("startIndex : \(startIndex)") - let endIndex = startIndex + newMembers.count - print("endIndex : \(endIndex)") - let newIndexPaths = (startIndex..>>>>>> Follower is currently reloading!!!!!!!") - } else { - self.followerCollectionView.insertItems(at: newIndexPaths) - print(">>>>>>> Follower is currently fethcing!!!!!!!") + let request = FollowListRequest(page: currentFetchingPage) + if let handle = receivedHandle { + UserService.getFollowers(handle: handle, request: request) { [weak self] data, failed in + guard let data = data else { + switch failed { + case .disconnected: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .serverError: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .unknownError: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + default: + self?.present(UIAlertController.networkErrorAlert(title: "요청에 실패하였습니다."), animated: true) + } + return + } + + let newMembers = data.result.memberList + let startIndex = data.result.memberList.count + let endIndex = startIndex + newMembers.count + let newIndexPaths = (startIndex.. Int { return max(0,(imageArray.count)) @@ -122,17 +131,18 @@ extension FollowerListTabmanViewController : UICollectionViewDelegate, UICollect // 유저 클릭 시 해당 프로필로 이동 func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - guard let otherUserProfileVC = self.storyboard?.instantiateViewController(withIdentifier: "OtherUserProfileVC") as? OtherUserProfileViewController else {return} + guard let otherUserProfileVC = self.storyboard?.instantiateViewController(withIdentifier: "OtherUserProfileVC") + as? OtherUserProfileViewController else { return } self.navigationController?.pushViewController(otherUserProfileVC, animated: true) - guard let cell: FollowerCollectionViewCell = self.followerCollectionView.cellForItem(at: indexPath) as? FollowerCollectionViewCell else {return} + guard let cell: FollowerCollectionViewCell = self.followerCollectionView.cellForItem(at: indexPath) + as? FollowerCollectionViewCell else { return } otherUserProfileVC.receivedHandle = cell.userId.text } - } -// MARK: - Extension : UICollectionViewDelegateFlowLayout +// MARK: - Extension: UICollectionViewDelegateFlowLayout -extension FollowerListTabmanViewController : UICollectionViewDelegateFlowLayout { +extension FollowerListTabmanViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize(width: followerCollectionView.bounds.width, @@ -144,30 +154,48 @@ extension FollowerListTabmanViewController : UICollectionViewDelegateFlowLayout } } -// MARK: - Extension : RemoveImageDelegate +// MARK: - Extension: RemoveImageDelegate extension FollowerListTabmanViewController: RemoveImageDelegate { func removeFromCollectionView(at indexPath: IndexPath, _ handle: String) { + cellHandle = handle + cellIndexPath = indexPath showAlert(alertType: .confirmAndCancel, titleText: "팔로워를 삭제하시겠습니까?", messageText: "팔로워를 삭제하면, 팔로워와 관련된 \n사진이 사라집니다.", cancelButtonText: "취소", - confirmButtonText: "삭제하기" - ) + confirmButtonText: "삭제하기") } } -// MARK: - Extension : CustomAlertDelegate +// MARK: - Extension: CustomAlertDelegate -extension FollowerListTabmanViewController : CustomAlertDelegate { +extension FollowerListTabmanViewController: CustomAlertDelegate { func confirmAction() { - DeleteFollowerDataManager.shared.deleteFollowerDataManager(receivedHandle ?? "", cellHandle ?? "", { resultData in - print(resultData.message) - self.imageArray.remove(at: self.cellIndexPath!.row) - self.followerCollectionView.reloadData() - }) + let request = DeleteFollowerRequest(followerHandle: cellHandle ?? "") + if let handle = receivedHandle { + UserService.deleteFollower(handle: handle, request: request) { data, failed in + guard let data = data else { + switch failed { + case .disconnected: + self.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .serverError: + self.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .unknownError: + self.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + default: + self.present(UIAlertController.networkErrorAlert(title: "요청에 실패하였습니다."), animated: true) + } + return + } + self.imageArray.remove(at: self.cellIndexPath!.row) + self.followerCollectionView.reloadData() + } + } else { + print("No handle received") + } } func cancel() { @@ -175,7 +203,7 @@ extension FollowerListTabmanViewController : CustomAlertDelegate { } } -// MARK: - Extension : UIScrollViewDelegate +// MARK: - Extension: UIScrollViewDelegate extension FollowerListTabmanViewController: UIScrollViewDelegate { diff --git a/pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowingListTabmanViewController.swift b/pochak/pochak/UI/Profile/FollowList/FollowingListTabmanViewController.swift similarity index 60% rename from pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowingListTabmanViewController.swift rename to pochak/pochak/UI/Profile/FollowList/FollowingListTabmanViewController.swift index bbfcfe4f..87f757e4 100644 --- a/pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowingListTabmanViewController.swift +++ b/pochak/pochak/UI/Profile/FollowList/FollowingListTabmanViewController.swift @@ -1,5 +1,5 @@ // -// SecondTabmanViewController.swift +// FollowingListTabmanViewController.swift // pochak // // Created by Seo Cindy on 12/27/23. @@ -7,11 +7,11 @@ import UIKit -class FollowingListTabmanViewController: UIViewController { +final class FollowingListTabmanViewController: UIViewController { // MARK: - Properties - var imageArray: [MemberListDataModel] = [] + var imageArray: [MemberListData] = [] var receivedHandle: String? private var isLastPage: Bool = false private var isCurrentlyFetching: Bool = false @@ -26,7 +26,6 @@ class FollowingListTabmanViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() currentFetchingPage = 0 - setUpCollectionView() setUpRefreshControl() setUpData() @@ -35,12 +34,10 @@ class FollowingListTabmanViewController: UIViewController { // MARK: - Actions @objc private func refreshData(_ sender: Any) { - // 데이터 새로고침 완료 후 UIRefreshControl을 종료 - print("refresh") imageArray = [] currentFetchingPage = 0 setUpData() - DispatchQueue.main.async { + DispatchQueue.main.async() { self.followingCollectionView.refreshControl?.endRefreshing() } } @@ -62,34 +59,47 @@ class FollowingListTabmanViewController: UIViewController { } private func setUpData() { - FollowListDataManager.shared.followingDataManager(receivedHandle ?? "", currentFetchingPage, { resultData in - let newMembers = resultData.memberList - let startIndex = resultData.memberList.count - print("startIndex : \(startIndex)") - let endIndex = startIndex + newMembers.count - print("endIndex : \(endIndex)") - let newIndexPaths = (startIndex..>>>>>> Follower is currently reloading!!!!!!!") - } else { - self.followingCollectionView.insertItems(at: newIndexPaths) - print(">>>>>>> Follower is currently fethcing!!!!!!!") + let request = FollowListRequest(page: currentFetchingPage) + if let handle = receivedHandle { + UserService.getFollowings(handle: handle, request: request) { [weak self] data, failed in + guard let data = data else { + switch failed { + case .disconnected: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .serverError: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .unknownError: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + default: + self?.present(UIAlertController.networkErrorAlert(title: "요청에 실패하였습니다."), animated: true) + } + return + } + + let newMembers = data.result.memberList + let startIndex = data.result.memberList.count + let endIndex = startIndex + newMembers.count + let newIndexPaths = (startIndex.. Void) { - - let url = "\(APIConstants.baseURL)/api/v2/members/\(handle)/follower?followerHandle=\(selectedHandle)" - - AF.request(url, - method: .delete, - encoding: URLEncoding.default, - interceptor: RequestInterceptor.getRequestInterceptor()) - .validate() - .responseDecodable(of: DeleteFollowerDataResponse.self) { response in - switch response.result { - case .success(let result): - completion(result) - case .failure(let error): - print("Request Fail : deleteFollowerDataManager") - print(error) - } - } - } -} diff --git a/pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowAPI/DeleteFollowerDataModel.swift b/pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowAPI/DeleteFollowerDataModel.swift deleted file mode 100644 index 398777b4..00000000 --- a/pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowAPI/DeleteFollowerDataModel.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// DeleteFollowerDataModel.swift -// pochak -// -// Created by Seo Cindy on 1/30/24. -// - -struct DeleteFollowerDataResponse: Codable { - let isSuccess: Bool - let code: String - let message: String -} diff --git a/pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowAPI/FollowListDataManager.swift b/pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowAPI/FollowListDataManager.swift deleted file mode 100644 index c6f5cec7..00000000 --- a/pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowAPI/FollowListDataManager.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// FollowDataManager.swift -// pochak -// -// Created by Seo Cindy on 1/30/24. -// - -import Foundation -import Alamofire - -class FollowListDataManager { - - static let shared = FollowListDataManager() - - func followerDataManager(_ handle : String, _ page : Int, _ completion: @escaping (FollowListDataModel) -> Void) { - - let url = "\(APIConstants.baseURL)/api/v2/members/\(handle)/follower?page=\(page)" - - AF.request(url, - method: .get, - encoding: URLEncoding.default, - interceptor: RequestInterceptor.getRequestInterceptor()) - .validate() - .responseDecodable(of: FollowListDataResponse.self) { response in - switch response.result { - case .success(let result): - if let data = response.data, let errorMessage = String(data: data, encoding: .utf8) { - print("Success Data for followerDataManager: \(errorMessage)") - } - let resultData = result.result - print(resultData) - completion(resultData) - case .failure(let error): - if let data = response.data, let errorMessage = String(data: data, encoding: .utf8) { - print("Failure Data for followerDataManager: \(errorMessage)") - } - print("Request Fail : followerDataManager") - print(error) - } - } - } - - func followingDataManager(_ handle : String, _ page : Int, _ completion: @escaping (FollowListDataModel) -> Void) { - - let url = "\(APIConstants.baseURLv2)/api/v2/members/\(handle)/following?page=\(page)" - - AF.request(url, - method: .get, - encoding: URLEncoding.default, - interceptor: RequestInterceptor.getRequestInterceptor()) - .validate() - .responseDecodable(of: FollowListDataResponse.self) { response in - print(response) - switch response.result { - case .success(let result): - let resultData = result.result - completion(resultData) - case .failure(let error): - if let data = response.data, let errorMessage = String(data: data, encoding: .utf8) { - print("Failure Data for followingDataManager: \(errorMessage)") - } - print("Request Fail : followingDataManager") - print(error) - } - } - } -} diff --git a/pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowAPI/FollowListDataModel.swift b/pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowAPI/FollowListDataModel.swift deleted file mode 100644 index f765a611..00000000 --- a/pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowAPI/FollowListDataModel.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// FollowDataModel.swift -// pochak -// -// Created by Seo Cindy on 1/30/24. -// - -struct FollowListDataResponse: Codable { - let isSuccess: Bool - let code: String - let message: String - let result: FollowListDataModel -} - -struct FollowListDataModel: Codable { - var pageInfo: PageDataModel - var memberList: [MemberListDataModel] -} - -struct MemberListDataModel: Codable { - var memberId: Int - var profileImage: String - var handle: String - var name: String - var isFollow: Bool? -} diff --git a/pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowAPI/FollowToggleDataManager.swift b/pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowAPI/FollowToggleDataManager.swift deleted file mode 100644 index 2b98a5e5..00000000 --- a/pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowAPI/FollowToggleDataManager.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// FollowToggleDataManager.swift -// pochak -// -// Created by Seo Cindy on 1/30/24. -// - -import Foundation -import Alamofire - -class FollowToggleDataManager { - - static let shared = FollowToggleDataManager() - - func followToggleDataManager(_ handle : String, _ completion: @escaping (FollowToggleDataResponse) -> Void) { - - let url = "\(APIConstants.baseURL)/api/v2/members/\(handle)/follow" - - AF.request(url, - method: .post, - encoding: URLEncoding.default, - interceptor: RequestInterceptor.getRequestInterceptor()) - .validate() - .responseDecodable(of: FollowToggleDataResponse.self) { response in - switch response.result { - case .success(let result): - completion(result) - case .failure(let error): - print("Request Fail : followToggleDataManager") - print(error) - } - } - } -} diff --git a/pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowAPI/FollowToggleDataModel.swift b/pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowAPI/FollowToggleDataModel.swift deleted file mode 100644 index 8aa86b75..00000000 --- a/pochak/pochak/UI/Profile/MyProfile/FollowListTabman/FollowAPI/FollowToggleDataModel.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// FollowToggleDataModel.swift -// pochak -// -// Created by Seo Cindy on 1/30/24. -// - -struct FollowToggleDataResponse: Codable { - let isSuccess: Bool - let code: String - let message: String -} diff --git a/pochak/pochak/UI/Profile/MyProfile/MyProfileTabViewController.swift b/pochak/pochak/UI/Profile/MyProfile/MyProfileTabViewController.swift deleted file mode 100644 index 31e0ee9c..00000000 --- a/pochak/pochak/UI/Profile/MyProfile/MyProfileTabViewController.swift +++ /dev/null @@ -1,173 +0,0 @@ -// -// ProfileTabViewController.swift -// pochak -// -// Created by Seo Cindy on 12/27/23. -// - -import UIKit - -class MyProfileTabViewController: UIViewController { - - // MARK: - Properties - - let handle = UserDefaultsManager.getData(type: String.self, forKey: .handle) ?? "" - - // MARK: - Views - - @IBOutlet weak var profileBackground: UIView! - @IBOutlet weak var profileImage: UIImageView! - @IBOutlet weak var followerList: UIStackView! - @IBOutlet weak var followingList: UIStackView! - @IBOutlet weak var whiteBackground1: UIView! - @IBOutlet weak var userHandle: UILabel! - @IBOutlet weak var userName: UILabel! - @IBOutlet weak var userMessage: UILabel! - @IBOutlet weak var postCount: UILabel! - @IBOutlet weak var followerCount: UILabel! - @IBOutlet weak var followingCount: UILabel! - @IBOutlet weak var postListTabmanView: UIView! - @IBOutlet weak var updateProfileBtn: UIButton! - @IBOutlet weak var topUIView: UIView! - @IBOutlet weak var settingBtn: UIButton! - - // MARK: - Lifecycle - - // Container View에 데이터 전달(ViewDidLoad보다 먼저 실행) - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - //storyboard에서 설정한 identifier와 동일한 이름 - if segue.identifier == "embedContainer" { - let postListVC = segue.destination as! PostListViewController - postListVC.handle = handle - } - } - - override func viewDidLoad() { - super.viewDidLoad() - setUpViewController() - setUpData() - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - setUpData() - self.navigationController?.isNavigationBarHidden = true - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - self.navigationController?.isNavigationBarHidden = false - } - - // MARK: - Actions - - @IBAction func clickSettingBtn(_ sender: Any) { - guard let settingsVC = self.storyboard?.instantiateViewController(withIdentifier: "SettingsVC") as? SettingsViewController else {return} - self.navigationController?.pushViewController(settingsVC, animated: true) - } - - @IBAction func updateProfile(_ sender: Any) { - guard let updateProfileVC = self.storyboard?.instantiateViewController(withIdentifier: "UpdateProfileVC") as? UpdateProfileViewController else {return} - self.navigationController?.pushViewController(updateProfileVC, animated: true) - } - - @objc private func viewFollowerTapped() { - guard let followListVC = self.storyboard?.instantiateViewController(withIdentifier: "FollowListVC") as? FollowListViewController else {return} - followListVC.index = 0 - followListVC.handle = handle - followListVC.followerCount = UserDefaultsManager.getData(type: Int.self, forKey: .followerCount) ?? 0 - followListVC.followingCount = UserDefaultsManager.getData(type: Int.self, forKey: .followingCount) ?? 0 - self.navigationController?.pushViewController(followListVC, animated: true) - } - - @objc private func viewFollowingTapped() { - guard let followListVC = self.storyboard?.instantiateViewController(withIdentifier: "FollowListVC") as? FollowListViewController else {return} - followListVC.index = 1 - followListVC.handle = handle - followListVC.followerCount = UserDefaultsManager.getData(type: Int.self, forKey: .followerCount) ?? 0 - followListVC.followingCount = UserDefaultsManager.getData(type: Int.self, forKey: .followingCount) ?? 0 - self.navigationController?.pushViewController(followListVC, animated: true) - } - - // MARK: - Functions - - private func setUpViewController() { - self.navigationController?.isNavigationBarHidden = true - - profileBackground.layer.cornerRadius = 58 - profileImage.layer.cornerRadius = 55 - - whiteBackground1.layer.cornerRadius = 8 - - viewFollowerList() - viewFollowingList() - - userHandle.text = "@\(handle)" - - let backBarButtonItem = UIBarButtonItem(title: nil, style: .plain, target: nil, action: nil) - backBarButtonItem.tintColor = .black - self.navigationItem.backBarButtonItem = backBarButtonItem - - postListTabmanView.translatesAutoresizingMaskIntoConstraints = false - postListTabmanView.topAnchor.constraint(equalTo: self.whiteBackground1.bottomAnchor, constant: 5).isActive = true - postListTabmanView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0).isActive = true - postListTabmanView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0).isActive = true - postListTabmanView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0).isActive = true - } - - private func viewFollowerList() { /* UITapGestureRecognizer 사용 */ - let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(viewFollowerTapped)) - followerList.addGestureRecognizer(tapGestureRecognizer) - } - - private func viewFollowingList() { /* UITapGestureRecognizer 사용 */ - let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(viewFollowingTapped)) - followingList.addGestureRecognizer(tapGestureRecognizer) - } - - private func setUpData() { - MyProfilePostDataManager.shared.myProfileUserAndPochakedPostDataManager(handle, 0,{ response in - switch response { - case .success(let resultData): - let imageURL = resultData.profileImage ?? "" - UserDefaultsManager.setData(value: imageURL, key: .profileImgUrl) - if let url = URL(string: imageURL) { - self.profileImage.kf.setImage(with: url) { result in - switch result { - case .success(let value): - print("Image successfully loaded: \(value.image)") - case .failure(let error): - print("Image failed to load with error: \(error.localizedDescription)") - } - } - } - - // 필요한 데이터 뷰에 반영 - self.setUpResponseData(resultData) - - // UserDefaultsManager에 새로운 데이터 저장 후 관리 : followerCount, followingCount - self.setUpUserDefaults(resultData) - - case .MEMBER4002: - print("유효하지 않은 멤버의 handle입니다.") - self.present(UIAlertController.networkErrorAlert(title: "유효하지 않은 멤버의 handle입니다."), animated: true) - } - }) - } - - private func setUpResponseData(_ resposeData: MyProfileUserAndPochakedPostModel) { - self.profileImage.contentMode = .scaleAspectFill - self.userName.text = String(resposeData.name ?? "") - self.userMessage.text = String(resposeData.message ?? "") - self.postCount.text = String(resposeData.totalPostNum ?? 0) - self.followerCount.text = String(resposeData.followerCount ?? 0) - self.followingCount.text = String(resposeData.followingCount ?? 0) - } - - private func setUpUserDefaults(_ resposeData: MyProfileUserAndPochakedPostModel) { - UserDefaultsManager.setData(value: resposeData.name, key: .name) - UserDefaultsManager.setData(value: resposeData.message, key: .message) - UserDefaultsManager.setData(value: resposeData.followerCount, key: .followerCount) - UserDefaultsManager.setData(value: resposeData.followingCount, key: .followingCount) - } -} diff --git a/pochak/pochak/UI/Profile/MyProfile/PostListTabman/PochakPostTabmanViewController.swift b/pochak/pochak/UI/Profile/MyProfile/PostListTabman/PochakPostTabmanViewController.swift deleted file mode 100644 index b84a1ad2..00000000 --- a/pochak/pochak/UI/Profile/MyProfile/PostListTabman/PochakPostTabmanViewController.swift +++ /dev/null @@ -1,140 +0,0 @@ -// -// SecondPostTabmanViewController.swift -// pochak -// -// Created by Seo Cindy on 12/27/23. -// - -import UIKit - -class PochakPostTabmanViewController: UIViewController { - - // MARK: - Properties - - var receivedHandle: String? - var imageArray: [PostDataModel] = [] - private var isLastPage: Bool = false - private var isCurrentlyFetching: Bool = false - private var currentFetchingPage: Int = 0 - - // MARK: - Views - - @IBOutlet weak var postCollectionView: UICollectionView! - - // MARK: - Lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - currentFetchingPage = 0 - - setUpCollectionView() - setUpRefreshControl() - setUpData() - } - - // MARK: - Actions - - @objc private func refreshData(_ sender: Any) { - // 데이터 새로고침 완료 후 UIRefreshControl을 종료 - print("refresh") - imageArray = [] - currentFetchingPage = 0 - setUpData() - DispatchQueue.main.async { - self.postCollectionView.refreshControl?.endRefreshing() - } - } - - // MARK: - Functions - - private func setUpCollectionView() { - postCollectionView.delegate = self - postCollectionView.dataSource = self - postCollectionView.register( - UINib(nibName: ProfilePostCollectionViewCell.identifier, bundle: nil), - forCellWithReuseIdentifier: ProfilePostCollectionViewCell.identifier) - } - - private func setUpRefreshControl() { - let refreshControl = UIRefreshControl() - refreshControl.addTarget(self, action: #selector(refreshData(_:)), for: .valueChanged) - postCollectionView.refreshControl = refreshControl - } - - private func setUpData() { - MyProfilePostDataManager.shared.myProfilePochakPostDataManager(receivedHandle ?? "",currentFetchingPage,{resultData in - let newPosts = resultData.postList - let startIndex = resultData.postList.count - let endIndex = startIndex + newPosts.count - let newIndexPaths = (startIndex..>>>>>> PochakPostDataManager is currently reloading!!!!!!!") - } else { - self.postCollectionView.insertItems(at: newIndexPaths) - } - self.isCurrentlyFetching = false - self.currentFetchingPage += 1; - } - }) - } -} - -// MARK: - Extension : UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UIScrollViewDelegate - -extension PochakPostTabmanViewController : UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { - - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return max(0,(imageArray.count)) - } - - func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - guard let cell = collectionView.dequeueReusableCell( - withReuseIdentifier: ProfilePostCollectionViewCell.identifier, - for: indexPath) as? ProfilePostCollectionViewCell else { - return UICollectionViewCell() - } - let postData = imageArray[indexPath.item] // indexPath 안에는 섹션에 대한 정보, 섹션에 들어가는 데이터 정보 등이 있다 - cell.setUpCellData(postData) - return cell - } - - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt: Int) -> CGFloat { - return 5 - } - - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { - return 5 - } - - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - let width = CGFloat((collectionView.frame.width - 10) / 3) - return CGSize(width: width, height: width * 4 / 3) - } - - // post 클릭 시 해당 post로 이동 - func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - let exploreTabSb = UIStoryboard(name: "ExploreTab", bundle: nil) - guard let postVC = exploreTabSb.instantiateViewController(withIdentifier: "PostVC") as? PostViewController - else { return } - postVC.receivedPostId = imageArray[indexPath.item].postId - self.navigationController?.pushViewController(postVC, animated: true) - } -} - -extension PochakPostTabmanViewController: UIScrollViewDelegate { - - func scrollViewDidScroll(_ scrollView: UIScrollView) { - if (postCollectionView.contentOffset.y > (postCollectionView.contentSize.height - postCollectionView.bounds.size.height)){ - if (!isLastPage && !isCurrentlyFetching) { - print("스크롤에 의해 새 데이터 가져오는 중, page: \(currentFetchingPage)") - isCurrentlyFetching = true - setUpData() - } - } - } -} diff --git a/pochak/pochak/UI/Profile/MyProfile/PostListTabman/PochakedPostTabmanViewController.swift b/pochak/pochak/UI/Profile/MyProfile/PostListTabman/PochakedPostTabmanViewController.swift deleted file mode 100644 index 9ba038d4..00000000 --- a/pochak/pochak/UI/Profile/MyProfile/PostListTabman/PochakedPostTabmanViewController.swift +++ /dev/null @@ -1,153 +0,0 @@ -// -// FirstPostTabmanViewController.swift -// pochak -// -// Created by Seo Cindy on 12/27/23. -// - -import UIKit - -class PochakedPostTabmanViewController: UIViewController { - - // MARK: - Properties - - var receivedHandle: String? - var imageArray: [PostDataModel] = [] - private var isLastPage: Bool = false - private var isCurrentlyFetching: Bool = false - private var currentFetchingPage: Int = 0 - - // MARK: - Views - - @IBOutlet weak var postCollectionView: UICollectionView! - - // MARK: - Lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - currentFetchingPage = 0 - - setUpCollectionView() - setUpRefreshControl() - setUpData() - } - - // MARK: - Actions - - @objc private func refreshData(_ sender: Any) { - print("refresh") - imageArray = [] - currentFetchingPage = 0 - setUpData() - DispatchQueue.main.async { - self.postCollectionView.refreshControl?.endRefreshing() - } - } - - // MARK: - Functions - - private func setUpCollectionView() { - postCollectionView.delegate = self - postCollectionView.dataSource = self - postCollectionView.register( - UINib(nibName: ProfilePostCollectionViewCell.identifier, bundle: nil), - forCellWithReuseIdentifier: ProfilePostCollectionViewCell.identifier) - } - - private func setUpRefreshControl() { - let refreshControl = UIRefreshControl() - refreshControl.addTarget(self, action: #selector(refreshData(_:)), for: .valueChanged) - postCollectionView.refreshControl = refreshControl - } - - private func setUpData() { - isCurrentlyFetching = true - MyProfilePostDataManager.shared.myProfileUserAndPochakedPostDataManager(receivedHandle ?? "", currentFetchingPage, { response in - switch response { - case .success(let resultData): - - let newPosts = resultData.postList - let startIndex = resultData.postList.count - print("startIndex : \(startIndex)") - let endIndex = startIndex + newPosts.count - print("endIndex : \(endIndex)") - let newIndexPaths = (startIndex..>>>>>> PochakedPostDataManager is currently reloading!!!!!!!") - } else { - self.postCollectionView.insertItems(at: newIndexPaths) - print(">>>>>>> PochakedPostDataManager is currently fethcing!!!!!!!") - } - self.isCurrentlyFetching = false - self.currentFetchingPage += 1; - } - case .MEMBER4002: - print("유효하지 않은 멤버의 handle입니다.") - } - }) - } -} - -// MARK: - Extension : UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UIScrollViewDelegate - -extension PochakedPostTabmanViewController : UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { - - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return max(0,(imageArray.count)) - } - - func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - // cell 생성 - guard let cell = collectionView.dequeueReusableCell( - withReuseIdentifier: ProfilePostCollectionViewCell.identifier, - for: indexPath) as? ProfilePostCollectionViewCell else { - return UICollectionViewCell() - } - - let postData = imageArray[indexPath.item] // indexPath 안에는 섹션에 대한 정보, 섹션에 들어가는 데이터 정보 등이 있다 - cell.setUpCellData(postData) - return cell - } - - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt: Int) -> CGFloat { - return 5 - } - - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { - return 5 - } - - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - let width = CGFloat((collectionView.frame.width - 10) / 3) - return CGSize(width: width, height: width * 4 / 3) - } - - // post 클릭 시 해당 post로 이동 - func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - let exploreTabSb = UIStoryboard(name: "ExploreTab", bundle: nil) - guard let postVC = exploreTabSb.instantiateViewController(withIdentifier: "PostVC") as? PostViewController - else { return } - postVC.receivedPostId = imageArray[indexPath.item].postId - self.navigationController?.pushViewController(postVC, animated: true) - } -} - -extension PochakedPostTabmanViewController: UIScrollViewDelegate { - - func scrollViewDidScroll(_ scrollView: UIScrollView) { - if (postCollectionView.contentOffset.y > (postCollectionView.contentSize.height - postCollectionView.bounds.size.height)){ - if (!isLastPage && !isCurrentlyFetching) { - print("스크롤에 의해 새 데이터 가져오는 중, page: \(currentFetchingPage)") - isCurrentlyFetching = true - setUpData() - } - } - } -} diff --git a/pochak/pochak/UI/Profile/MyProfile/PostListTabman/cell/ProfilePostCollectionViewCell.swift b/pochak/pochak/UI/Profile/MyProfile/PostListTabman/cell/ProfilePostCollectionViewCell.swift deleted file mode 100644 index 81631014..00000000 --- a/pochak/pochak/UI/Profile/MyProfile/PostListTabman/cell/ProfilePostCollectionViewCell.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// PostCollectionViewCell.swift -// pochak -// -// Created by Seo Cindy on 12/27/23. -// - -import UIKit -import Kingfisher - -class ProfilePostCollectionViewCell: UICollectionViewCell { - - // MARK: - Properties - - static let identifier = "ProfilePostCollectionViewCell" // Collection View가 생성할 cell임을 명시 - - // MARK: - Views - - @IBOutlet weak var profilePostImage: UIImageView! - - // MARK: - LifeCycle - - override func awakeFromNib() { - super.awakeFromNib() - } - - // MARK: - Functions - - func setUpCellData(_ postDataModel : PostDataModel) { - var imageURL = postDataModel.postImage - if let url = URL(string: (imageURL)) { - profilePostImage.load(with: url) - } -// if let url = URL(string: imageURL) { -// -// profilePostImage.kf.setImage(with: url) { result in -// switch result { -// case .success(let value): -// print("Image successfully loaded: \(value.image)") -// case .failure(let error): -// print("Image failed to load with error: \(error.localizedDescription)") -// } -// } -// } - } -} diff --git a/pochak/pochak/UI/Profile/MyProfile/ProfileAPI/MyProfilePochakedPostModel.swift b/pochak/pochak/UI/Profile/MyProfile/ProfileAPI/MyProfilePochakedPostModel.swift deleted file mode 100644 index 0c84f1b0..00000000 --- a/pochak/pochak/UI/Profile/MyProfile/ProfileAPI/MyProfilePochakedPostModel.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// MyProfilePochakedPostModel.swift -// pochak -// -// Created by Seo Cindy on 12/28/23. -// - -struct MyProfilePochakPostResponse: Codable { - let isSuccess: Bool - let code: String - let message: String - let result: MyProfilePochakPostModel -} - -struct MyProfilePochakPostModel : Codable { - var pageInfo : PageDataModel - var postList : [PostDataModel] -} diff --git a/pochak/pochak/UI/Profile/MyProfile/ProfileAPI/MyProfilePostDataManager.swift b/pochak/pochak/UI/Profile/MyProfile/ProfileAPI/MyProfilePostDataManager.swift deleted file mode 100644 index ab575ec8..00000000 --- a/pochak/pochak/UI/Profile/MyProfile/ProfileAPI/MyProfilePostDataManager.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// MyProfilePostDataManager.swift -// pochak -// -// Created by Seo Cindy on 12/28/23. -// - -import Foundation -import Alamofire - -struct SimpleJson: Codable { - var isSuccess: Bool - var code: String - var message: String -} - -class MyProfilePostDataManager { - - static let shared = MyProfilePostDataManager() - - func myProfileUserAndPochakedPostDataManager(_ handle : String, _ page : Int, _ completion: @escaping (NetworkResult400) -> Void) { - - let url = "\(APIConstants.baseURL)/api/v2/members/\(handle)?page=\(page)" - - AF.request(url, - method: .get, - encoding: URLEncoding.default, - interceptor: RequestInterceptor.getRequestInterceptor()) - .validate() - .responseDecodable(of: MyProfileUserAndPochakedPostResponse.self) { response in - print(response) - switch response.result { - case .success(let result): - let resultData = result.result - completion(.success(resultData)) - case .failure(let error): - print("myProfileUserAndPochakedPostDataManager error : \(error.localizedDescription)") - if let data = response.data, let errorMessage = String(data: data, encoding: .utf8) { - print("Failure Data: \(errorMessage)") - } - if let statusCode = response.response?.statusCode { - if statusCode == 400 { - completion(.MEMBER4002) - } - } - } - } - } - - func myProfilePochakPostDataManager(_ handle : String, _ page : Int, _ completion: @escaping (MyProfilePochakPostModel) -> Void) { - - let url = "\(APIConstants.baseURLv2)/api/v2/members/\(handle)/upload?page=\(page)" - - AF.request(url, - method: .get, - encoding: URLEncoding.default, - interceptor: RequestInterceptor.getRequestInterceptor()) - .validate() - .responseDecodable(of: MyProfilePochakPostResponse.self) { response in - print(response) - switch response.result { - case .success(let result): - let resultData = result.result - completion(resultData) - case .failure(let error): - print("myProfilePochakPostDataManager error : \(error.localizedDescription)") - guard let data = response.data else { return } - // data - let decoder = JSONDecoder() - if let json = try? decoder.decode(SimpleJson.self, from: data) { - print(">>>>> decoder : \(json.code)") // hyeon - } - if let errorMessage = String(data: data, encoding: .utf8) { - print("Failure Data: \(errorMessage)") - } - } - } - } -} diff --git a/pochak/pochak/UI/Profile/MyProfile/ProfileUpdateAPI/ProfileUpdateDataManager.swift b/pochak/pochak/UI/Profile/MyProfile/ProfileUpdateAPI/ProfileUpdateDataManager.swift deleted file mode 100644 index 7dd5da8a..00000000 --- a/pochak/pochak/UI/Profile/MyProfile/ProfileUpdateAPI/ProfileUpdateDataManager.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// MyProfileUpdateDataManager.swift -// pochak -// -// Created by Seo Cindy on 1/14/24. -// - -import UIKit -import Alamofire - -class ProfileUpdateDataManager{ - static let shared = ProfileUpdateDataManager() - - func updateDataManager(_ name : String, - _ handle : String, - _ message : String, - _ profileImage : UIImage?, - _ completion: @escaping (ProfileUpdateDataModel) -> Void) { - - let accessToken = GetToken.getAccessToken() - - let header : HTTPHeaders = ["Authorization": accessToken, "Content-type": "multipart/form-data"] - let url = "\(APIConstants.baseURL)/api/v2/members/\(handle)" - let requestBody : [String : String] = [ - "name" : name, - "message" : message] - - AF.upload(multipartFormData: { multipartFormData in - // requestBody 추가 - for (key, value) in requestBody { - if let valueData = value.data(using: .utf8) { - multipartFormData.append(valueData, withName: key) - } - } - - // profileImage 추가 - if let image = profileImage?.jpegData(compressionQuality: 0.1) { - multipartFormData.append(image, withName: "profileImage", fileName: "profileImage.jpg", mimeType: "image/jpeg") - } - }, to: url, method: .put, headers: header).validate().responseDecodable(of: ProfileUpdateResponse.self) { response in - switch response.result { - case .success(let result): - let resultData = result.result - print("update 성공!!!!!!!!!!") - print(resultData) - completion(resultData) - case .failure(let error): - print("updateDataManager error : \(error.localizedDescription)") - if let data = response.data, let errorMessage = String(data: data, encoding: .utf8) { - print("Failure Data: \(errorMessage)") - } - } - } - } -} diff --git a/pochak/pochak/UI/Profile/MyProfile/Setting/BlockedUserList/BlockListAPI/BlockedUserListDataManager.swift b/pochak/pochak/UI/Profile/MyProfile/Setting/BlockedUserList/BlockListAPI/BlockedUserListDataManager.swift deleted file mode 100644 index e50e9f38..00000000 --- a/pochak/pochak/UI/Profile/MyProfile/Setting/BlockedUserList/BlockListAPI/BlockedUserListDataManager.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// BlockedUserListDataManager.swift -// pochak -// -// Created by Seo Cindy on 7/5/24. -// - -import Foundation -import Alamofire - -class BlockedUserListDataManager{ - - static let shared = BlockedUserListDataManager() - - func blockedUserListDataManager(_ handle : String,_ page : Int, _ completion: @escaping (BlockedUserDataModel) -> Void) { - - let url = "\(APIConstants.baseURL)/api/v2/members/\(handle)/block?page=\(page)" - - AF.request(url, - method: .get, - encoding: URLEncoding.default, - interceptor: RequestInterceptor.getRequestInterceptor()) - .validate() - .responseDecodable(of: BlockedUserDataResponse.self) { response in - switch response.result { - case .success(let result): - let resultData = result.result - print(">>>>> resultData : \(resultData)") - completion(resultData) - case .failure(let error): - print("Request Fail : blockedUserListDataManager") - if let data = response.data, let errorMessage = String(data: data, encoding: .utf8) { - print("Failure Data: \(errorMessage)") - } - } - } - } -} diff --git a/pochak/pochak/UI/Profile/MyProfile/Setting/BlockedUserList/BlockListAPI/BlockedUserListDataModel.swift b/pochak/pochak/UI/Profile/MyProfile/Setting/BlockedUserList/BlockListAPI/BlockedUserListDataModel.swift deleted file mode 100644 index ce5fa25d..00000000 --- a/pochak/pochak/UI/Profile/MyProfile/Setting/BlockedUserList/BlockListAPI/BlockedUserListDataModel.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// BlockedUserListDataModel.swift -// pochak -// -// Created by Seo Cindy on 7/5/24. -// - -import Foundation - -struct BlockedUserDataResponse : Codable { - var isSuccess: Bool - var code: String - var message: String - var result : BlockedUserDataModel -} - -struct BlockedUserDataModel : Codable { - var pageInfo: BlockedUserPageDataModel - var blockList: [BlockedUserListDataModel] -} - -struct BlockedUserPageDataModel : Codable { - var lastPage : Bool - var totalPages: Int - var totalElements: Int - var size: Int -} - -struct BlockedUserListDataModel : Codable { - var profileImage: String - var handle: String - var name: String -} diff --git a/pochak/pochak/UI/Profile/MyProfile/Setting/LogoutAPI/LogoutDataManager.swift b/pochak/pochak/UI/Profile/MyProfile/Setting/LogoutAPI/LogoutDataManager.swift deleted file mode 100644 index cbac21bb..00000000 --- a/pochak/pochak/UI/Profile/MyProfile/Setting/LogoutAPI/LogoutDataManager.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// LogoutDataManager.swift -// pochak -// -// Created by Seo Cindy on 1/14/24. -// -import Alamofire - -class LogoutDataManager{ - - static let shared = LogoutDataManager() - - func logoutDataManager(_ completion: @escaping (LogoutDataModel) -> Void) { - let url = "\(APIConstants.baseURL)/api/v2/logout" - - AF.request(url, - method: .get, - encoding: URLEncoding.default, - interceptor: RequestInterceptor.getRequestInterceptor()) - .validate() - .responseDecodable(of: LogoutDataModel.self) { response in - switch response.result { - case .success(let result): - print("logout success!") - let resultData = result - completion(resultData) - case .failure(let error): - print("Request Fail : logoutDataManager") - if let data = response.data, let errorMessage = String(data: data, encoding: .utf8) { - print("Failure Data: \(errorMessage)") - } - } - } - } -} diff --git a/pochak/pochak/UI/Profile/MyProfile/Setting/LogoutAPI/LogoutDataModel.swift b/pochak/pochak/UI/Profile/MyProfile/Setting/LogoutAPI/LogoutDataModel.swift deleted file mode 100644 index 70591c7e..00000000 --- a/pochak/pochak/UI/Profile/MyProfile/Setting/LogoutAPI/LogoutDataModel.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// LogoutDataModel.swift -// pochak -// -// Created by Seo Cindy on 1/14/24. -// - -struct LogoutDataModel : Codable { - var isSuccess: Bool - var code: String - var message: String -} diff --git a/pochak/pochak/UI/Profile/MyProfile/Setting/SignoutAPI/SignOutDataManager.swift b/pochak/pochak/UI/Profile/MyProfile/Setting/SignoutAPI/SignOutDataManager.swift deleted file mode 100644 index e405d512..00000000 --- a/pochak/pochak/UI/Profile/MyProfile/Setting/SignoutAPI/SignOutDataManager.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// DeleteAccountDataManager.swift -// pochak -// -// Created by Seo Cindy on 1/16/24. -// - -import Alamofire - -class SignOutDataManager{ - - static let shared = SignOutDataManager() - - func signOutDataManager(_ completion: @escaping (SignOutDataModel) -> Void) { - - let url = "\(APIConstants.baseURL)/api/v2/signout" - - AF.request(url, - method: .delete, - encoding: URLEncoding.default, - interceptor: RequestInterceptor.getRequestInterceptor()) - .validate() - .responseDecodable(of: SignOutDataModel.self) { response in - switch response.result { - case .success(let result): - print("signout success!!!!!!!!!") - let resultData = result - completion(resultData) - case .failure(let error): - print("Request Fail : deleteAccountDataManager") - print(error) - } - } - } -} diff --git a/pochak/pochak/UI/Profile/MyProfile/Setting/SignoutAPI/SignOutDataModel.swift b/pochak/pochak/UI/Profile/MyProfile/Setting/SignoutAPI/SignOutDataModel.swift deleted file mode 100644 index 18fa9b0b..00000000 --- a/pochak/pochak/UI/Profile/MyProfile/Setting/SignoutAPI/SignOutDataModel.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// DeleteAccountDataModel.swift -// pochak -// -// Created by Seo Cindy on 1/16/24. -// - -struct SignOutDataModel : Codable { - var isSuccess: Bool - var code: String - var message: String -} diff --git a/pochak/pochak/UI/Profile/MyProfileTabViewController.swift b/pochak/pochak/UI/Profile/MyProfileTabViewController.swift new file mode 100644 index 00000000..3d52c44b --- /dev/null +++ b/pochak/pochak/UI/Profile/MyProfileTabViewController.swift @@ -0,0 +1,317 @@ +// +// MyProfileTabViewController.swift +// pochak +// +// Created by Seo Cindy on 12/27/23. +// + +import UIKit + +final class MyProfileTabViewController: UIViewController { + + // MARK: - Properties + + private let handle = UserDefaultsManager.getData(type: String.self, forKey: .handle) ?? "" + private let contentScrollView: UIScrollView = { + let scrollView = UIScrollView() + scrollView.translatesAutoresizingMaskIntoConstraints = false + scrollView.showsVerticalScrollIndicator = false + scrollView.backgroundColor = UIColor(named: "gray01") + return scrollView + }() + + // MARK: - Views + + @IBOutlet weak var profileBackground: UIView! + @IBOutlet weak var profileImage: UIImageView! + @IBOutlet weak var followerList: UIStackView! + @IBOutlet weak var followingList: UIStackView! + @IBOutlet weak var whiteBackground1: UIView! + @IBOutlet weak var userHandle: UILabel! + @IBOutlet weak var userName: UILabel! + @IBOutlet weak var userMessage: UILabel! + @IBOutlet weak var postCount: UILabel! + @IBOutlet weak var followerCount: UILabel! + @IBOutlet weak var followingCount: UILabel! + @IBOutlet weak var postListTabmanView: UIView! + @IBOutlet weak var updateProfileBtn: UIButton! + @IBOutlet weak var topUIView: UIView! + @IBOutlet weak var settingBtn: UIButton! + + // MARK: - Lifecycle + + // Container View에 데이터 전달(ViewDidLoad보다 먼저 실행) + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + //storyboard에서 설정한 identifier와 동일한 이름 + if segue.identifier == "embedContainer" { + let postListVC = segue.destination as! PostListViewController + postListVC.handle = handle + } + } + + override func viewDidLoad() { + super.viewDidLoad() + addSubview() + setUpUIConstraints() + setUpRefreshControl() + setUpViewController() + setUpData() + initializeSingleton() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + setUpData() + self.navigationController?.isNavigationBarHidden = true + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + self.navigationController?.isNavigationBarHidden = false + } + + // MARK: - Actions + + @IBAction func clickSettingBtn(_ sender: Any) { + guard let settingsVC = self.storyboard?.instantiateViewController(withIdentifier: "SettingsVC") as? SettingsViewController else { return } + self.navigationController?.pushViewController(settingsVC, animated: true) + } + + @IBAction func updateProfile(_ sender: Any) { + guard let updateProfileVC = self.storyboard?.instantiateViewController(withIdentifier: "UpdateProfileVC") as? UpdateProfileViewController else { return } + self.navigationController?.pushViewController(updateProfileVC, animated: true) + } + + @objc private func viewFollowerTapped() { + guard let followListVC = self.storyboard?.instantiateViewController(withIdentifier: "FollowListVC") + as? FollowListViewController else { return } + followListVC.index = 0 + followListVC.handle = handle + self.navigationController?.pushViewController(followListVC, animated: true) + } + + @objc private func viewFollowingTapped() { + guard let followListVC = self.storyboard?.instantiateViewController(withIdentifier: "FollowListVC") + as? FollowListViewController else { return } + followListVC.index = 1 + followListVC.handle = handle + self.navigationController?.pushViewController(followListVC, animated: true) + } + + @objc private func refreshData(_ sender: Any) { + setUpData() + DispatchQueue.main.async() { + self.contentScrollView.refreshControl?.endRefreshing() + } + } + + @objc private func totalHeightUpdated() { + ProfileDataSingleton.shared.currentTabIndex == 0 ? + (updatePostListTabmanViewHeight(ProfileDataSingleton.shared.firstTabHeight)) : + (updatePostListTabmanViewHeight(ProfileDataSingleton.shared.secondTabHeight)) + } + + // MARK: - Functions + + private func addSubview() { + self.view.addSubview(contentScrollView) + contentScrollView.addSubview(topUIView) + topUIView.addSubview(postListTabmanView) + } + + private func setUpUIConstraints() { + contentScrollView.translatesAutoresizingMaskIntoConstraints = false + topUIView.translatesAutoresizingMaskIntoConstraints = false + postListTabmanView.translatesAutoresizingMaskIntoConstraints = false + profileBackground.translatesAutoresizingMaskIntoConstraints = false + whiteBackground1.translatesAutoresizingMaskIntoConstraints = false + userName.translatesAutoresizingMaskIntoConstraints = false + userHandle.translatesAutoresizingMaskIntoConstraints = false + userMessage.translatesAutoresizingMaskIntoConstraints = false + profileImage.translatesAutoresizingMaskIntoConstraints = false + updateProfileBtn.translatesAutoresizingMaskIntoConstraints = false + settingBtn.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + contentScrollView.topAnchor.constraint(equalTo: view.topAnchor), + contentScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + contentScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + contentScrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + + let scrollContentGuide = contentScrollView.contentLayoutGuide + NSLayoutConstraint.activate([ + topUIView.topAnchor.constraint(equalTo: scrollContentGuide.topAnchor), + topUIView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + topUIView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + topUIView.bottomAnchor.constraint(equalTo: postListTabmanView.bottomAnchor), // Dynamic height for topUIView + + userHandle.topAnchor.constraint(equalTo: topUIView.topAnchor, constant: 13), + userHandle.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), + + settingBtn.trailingAnchor.constraint(equalTo: view.trailingAnchor), + settingBtn.centerYAnchor.constraint(equalTo: userHandle.centerYAnchor), + + profileBackground.topAnchor.constraint(equalTo: userHandle.bottomAnchor, constant: 28), + profileBackground.leadingAnchor.constraint(equalTo: topUIView.leadingAnchor, constant: 20), + + userName.topAnchor.constraint(equalTo: profileBackground.topAnchor, constant: 15), + userName.leadingAnchor.constraint(equalTo: profileBackground.trailingAnchor, constant: 20), + userName.trailingAnchor.constraint(equalTo: topUIView.trailingAnchor), + + profileImage.centerXAnchor.constraint(equalTo: profileBackground.centerXAnchor), + profileImage.centerYAnchor.constraint(equalTo: profileBackground.centerYAnchor), + + updateProfileBtn.bottomAnchor.constraint(equalTo: profileBackground.bottomAnchor, constant: -5), + updateProfileBtn.trailingAnchor.constraint(equalTo: profileBackground.trailingAnchor, constant: -5), + + userMessage.topAnchor.constraint(equalTo: userName.bottomAnchor, constant: 10), + userMessage.leadingAnchor.constraint(equalTo: profileBackground.trailingAnchor, constant: 20), + userMessage.trailingAnchor.constraint(equalTo: topUIView.trailingAnchor), + + whiteBackground1.topAnchor.constraint(equalTo: profileBackground.bottomAnchor, constant: 20), + whiteBackground1.leadingAnchor.constraint(equalTo: profileBackground.leadingAnchor), + whiteBackground1.centerXAnchor.constraint(equalTo: topUIView.centerXAnchor), + whiteBackground1.trailingAnchor.constraint(equalTo: topUIView.trailingAnchor, constant: -20), + + postListTabmanView.topAnchor.constraint(equalTo: whiteBackground1.bottomAnchor, constant: 5), + postListTabmanView.leadingAnchor.constraint(equalTo: topUIView.leadingAnchor), + postListTabmanView.trailingAnchor.constraint(equalTo: topUIView.trailingAnchor), + postListTabmanView.heightAnchor.constraint(equalToConstant: view.frame.height - 270) + ]) + } + + private func setUpRefreshControl() { + contentScrollView.refreshControl = UIRefreshControl() + contentScrollView.refreshControl?.addTarget(self, action: #selector(refreshData(_:)), for: .valueChanged) + } + + private func setUpViewController() { + self.navigationController?.isNavigationBarHidden = true + profileBackground.layer.cornerRadius = 58 + profileImage.layer.cornerRadius = 55 + whiteBackground1.layer.cornerRadius = 8 + viewFollowerList() + viewFollowingList() + userHandle.text = "@\(handle)" + let backBarButtonItem = UIBarButtonItem(title: nil, style: .plain, target: nil, action: nil) + backBarButtonItem.tintColor = .black + self.navigationItem.backBarButtonItem = backBarButtonItem + NotificationCenter.default.addObserver(self, selector: #selector(totalHeightUpdated), name: .didUpdateTotalHeight, object: nil) + } + + private func setUpData() { + let request = ProfileRetrievalRequest(page: 0) + ProfileService.getProfile(handle: handle, request: request) { data, failed in + guard let data = data else { + switch failed { + case .clientError: + self.present(UIAlertController.networkErrorAlert(title: "유효하지 않은 멤버의 handle입니다."), animated: true) + case .disconnected: + self.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .serverError: + self.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .unknownError: + self.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + default: + self.present(UIAlertController.networkErrorAlert(title: "요청에 실패하였습니다."), animated: true) + } + return + } + + // 프로필 이미지 로드 + if let url = URL(string: data.result.profileImage ?? "") { + self.profileImage.load(with: url) + } + + // 필요한 데이터 뷰에 반영 + self.setUpResponseData(data.result) + + // UserDefaultsManager에 데이터 저장 후 관리 + self.setUpUserDefaults(data.result) + } + } + + private func initializeSingleton() { + ProfileDataSingleton.shared.currentTabIndex = 0 + ProfileDataSingleton.shared.firstTabHeight = 0.0 + ProfileDataSingleton.shared.secondTabHeight = 0.0 + ProfileDataSingleton.shared.firstTabIsCurrentlyFetching = false + ProfileDataSingleton.shared.secondTabIsCurrentlyFetching = false + ProfileDataSingleton.shared.firstTabIsLastPage = false + ProfileDataSingleton.shared.secondTabIsLastPage = false + } + + private func viewFollowerList() { /* UITapGestureRecognizer 사용 */ + let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(viewFollowerTapped)) + followerList.addGestureRecognizer(tapGestureRecognizer) + } + + private func viewFollowingList() { /* UITapGestureRecognizer 사용 */ + let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(viewFollowingTapped)) + followingList.addGestureRecognizer(tapGestureRecognizer) + } + + private func setUpResponseData(_ responseData: ProfileRetrievalResult) { + self.profileImage.contentMode = .scaleAspectFill + self.userName.text = String(responseData.name ?? "") + self.userMessage.text = String(responseData.message ?? "") + self.postCount.text = String(responseData.totalPostNum ?? 0) + self.followerCount.text = String(responseData.followerCount ?? 0) + self.followingCount.text = String(responseData.followingCount ?? 0) + } + + private func setUpUserDefaults(_ responseData: ProfileRetrievalResult) { + UserDefaultsManager.setData(value: responseData.name, key: .name) + UserDefaultsManager.setData(value: responseData.message, key: .message) + UserDefaultsManager.setData(value: responseData.profileImage, key: .profileImgUrl) + } + + private func updatePostListTabmanViewHeight(_ height: CGFloat) { + postListTabmanView.constraints.forEach { constraint in + if constraint.firstAttribute == .height { + if height >= constraint.constant && constraint.constant != 0 { + constraint.isActive = false + + // 새로운 높이 제약 조건 추가 + postListTabmanView.heightAnchor.constraint(equalToConstant: height).isActive = true + UIView.animate(withDuration: 0.3, animations: { + self.contentScrollView.layoutIfNeeded() + }) { _ in + // ScrollView의 contentSize 업데이트 + self.contentScrollView.contentSize = CGSize( + width: self.contentScrollView.frame.width, + height: self.topUIView.frame.height + ) + } + } else { + print("no posts yet") + } + } + } + } + + deinit { + NotificationCenter.default.removeObserver(self) + } +} + +// MARK: - Extension: CustomAlertDelegate, SecondViewControllerDelegate + +extension MyProfileTabViewController: UIScrollViewDelegate { + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if (contentScrollView.contentOffset.y > (contentScrollView.contentSize.height - contentScrollView.frame.size.height)) { + if (ProfileDataSingleton.shared.currentTabIndex == 0 && + !ProfileDataSingleton.shared.firstTabIsCurrentlyFetching && + !ProfileDataSingleton.shared.firstTabIsLastPage) { + NotificationCenter.default.post(name: .didHitBottom, object: nil) + } else if (ProfileDataSingleton.shared.currentTabIndex == 1 && + !ProfileDataSingleton.shared.secondTabIsCurrentlyFetching && + !ProfileDataSingleton.shared.secondTabIsLastPage) { + NotificationCenter.default.post(name: .didHitBottom, object: nil) + } + } + } +} + diff --git a/pochak/pochak/UI/Profile/OtherProfile/OtherUserProfileViewController.swift b/pochak/pochak/UI/Profile/OtherProfile/OtherUserProfileViewController.swift deleted file mode 100644 index a9a50a75..00000000 --- a/pochak/pochak/UI/Profile/OtherProfile/OtherUserProfileViewController.swift +++ /dev/null @@ -1,268 +0,0 @@ -// -// OtherUseProfileViewController.swift -// pochak -// -// Created by Seo Cindy on 1/16/24. -// - -import UIKit - -protocol SecondViewControllerDelegate: AnyObject { - func dismissSecondViewController() -} - -class OtherUserProfileViewController: UIViewController { - - // MARK: - Properties - - let socialId = UserDefaultsManager.getData(type: String.self, forKey: .socialId) - var receivedHandle: String? - var receivedFollowerCount: Int = 0 - var receivedFollowingCount: Int = 0 - var receivedIsFollow: Bool? - var searchBlockedUser: Bool = false - lazy var moreButton: UIBarButtonItem = { // 업로드 버튼 - let barButton = UIBarButtonItem(image: UIImage(named: "moreButtonIcon"), style: .plain, target: self, action: #selector(moreButtonPressed)) - return barButton - }() - - // MARK: - Views - - @IBOutlet weak var profileBackground: UIView! - @IBOutlet weak var profileImage: UIImageView! - @IBOutlet weak var followerList: UIStackView! - @IBOutlet weak var followingList: UIStackView! - @IBOutlet weak var whiteBackground: UIView! - @IBOutlet weak var userName: UILabel! - @IBOutlet weak var userMessage: UILabel! - @IBOutlet weak var postCount: UILabel! - @IBOutlet weak var followerCount: UILabel! - @IBOutlet weak var followingCount: UILabel! - @IBOutlet weak var followToggleBtn: UIButton! - @IBOutlet weak var postListTabmanView: UIView! - @IBOutlet weak var updateProfileBtn: UIButton! - - // MARK: - Lifecycle - - // Container View에 데이터 전달(ViewDidLoad보다 먼저 실행) - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - //storyboard에서 설정한 identifier와 동일한 이름 - if segue.identifier == "embedContainer" { - let postListVC = segue.destination as! PostListViewController - postListVC.handle = receivedHandle - } - } - - override func viewDidLoad() { - super.viewDidLoad() - setUpNavigationBar() - setUpViewController() - setUpData() - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - // VC가 나타날 때 네비게이션바 숨김 - self.navigationController?.isNavigationBarHidden = false - self.navigationController?.navigationBar.backgroundColor = UIColor.clear - setUpData() - } - - // MARK: - Actions - - @IBAction func followToggleButton(_ sender: UIButton) { - if receivedIsFollow! { - showAlert(alertType: .confirmAndCancel, - titleText: "팔로우를 취소할까요?", - messageText: "", - cancelButtonText: "나가기", - confirmButtonText: "계속하기" - ) - } else { - FollowToggleDataManager.shared.followToggleDataManager(self.receivedHandle ?? "", { resultData in - print(resultData.message) - }) - self.receivedIsFollow = true - sender.setTitle("팔로잉", for: .normal) - sender.backgroundColor = UIColor(named: "gray03") - } - } - - @objc private func viewFollowerTapped() { - guard let followListVC = self.storyboard?.instantiateViewController(withIdentifier: "FollowListVC") as? FollowListViewController else {return} - followListVC.index = 0 - followListVC.handle = receivedHandle ?? "" - followListVC.followerCount = receivedFollowerCount - followListVC.followingCount = receivedFollowingCount - self.navigationController?.pushViewController(followListVC, animated: true) - } - - @objc private func viewFollowingTapped() { - guard let followListVC = self.storyboard?.instantiateViewController(withIdentifier: "FollowListVC") as? FollowListViewController else {return} - followListVC.index = 1 - followListVC.handle = receivedHandle ?? "" - followListVC.followerCount = receivedFollowerCount - followListVC.followingCount = receivedFollowingCount - self.navigationController?.pushViewController(followListVC, animated: true) - } - - @objc func moreButtonPressed() { - guard let profileMenuVC = self.storyboard?.instantiateViewController(withIdentifier: "profileMenuVC") as? ProfileMenuViewController else {return} - let sheet = profileMenuVC.sheetPresentationController - profileMenuVC.receivedHandle = receivedHandle - - let multiplier = 0.25 - let fraction = UISheetPresentationController.Detent.custom { context in - self.view.bounds.height * multiplier - } - sheet?.detents = [fraction] - sheet?.prefersGrabberVisible = true - sheet?.prefersScrollingExpandsWhenScrolledToEdge = false - - // delegate 채택 - profileMenuVC.delegate = self - present(profileMenuVC, animated: true) - } - - // MARK: - Functions - - @IBAction func updateProfile(_ sender: Any) { - guard let updateProfileVC = self.storyboard?.instantiateViewController(withIdentifier: "UpdateProfileVC") as? UpdateProfileViewController else {return} - self.navigationController?.pushViewController(updateProfileVC, animated: true) - } - - private func setUpNavigationBar() { - self.navigationController?.isNavigationBarHidden = false - self.navigationController?.navigationBar.backgroundColor = UIColor.clear - self.navigationItem.title = "@" + (receivedHandle ?? "handle not found") - self.navigationItem.rightBarButtonItem = moreButton - } - - private func setUpViewController() { - profileBackground.layer.cornerRadius = 58 - profileImage.layer.cornerRadius = 55 - profileImage.contentMode = .scaleAspectFill - - whiteBackground.layer.cornerRadius = 8 - followToggleBtn.layer.cornerRadius = 8 - - viewFollowerList() - viewFollowingList() - - postListTabmanView.translatesAutoresizingMaskIntoConstraints = false - postListTabmanView.topAnchor.constraint(equalTo: self.followToggleBtn.bottomAnchor, constant: 5).isActive = true - postListTabmanView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0).isActive = true - postListTabmanView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0).isActive = true - postListTabmanView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0).isActive = true - - updateProfileBtn.layer.isHidden = true - } - - private func viewFollowerList() { // UITapGestureRecognizer 사용 - let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(viewFollowerTapped)) - followerList.addGestureRecognizer(tapGestureRecognizer) - } - - private func viewFollowingList() { // UITapGestureRecognizer 사용 - let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(viewFollowingTapped)) - followingList.addGestureRecognizer(tapGestureRecognizer) - } - - private func setUpData() { - MyProfilePostDataManager.shared.myProfileUserAndPochakedPostDataManager(receivedHandle ?? "",0,{ - response in - switch response { - case .success(let resultData): - let currentHandle = UserDefaultsManager.getData(type: String.self, forKey: .handle) - let imageURL = resultData.profileImage ?? "" - if let url = URL(string: imageURL) { - self.profileImage.kf.setImage(with: url) { result in - switch result { - case .success(let value): - print("Image successfully loaded: \(value.image)") - case .failure(let error): - print("Image failed to load with error: \(error.localizedDescription)") - } - } - } - - // 필요한 데이터 뷰에 반영 - self.userName.text = String(resultData.name ?? "") - self.userMessage.text = String(resultData.message ?? "") - self.postCount.text = String(resultData.totalPostNum ?? 0) - self.followerCount.text = String(resultData.followerCount ?? 0) - self.followingCount.text = String(resultData.followingCount ?? 0) - self.receivedFollowerCount = resultData.followerCount ?? 0 - self.receivedFollowingCount = resultData.followingCount ?? 0 - self.receivedIsFollow = resultData.isFollow - - // 버튼 설정 - if currentHandle == self.receivedHandle{ - /// 내 프로필 조회한 경우 - self.followToggleBtn.layer.isHidden = true - self.updateProfileBtn.layer.isHidden = false - self.postListTabmanView.topAnchor.constraint(equalTo: self.whiteBackground.bottomAnchor, constant: 5).isActive = true - self.moreButton.isHidden = true - - } else { - self.followToggleBtn.setTitleColor(UIColor.white, for: .normal) - self.followToggleBtn.titleLabel?.font = UIFont(name: "Pretendard-Bold", size: 16) // 폰트 설정 - self.followToggleBtn.layer.cornerRadius = 5 - - if resultData.isFollow == true { - /// 팔로우 중인 유저인 경우 - self.followToggleBtn.setTitle("팔로잉", for: .normal) - self.followToggleBtn.backgroundColor = UIColor(named: "gray03") - - } else { - /// 팔로우하고 있지 않은 유저인 경우 - self.followToggleBtn.setTitle("팔로우", for: .normal) - self.followToggleBtn.backgroundColor = UIColor(named: "yellow00") - - } - } - case .MEMBER4002: - self.navigationController?.popViewController(animated: true) - print("유효하지 않은 멤버의 handle입니다.") - self.navigationItem.title = "" - self.searchBlockedUser = true - self.showAlert(alertType: .confirmOnly, - titleText: "차단한 유저의 프로필입니다.", - messageText: "차단해제를 원하시면\n설정 탭의 차단관리 페이지를 확인해주세요.", - cancelButtonText: "", - confirmButtonText: "확인" - ) - } - }) - } -} - -// MARK: - Extension : CustomAlertDelegate, SecondViewControllerDelegate - -extension OtherUserProfileViewController: CustomAlertDelegate { - - func confirmAction() { - if searchBlockedUser { - print("confirm selected!") - } else { - FollowToggleDataManager.shared.followToggleDataManager(receivedHandle ?? "", { resultData in - print(resultData.message) - }) - self.receivedIsFollow = false - followToggleBtn.setTitle("팔로우", for: .normal) - followToggleBtn.backgroundColor = UIColor(named: "yellow00") - } - } - - func cancel() { - print("취소하기 선택됨") - } -} - -extension OtherUserProfileViewController: SecondViewControllerDelegate { - - // 차단한 유저의 프로필 조회 시 VC를 dismiss하여 전 화면으로 돌아감 - func dismissSecondViewController() { - self.navigationController?.popViewController(animated: true) - } -} diff --git a/pochak/pochak/UI/Profile/OtherProfile/ProfileMenu/BlockAPI/BlockDataManager.swift b/pochak/pochak/UI/Profile/OtherProfile/ProfileMenu/BlockAPI/BlockDataManager.swift deleted file mode 100644 index b8ab6ac3..00000000 --- a/pochak/pochak/UI/Profile/OtherProfile/ProfileMenu/BlockAPI/BlockDataManager.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// BlockDataManager.swift -// pochak -// -// Created by Seo Cindy on 6/30/24. -// - -import Foundation -import Alamofire - -class BlockDataManager { - - static let shared = BlockDataManager() - - func blockDataManager(_ handle : String, _ completion: @escaping (BlockDataResponse) -> Void) { - - let url = "\(APIConstants.baseURL)/api/v2/members/\(handle)/block" - - AF.request(url, - method: .post, - encoding: URLEncoding.default, - interceptor: RequestInterceptor.getRequestInterceptor()) - .validate() - .responseDecodable(of: BlockDataResponse.self) { response in - switch response.result { - case .success(let result): - completion(result) - case .failure(let error): - print("Request Fail blockDataManager : \(error.localizedDescription)") - if let data = response.data, let errorMessage = String(data: data, encoding: .utf8) { - print("Failure Data: \(errorMessage)") - } - } - } - } -} diff --git a/pochak/pochak/UI/Profile/OtherProfile/ProfileMenu/BlockAPI/UnblockDataManager.swift b/pochak/pochak/UI/Profile/OtherProfile/ProfileMenu/BlockAPI/UnblockDataManager.swift deleted file mode 100644 index a8ed09f1..00000000 --- a/pochak/pochak/UI/Profile/OtherProfile/ProfileMenu/BlockAPI/UnblockDataManager.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// BlockDataManager.swift -// pochak -// -// Created by Seo Cindy on 6/30/24. -// - -import Foundation -import Alamofire - -class UnBlockDataManager { - - static let shared = UnBlockDataManager() - - func unBlockDataManager(_ handle : String, _ blockedMemberHandle : String, _ completion: @escaping (UnBlockDataResponse) -> Void) { - - let url = "\(APIConstants.baseURL)/api/v2/members/\(handle)/block?blockedMemberHandle=\(blockedMemberHandle)" - - AF.request(url, - method: .delete, - encoding: URLEncoding.default, - interceptor: RequestInterceptor.getRequestInterceptor()) - .validate() - .responseDecodable(of: UnBlockDataResponse.self) { response in - switch response.result { - case .success(let result): - completion(result) - case .failure(let error): - print("Request Fail unBlockDataManager : \(error.localizedDescription)") - if let data = response.data, let errorMessage = String(data: data, encoding: .utf8) { - print("Failure Data: \(errorMessage)") - } - } - } - } -} diff --git a/pochak/pochak/UI/Profile/OtherProfile/ProfileMenu/ProfileMenuViewController.swift b/pochak/pochak/UI/Profile/OtherProfile/ProfileMenu/ProfileMenuViewController.swift deleted file mode 100644 index 20aba28d..00000000 --- a/pochak/pochak/UI/Profile/OtherProfile/ProfileMenu/ProfileMenuViewController.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// ProfileMenuViewController.swift -// pochak -// -// Created by Seo Cindy on 6/29/24. -// - -import UIKit - -class ProfileMenuViewController: UIViewController { - - // MARK: - Properties - - let userHandle = UserDefaultsManager.getData(type: String.self, forKey: .handle) - var receivedHandle: String? - weak var delegate: SecondViewControllerDelegate? - - // MARK: - Views - - @IBOutlet weak var userBlockBtn: UIButton! - @IBOutlet weak var cancelButton: UIButton! - - // MARK: - Lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - } - - // MARK: - Actions - - @IBAction func userBlockBtnClicked(_ sender: Any) { - showAlert(alertType: .confirmAndCancel, - titleText: "유저를 차단하시겠습니까?", - messageText: "유저를 차단하면, 팔로워와 관련된 \n사진 및 소식을 접할 수 없습니다.", - cancelButtonText: "취소", - confirmButtonText: "차단하기" - ) - } - - @IBAction func cancelBtnClicked(_ sender: Any) { - self.dismiss(animated: true, completion: nil) - } -} - -// MARK: - Extension : CustomAlertDelegate - -extension ProfileMenuViewController: CustomAlertDelegate { - - func confirmAction() { - BlockDataManager.shared.blockDataManager(receivedHandle ?? "", { resultData in - print(resultData.message) - self.userBlockBtn.setTitle("차단취소", for: .normal) - self.delegate?.dismissSecondViewController() - self.dismiss(animated: true, completion: nil) - }) - } - - func cancel() { - print("취소하기 선택됨") - } -} diff --git a/pochak/pochak/UI/Profile/OtherUserProfileViewController.swift b/pochak/pochak/UI/Profile/OtherUserProfileViewController.swift new file mode 100644 index 00000000..49542c83 --- /dev/null +++ b/pochak/pochak/UI/Profile/OtherUserProfileViewController.swift @@ -0,0 +1,456 @@ +// +// OtherUseProfileViewController.swift +// pochak +// +// Created by Seo Cindy on 1/16/24. +// + +import UIKit +import Foundation + +protocol SecondViewControllerDelegate: AnyObject { + func dismissSecondViewController() +} + +final class OtherUserProfileViewController: UIViewController { + + // MARK: - Properties + + var receivedHandle: String? + var receivedFollowerCount: Int = 0 + var receivedFollowingCount: Int = 0 + var receivedIsFollow: Bool? + private let socialId = UserDefaultsManager.getData(type: String.self, forKey: .socialId) + private var searchBlockedUser: Bool = false + + private lazy var moreButton: UIBarButtonItem = { // 업로드 버튼 + let barButton = UIBarButtonItem(image: UIImage(named: "moreButtonIcon"), style: .plain, target: self, action: #selector(moreButtonPressed)) + return barButton + }() + + private let contentScrollView: UIScrollView = { + let scrollView = UIScrollView() + scrollView.translatesAutoresizingMaskIntoConstraints = false + scrollView.showsVerticalScrollIndicator = false + scrollView.backgroundColor = UIColor(named: "gray01") + return scrollView + }() + + // MARK: - Views + + @IBOutlet weak var profileBackground: UIView! + @IBOutlet weak var profileImage: UIImageView! + @IBOutlet weak var followerList: UIStackView! + @IBOutlet weak var followingList: UIStackView! + @IBOutlet weak var whiteBackground: UIView! + @IBOutlet weak var userName: UILabel! + @IBOutlet weak var userMessage: UILabel! + @IBOutlet weak var postCount: UILabel! + @IBOutlet weak var followerCount: UILabel! + @IBOutlet weak var followingCount: UILabel! + @IBOutlet weak var followToggleBtn: UIButton! + @IBOutlet weak var postListTabmanView: UIView! + @IBOutlet weak var updateProfileBtn: UIButton! + @IBOutlet weak var topUIView: UIView! + + // MARK: - Lifecycle + + // Container View에 데이터 전달(ViewDidLoad보다 먼저 실행) + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + //storyboard에서 설정한 identifier와 동일한 이름 + if segue.identifier == "embedContainer" { + let postListVC = segue.destination as! PostListViewController + postListVC.handle = receivedHandle + } + } + + override func viewDidLoad() { + super.viewDidLoad() + addSubview() + setUpUIConstraints() + setUpRefreshControl() + setUpNavigationBar() + setUpViewController() + setUpData() + initializeSingleton() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + // VC가 나타날 때 네비게이션바 숨김 + self.navigationController?.isNavigationBarHidden = false + self.navigationController?.navigationBar.backgroundColor = UIColor.clear + setUpData() + } + + // MARK: - Actions + + @IBAction func followToggleButton(_ sender: UIButton) { + if receivedIsFollow! { + showAlert(alertType: .confirmAndCancel, + titleText: "팔로우를 취소할까요?", + messageText: "", + cancelButtonText: "나가기", + confirmButtonText: "계속하기") + } else { + if let handle = receivedHandle { + UserService.postFollowRequest(handle: handle) { [weak self] data, failed in + guard let data = data else { + switch failed { + case .disconnected: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .serverError: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .unknownError: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + default: + self?.present(UIAlertController.networkErrorAlert(title: "요청에 실패하였습니다."), animated: true) + } + return + } + self?.receivedIsFollow = true + sender.setTitle("팔로잉", for: .normal) + sender.backgroundColor = UIColor(named: "gray03") + self?.setUpData() + } + } else { + print("No handle received") + } + } + } + + @IBAction func updateProfile(_ sender: Any) { + guard let updateProfileVC = self.storyboard?.instantiateViewController(withIdentifier: "UpdateProfileVC") as? UpdateProfileViewController else { return } + self.navigationController?.pushViewController(updateProfileVC, animated: true) + } + + @objc private func viewFollowerTapped() { + guard let followListVC = self.storyboard?.instantiateViewController(withIdentifier: "FollowListVC") as? FollowListViewController else { return } + followListVC.index = 0 + followListVC.handle = receivedHandle ?? "" + self.navigationController?.pushViewController(followListVC, animated: true) + } + + @objc private func viewFollowingTapped() { + guard let followListVC = self.storyboard?.instantiateViewController(withIdentifier: "FollowListVC") + as? FollowListViewController else { return } + followListVC.index = 1 + followListVC.handle = receivedHandle ?? "" + self.navigationController?.pushViewController(followListVC, animated: true) + } + + @objc private func moreButtonPressed() { + guard let profileMenuVC = self.storyboard?.instantiateViewController(withIdentifier: "profileMenuVC") + as? ProfileMenuViewController else { return } + let sheet = profileMenuVC.sheetPresentationController + profileMenuVC.receivedHandle = receivedHandle + + let multiplier = 0.25 + let fraction = UISheetPresentationController.Detent.custom { context in + self.view.bounds.height * multiplier + } + sheet?.detents = [fraction] + sheet?.prefersGrabberVisible = true + sheet?.prefersScrollingExpandsWhenScrolledToEdge = false + + profileMenuVC.delegate = self + present(profileMenuVC, animated: true) + } + + @objc private func refreshData(_ sender: Any) { + setUpData() + DispatchQueue.main.async() { + self.contentScrollView.refreshControl?.endRefreshing() + } + } + + @objc private func totalHeightUpdated() { + ProfileDataSingleton.shared.currentTabIndex == 0 ? + (updatePostListTabmanViewHeight(ProfileDataSingleton.shared.firstTabHeight)) : + (updatePostListTabmanViewHeight(ProfileDataSingleton.shared.secondTabHeight)) + } + + // MARK: - Functions + + private func addSubview() { + self.view.addSubview(contentScrollView) + contentScrollView.addSubview(topUIView) + topUIView.addSubview(postListTabmanView) + } + + private func setUpUIConstraints() { + contentScrollView.translatesAutoresizingMaskIntoConstraints = false + topUIView.translatesAutoresizingMaskIntoConstraints = false + postListTabmanView.translatesAutoresizingMaskIntoConstraints = false + profileBackground.translatesAutoresizingMaskIntoConstraints = false + whiteBackground.translatesAutoresizingMaskIntoConstraints = false + userName.translatesAutoresizingMaskIntoConstraints = false + userMessage.translatesAutoresizingMaskIntoConstraints = false + followToggleBtn.translatesAutoresizingMaskIntoConstraints = false + profileImage.translatesAutoresizingMaskIntoConstraints = false + updateProfileBtn.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + contentScrollView.topAnchor.constraint(equalTo: view.topAnchor), + contentScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + contentScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + contentScrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + + let scrollContentGuide = contentScrollView.contentLayoutGuide + NSLayoutConstraint.activate([ + topUIView.topAnchor.constraint(equalTo: scrollContentGuide.topAnchor), + topUIView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + topUIView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + topUIView.bottomAnchor.constraint(equalTo: postListTabmanView.bottomAnchor), // Dynamic height for topUIView + + profileBackground.topAnchor.constraint(equalTo: topUIView.topAnchor, constant: 20), + profileBackground.leadingAnchor.constraint(equalTo: topUIView.leadingAnchor, constant: 20), + + userName.topAnchor.constraint(equalTo: profileBackground.topAnchor, constant: 15), + userName.leadingAnchor.constraint(equalTo: profileBackground.trailingAnchor, constant: 20), + userName.trailingAnchor.constraint(equalTo: topUIView.trailingAnchor), + + profileImage.centerXAnchor.constraint(equalTo: profileBackground.centerXAnchor), + profileImage.centerYAnchor.constraint(equalTo: profileBackground.centerYAnchor), + + updateProfileBtn.bottomAnchor.constraint(equalTo: profileBackground.bottomAnchor, constant: -5), + updateProfileBtn.trailingAnchor.constraint(equalTo: profileBackground.trailingAnchor, constant: -5), + + userMessage.topAnchor.constraint(equalTo: userName.bottomAnchor, constant: 10), + userMessage.leadingAnchor.constraint(equalTo: profileBackground.trailingAnchor, constant: 20), + userMessage.trailingAnchor.constraint(equalTo: topUIView.trailingAnchor), + + whiteBackground.topAnchor.constraint(equalTo: profileBackground.bottomAnchor, constant: 20), + whiteBackground.leadingAnchor.constraint(equalTo: profileBackground.leadingAnchor), + whiteBackground.centerXAnchor.constraint(equalTo: topUIView.centerXAnchor), + whiteBackground.trailingAnchor.constraint(equalTo: topUIView.trailingAnchor, constant: -20), + + followToggleBtn.topAnchor.constraint(equalTo: whiteBackground.bottomAnchor, constant: 9), + followToggleBtn.leadingAnchor.constraint(equalTo: whiteBackground.leadingAnchor), + followToggleBtn.trailingAnchor.constraint(equalTo: whiteBackground.trailingAnchor), + + postListTabmanView.topAnchor.constraint(equalTo: followToggleBtn.bottomAnchor, constant: 5), + postListTabmanView.leadingAnchor.constraint(equalTo: topUIView.leadingAnchor), + postListTabmanView.trailingAnchor.constraint(equalTo: topUIView.trailingAnchor), + postListTabmanView.heightAnchor.constraint(equalToConstant: view.frame.height - 270) + ]) + } + + private func setUpRefreshControl() { + contentScrollView.refreshControl = UIRefreshControl() + contentScrollView.refreshControl?.addTarget(self, action: #selector(refreshData(_:)), for: .valueChanged) + } + + private func setUpNavigationBar() { + navigationController?.isNavigationBarHidden = false + navigationController?.hidesBarsOnSwipe = true + navigationController?.navigationBar.backgroundColor = UIColor.clear + navigationItem.title = "@" + (receivedHandle ?? "handle not found") + navigationItem.rightBarButtonItem = moreButton + } + + private func setUpViewController() { + profileBackground.layer.cornerRadius = 58 + profileImage.layer.cornerRadius = 55 + profileImage.contentMode = .scaleAspectFill + whiteBackground.layer.cornerRadius = 8 + followToggleBtn.layer.cornerRadius = 8 + viewFollowerList() + viewFollowingList() + updateProfileBtn.layer.isHidden = true + contentScrollView.delegate = self + NotificationCenter.default.addObserver(self, selector: #selector(totalHeightUpdated), name: .didUpdateTotalHeight, object: nil) + } + + private func setUpData() { + let request = ProfileRetrievalRequest(page: 0) + if let handle = receivedHandle { + ProfileService.getProfile(handle: handle, request: request) { data, failed in + guard let data = data else { + switch failed { + case .clientError: + self.navigationItem.title = "" + self.searchBlockedUser = true + self.showAlert(alertType: .confirmOnly, + titleText: "차단한 유저의 프로필입니다.", + messageText: "차단해제를 원하시면\n설정 탭의 차단관리 페이지를 확인해주세요.", + cancelButtonText: "", + confirmButtonText: "확인") + case .disconnected: + self.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .serverError: + self.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .unknownError: + self.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + default: + self.present(UIAlertController.networkErrorAlert(title: "요청에 실패하였습니다."), animated: true) + } + return + } + + // 프로필 이미지 로드 + if let url = URL(string: data.result.profileImage ?? "") { + self.profileImage.load(with: url) + } + + // 필요한 데이터 뷰에 반영 + self.setUpResponseData(data.result) + + // 팔로우 버튼 설정 + self.setUpFollowBtn(data.result) + } + } else { + print("No handle received") + } + } + + private func initializeSingleton() { + ProfileDataSingleton.shared.currentTabIndex = 0 + ProfileDataSingleton.shared.firstTabHeight = 0.0 + ProfileDataSingleton.shared.secondTabHeight = 0.0 + ProfileDataSingleton.shared.firstTabIsCurrentlyFetching = false + ProfileDataSingleton.shared.secondTabIsCurrentlyFetching = false + ProfileDataSingleton.shared.firstTabIsLastPage = false + ProfileDataSingleton.shared.secondTabIsLastPage = false + } + + private func viewFollowerList() { // UITapGestureRecognizer 사용 + let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(viewFollowerTapped)) + followerList.addGestureRecognizer(tapGestureRecognizer) + } + + private func viewFollowingList() { // UITapGestureRecognizer 사용 + let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(viewFollowingTapped)) + followingList.addGestureRecognizer(tapGestureRecognizer) + } + + private func setUpResponseData(_ responseData: ProfileRetrievalResult) { + self.userName.text = String(responseData.name ?? "") + self.userMessage.text = String(responseData.message ?? "") + self.postCount.text = String(responseData.totalPostNum ?? 0) + self.followerCount.text = String(responseData.followerCount ?? 0) + self.followingCount.text = String(responseData.followingCount ?? 0) + self.receivedFollowerCount = responseData.followerCount ?? 0 + self.receivedFollowingCount = responseData.followingCount ?? 0 + self.receivedIsFollow = responseData.isFollow + } + + private func setUpFollowBtn(_ responseData: ProfileRetrievalResult) { + let currentHandle = UserDefaultsManager.getData(type: String.self, forKey: .handle) + if currentHandle == self.receivedHandle { + /// 내 프로필 조회한 경우 + self.followToggleBtn.layer.isHidden = true + self.updateProfileBtn.layer.isHidden = false + self.postListTabmanView.topAnchor.constraint(equalTo: self.whiteBackground.bottomAnchor, constant: 5).isActive = true + self.moreButton.isHidden = true + + } else { + self.followToggleBtn.setTitleColor(UIColor.white, for: .normal) + self.followToggleBtn.titleLabel?.font = UIFont(name: "Pretendard-Bold", size: 16) + self.followToggleBtn.layer.cornerRadius = 5 + + if responseData.isFollow == true { + /// 팔로우 중인 유저인 경우 + self.followToggleBtn.setTitle("팔로잉", for: .normal) + self.followToggleBtn.backgroundColor = UIColor(named: "gray03") + + } else { + /// 팔로우하고 있지 않은 유저인 경우 + self.followToggleBtn.setTitle("팔로우", for: .normal) + self.followToggleBtn.backgroundColor = UIColor(named: "yellow00") + } + } + } + + private func updatePostListTabmanViewHeight(_ height: CGFloat) { + postListTabmanView.constraints.forEach { constraint in + if constraint.firstAttribute == .height { + if height >= constraint.constant && constraint.constant != 0 { + constraint.isActive = false + + // 새로운 높이 제약 조건 추가 + postListTabmanView.heightAnchor.constraint(equalToConstant: height).isActive = true + UIView.animate(withDuration: 0.3, animations: { + self.contentScrollView.layoutIfNeeded() + }) { _ in + // ScrollView의 contentSize 업데이트 + self.contentScrollView.contentSize = CGSize( + width: self.contentScrollView.frame.width, + height: self.topUIView.frame.height + ) + } + } else { + print("no posts yet") + } + } + } + } + + deinit { + NotificationCenter.default.removeObserver(self) + } +} + +// MARK: - Extension: CustomAlertDelegate, SecondViewControllerDelegate + +extension OtherUserProfileViewController: CustomAlertDelegate { + + func confirmAction() { + if searchBlockedUser { + self.navigationController?.popViewController(animated: true) + } else { + if let handle = receivedHandle { + UserService.postFollowRequest(handle: handle) { [weak self] data, failed in + guard let data = data else { + switch failed { + case .disconnected: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .serverError: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .unknownError: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + default: + self?.present(UIAlertController.networkErrorAlert(title: "요청에 실패하였습니다."), animated: true) + } + return + } + print(data.message) + self?.receivedIsFollow = false + self?.followToggleBtn.setTitle("팔로우", for: .normal) + self?.followToggleBtn.backgroundColor = UIColor(named: "yellow00") + self?.setUpData() + } + } else { + print("No handle received") + } + } + } + + func cancel() { + print("취소하기 선택됨") + } +} + +extension OtherUserProfileViewController: SecondViewControllerDelegate { + // 차단한 유저의 프로필 조회 시 VC를 dismiss하여 전 화면으로 돌아감 + func dismissSecondViewController() { + self.navigationController?.popViewController(animated: true) + } +} + +extension OtherUserProfileViewController: UIScrollViewDelegate { + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if (contentScrollView.contentOffset.y > (contentScrollView.contentSize.height - contentScrollView.frame.size.height)) { + if (ProfileDataSingleton.shared.currentTabIndex == 0 && + !ProfileDataSingleton.shared.firstTabIsCurrentlyFetching && + !ProfileDataSingleton.shared.firstTabIsLastPage) { + NotificationCenter.default.post(name: .didHitBottom, object: nil) + } else if (ProfileDataSingleton.shared.currentTabIndex == 1 && + !ProfileDataSingleton.shared.secondTabIsCurrentlyFetching && + !ProfileDataSingleton.shared.secondTabIsLastPage) { + NotificationCenter.default.post(name: .didHitBottom, object: nil) + } + } + } +} diff --git a/pochak/pochak/UI/Profile/PostList/PochakPostTabmanViewController.swift b/pochak/pochak/UI/Profile/PostList/PochakPostTabmanViewController.swift new file mode 100644 index 00000000..d94b4572 --- /dev/null +++ b/pochak/pochak/UI/Profile/PostList/PochakPostTabmanViewController.swift @@ -0,0 +1,176 @@ +// +// SecondPostTabmanViewController.swift +// pochak +// +// Created by Seo Cindy on 12/27/23. +// + +import UIKit + +final class PochakPostTabmanViewController: UIViewController { + + // MARK: - Properties + + var receivedHandle: String? + var imageArray: [ProfilePostList]! = [] + private var isLastPage: Bool = false + private var isCurrentlyFetching: Bool = false + private var currentFetchingPage: Int = 0 + private let minimumLineSpacing: CGFloat = 9 + private let minimumInterItemSpacing: CGFloat = 8 + + // MARK: - Views + + @IBOutlet weak var postCollectionView: UICollectionView! + + // MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + currentFetchingPage = 0 + setUpData() + setUpCollectionView() + NotificationCenter.default.addObserver(self, selector: #selector(didReceiveRefreshRequest), name: .didHitBottom, object: nil) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + calculateCurentHeight() + } + // MARK: - Actions + + @objc private func didReceiveRefreshRequest(_ notification: Notification) { + if ProfileDataSingleton.shared.currentTabIndex == 1 { + setUpData() + } + } + + // MARK: - Functions + + func setUpData() { + isCurrentlyFetching = true + ProfileDataSingleton.shared.secondTabIsCurrentlyFetching = true + let request = ProfileRetrievalRequest(page: currentFetchingPage) + if let handle = receivedHandle { + ProfileService.getProfilePochakPosts(handle: handle, request: request) { [weak self] data, failed in + guard let data = data else { + switch failed { + case .clientError: + print("client error") + case .disconnected: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .serverError: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .unknownError: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + default: + self?.present(UIAlertController.networkErrorAlert(title: "요청에 실패하였습니다."), animated: true) + } + return + } + + let newPosts = data.result.postList + let startIndex = self?.imageArray.count + let endIndex = startIndex! + newPosts.count + let newIndexPaths = (startIndex!.. Int { + return max(0,(imageArray.count)) + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell( + withReuseIdentifier: ProfilePostCollectionViewCell.identifier, + for: indexPath) as? ProfilePostCollectionViewCell else { + return UICollectionViewCell() + } + let postData = imageArray[indexPath.item] // indexPath 안에는 섹션에 대한 정보, 섹션에 들어가는 데이터 정보 등이 있다 + cell.setUpCellData(postData) + return cell + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt: Int) -> CGFloat { + return minimumLineSpacing + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + return minimumInterItemSpacing + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { + return UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20) + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + let width = CGFloat((collectionView.frame.width - 20 * 2 - minimumInterItemSpacing * 2) / 3) + return CGSize(width: width, height: width * 4 / 3) + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let exploreTabSb = UIStoryboard(name: "ExploreTab", bundle: nil) + guard let postVC = exploreTabSb.instantiateViewController(withIdentifier: "PostVC") as? PostViewController + else { return } + postVC.receivedPostId = imageArray[indexPath.item].postId + self.navigationController?.pushViewController(postVC, animated: true) + } +} diff --git a/pochak/pochak/UI/Profile/PostList/PochakedPostTabmanViewController.swift b/pochak/pochak/UI/Profile/PostList/PochakedPostTabmanViewController.swift new file mode 100644 index 00000000..b0534f84 --- /dev/null +++ b/pochak/pochak/UI/Profile/PostList/PochakedPostTabmanViewController.swift @@ -0,0 +1,180 @@ +// +// FirstPostTabmanViewController.swift +// pochak +// +// Created by Seo Cindy on 12/27/23. +// + +import UIKit +import Foundation + +final class PochakedPostTabmanViewController: UIViewController { + + // MARK: - Properties + var receivedHandle: String? + var imageArray: [ProfilePostList]! = [] + private var isLastPage: Bool = false + private var isCurrentlyFetching: Bool = false + private var currentFetchingPage: Int = 0 + private let minimumLineSpacing: CGFloat = 9 + private let minimumInterItemSpacing: CGFloat = 8 + + // MARK: - Views + + @IBOutlet weak var postCollectionView: UICollectionView! + + // MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + NotificationCenter.default.addObserver(self, selector: #selector(didReceiveRefreshRequest), name: .didHitBottom, object: nil) + currentFetchingPage = 0 + setUpData() + setUpCollectionView() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + calculateCurentHeight() + } + + // MARK: - Actions + + @objc private func didReceiveRefreshRequest(_ notification: Notification) { + if ProfileDataSingleton.shared.currentTabIndex == 0 { + setUpData() + } + } + + // MARK: - Functions + + func setUpData() { + isCurrentlyFetching = true + ProfileDataSingleton.shared.firstTabIsCurrentlyFetching = true + let request = ProfileRetrievalRequest(page: currentFetchingPage) + if let handle = receivedHandle { + ProfileService.getProfile(handle: handle, request: request) { [weak self] data, failed in + guard let data = data else { + switch failed { + case .clientError: + print("client error") + case .disconnected: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .serverError: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .unknownError: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + default: + self?.present(UIAlertController.networkErrorAlert(title: "요청에 실패하였습니다."), animated: true) + } + return + } + + let newPosts = data.result.postList + let startIndex = self?.imageArray.count + let endIndex = startIndex! + newPosts.count + let newIndexPaths = (startIndex!.. Int { + return max(0,(imageArray.count)) + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + // cell 생성 + guard let cell = collectionView.dequeueReusableCell( + withReuseIdentifier: ProfilePostCollectionViewCell.identifier, + for: indexPath) as? ProfilePostCollectionViewCell else { + return UICollectionViewCell() + } + + let postData = imageArray[indexPath.item] // indexPath 안에는 섹션에 대한 정보, 섹션에 들어가는 데이터 정보 등이 있다 + cell.setUpCellData(postData) + return cell + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt: Int) -> CGFloat { + return minimumLineSpacing + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + return minimumInterItemSpacing + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { + return UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20) + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + let width = CGFloat((collectionView.frame.width - 20 * 2 - minimumInterItemSpacing * 2) / 3) + return CGSize(width: width, height: width * 4 / 3) + } + + // post 클릭 시 해당 post로 이동 + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let exploreTabSb = UIStoryboard(name: "ExploreTab", bundle: nil) + guard let postVC = exploreTabSb.instantiateViewController(withIdentifier: "PostVC") as? PostViewController + else { return } + postVC.receivedPostId = imageArray[indexPath.item].postId + self.navigationController?.pushViewController(postVC, animated: true) + } +} diff --git a/pochak/pochak/UI/Profile/MyProfile/PostListTabman/PostListViewController.swift b/pochak/pochak/UI/Profile/PostList/PostListViewController.swift similarity index 91% rename from pochak/pochak/UI/Profile/MyProfile/PostListTabman/PostListViewController.swift rename to pochak/pochak/UI/Profile/PostList/PostListViewController.swift index 196853e0..dc228b01 100644 --- a/pochak/pochak/UI/Profile/MyProfile/PostListTabman/PostListViewController.swift +++ b/pochak/pochak/UI/Profile/PostList/PostListViewController.swift @@ -9,12 +9,12 @@ import UIKit import Tabman import Pageboy -class PostListViewController: TabmanViewController { +final class PostListViewController: TabmanViewController { // MARK: - Properties - private var viewControllers: [UIViewController] = [] var handle: String? + private var viewControllers: [UIViewController] = [] // MARK: - LifeCycle @@ -65,7 +65,7 @@ class PostListViewController: TabmanViewController { } } -// MARK: - Extension : PageboyViewControllerDataSource, TMBarDataSource +// MARK: - Extension: PageboyViewControllerDataSource, TMBarDataSource extension PostListViewController: PageboyViewControllerDataSource, TMBarDataSource { @@ -75,15 +75,14 @@ extension PostListViewController: PageboyViewControllerDataSource, TMBarDataSour func viewController(for pageboyViewController: PageboyViewController, at index: PageboyViewController.PageIndex) -> UIViewController? { + ProfileDataSingleton.shared.currentTabIndex = index return viewControllers[index] } func defaultPage(for pageboyViewController: PageboyViewController) -> PageboyViewController.Page? { - // index를 통해 처음에 보이는 탭을 설정 return .at(index: 0) } - // 팔로워 페이지 혹은 팔로잉 페이지인지에 따라 defualtPage 다르게 하기 func barItem(for bar: TMBar, at index: Int) -> TMBarItemable { switch index { case 0: diff --git a/pochak/pochak/UI/Profile/PostList/cell/ProfilePostCollectionViewCell.swift b/pochak/pochak/UI/Profile/PostList/cell/ProfilePostCollectionViewCell.swift new file mode 100644 index 00000000..0b8609ec --- /dev/null +++ b/pochak/pochak/UI/Profile/PostList/cell/ProfilePostCollectionViewCell.swift @@ -0,0 +1,35 @@ +// +// ProfilePostCollectionViewCell.swift +// pochak +// +// Created by Seo Cindy on 12/27/23. +// + +import UIKit +import Kingfisher + +final class ProfilePostCollectionViewCell: UICollectionViewCell { + + // MARK: - Properties + + static let identifier = "ProfilePostCollectionViewCell" // Collection View가 생성할 cell임을 명시 + + // MARK: - Views + + @IBOutlet weak var profilePostImage: UIImageView! + + // MARK: - LifeCycle + + override func awakeFromNib() { + super.awakeFromNib() + } + + // MARK: - Functions + + func setUpCellData(_ postDataModel: ProfilePostList) { + var imageURL = postDataModel.postImage + if let url = URL(string: (imageURL)) { + profilePostImage.load(with: url) + } + } +} diff --git a/pochak/pochak/UI/Profile/MyProfile/PostListTabman/cell/ProfilePostCollectionViewCell.xib b/pochak/pochak/UI/Profile/PostList/cell/ProfilePostCollectionViewCell.xib similarity index 100% rename from pochak/pochak/UI/Profile/MyProfile/PostListTabman/cell/ProfilePostCollectionViewCell.xib rename to pochak/pochak/UI/Profile/PostList/cell/ProfilePostCollectionViewCell.xib diff --git a/pochak/pochak/UI/Profile/ProfileDataSingleton.swift b/pochak/pochak/UI/Profile/ProfileDataSingleton.swift new file mode 100644 index 00000000..d2ac8e1d --- /dev/null +++ b/pochak/pochak/UI/Profile/ProfileDataSingleton.swift @@ -0,0 +1,44 @@ +// +// ProfileDataSingleton.swift +// pochak +// +// Created by Seo Cindy on 12/2/24. +// + +import CoreFoundation +import Foundation + +class ProfileDataSingleton { + static let shared = ProfileDataSingleton() + + var currentTabIndex: Int = 0 + var firstTabIsCurrentlyFetching: Bool = false + var secondTabIsCurrentlyFetching: Bool = false + var firstTabIsLastPage: Bool = false + var secondTabIsLastPage: Bool = false + + private init() {} + + private var _firstTabHeight: CGFloat = 0.0 + private var _secondTabHeight: CGFloat = 0.0 + + var firstTabHeight: CGFloat { + get { + return _firstTabHeight + } + set { + _firstTabHeight = newValue + NotificationCenter.default.post(name: .didUpdateTotalHeight, object: nil) + } + } + + var secondTabHeight: CGFloat { + get { + return _firstTabHeight + } + set { + _firstTabHeight = newValue + NotificationCenter.default.post(name: .didUpdateTotalHeight, object: nil) + } + } +} diff --git a/pochak/pochak/UI/Profile/ProfileMenuViewController.swift b/pochak/pochak/UI/Profile/ProfileMenuViewController.swift new file mode 100644 index 00000000..8f22031f --- /dev/null +++ b/pochak/pochak/UI/Profile/ProfileMenuViewController.swift @@ -0,0 +1,76 @@ +// +// ProfileMenuViewController.swift +// pochak +// +// Created by Seo Cindy on 6/29/24. +// + +import UIKit + +final class ProfileMenuViewController: UIViewController { + + // MARK: - Properties + + var receivedHandle: String? + weak var delegate: SecondViewControllerDelegate? + private let userHandle = UserDefaultsManager.getData(type: String.self, forKey: .handle) + + // MARK: - Views + + @IBOutlet weak var userBlockBtn: UIButton! + @IBOutlet weak var cancelButton: UIButton! + + // MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + } + + // MARK: - Actions + + @IBAction func userBlockBtnClicked(_ sender: Any) { + showAlert(alertType: .confirmAndCancel, + titleText: "유저를 차단하시겠습니까?", + messageText: "유저를 차단하면, 팔로워와 관련된 \n사진 및 소식을 접할 수 없습니다.", + cancelButtonText: "취소", + confirmButtonText: "차단하기") + } + + @IBAction func cancelBtnClicked(_ sender: Any) { + self.dismiss(animated: true, completion: nil) + } +} + +// MARK: - Extension: CustomAlertDelegate + +extension ProfileMenuViewController: CustomAlertDelegate { + + func confirmAction() { + if let handle = receivedHandle { + UserService.blockUser(handle: handle) { [weak self] data, failed in + guard let data = data else { + switch failed { + case .disconnected: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .serverError: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .unknownError: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + default: + self?.present(UIAlertController.networkErrorAlert(title: "요청에 실패하였습니다."), animated: true) + } + return + } + self?.userBlockBtn.setTitle("차단취소", for: .normal) + self?.delegate?.dismissSecondViewController() + self?.dismiss(animated: true, completion: nil) + } + } else { + print("No handle received") + } + } + + func cancel() { + print("취소하기 선택됨") + } +} diff --git a/pochak/pochak/UI/Profile/ProfileTab.storyboard b/pochak/pochak/UI/Profile/ProfileTab.storyboard index c3c1ee0c..0dca9502 100644 --- a/pochak/pochak/UI/Profile/ProfileTab.storyboard +++ b/pochak/pochak/UI/Profile/ProfileTab.storyboard @@ -1,9 +1,9 @@ - + - + @@ -29,21 +29,21 @@ - + - - + + - + - - - - - - - - - - - - - - - - - @@ -190,11 +175,7 @@ - - - - @@ -219,7 +200,7 @@ - + @@ -695,12 +676,13 @@ - + + @@ -715,8 +697,11 @@ - + + + + @@ -729,10 +714,10 @@ - - + + - + @@ -752,7 +737,7 @@ - + @@ -766,9 +751,9 @@ - - - + + + @@ -858,21 +843,22 @@ - + + - - + + - + - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - @@ -1015,13 +981,6 @@ - - - - - - - @@ -1035,6 +994,7 @@ + @@ -1043,7 +1003,7 @@ - + diff --git a/pochak/pochak/UI/Profile/MyProfile/Setting/SettingsViewController.swift b/pochak/pochak/UI/Profile/SettingsViewController.swift similarity index 78% rename from pochak/pochak/UI/Profile/MyProfile/Setting/SettingsViewController.swift rename to pochak/pochak/UI/Profile/SettingsViewController.swift index 13326050..0b1387f5 100644 --- a/pochak/pochak/UI/Profile/MyProfile/Setting/SettingsViewController.swift +++ b/pochak/pochak/UI/Profile/SettingsViewController.swift @@ -8,11 +8,12 @@ import UIKit import SafariServices -class SettingsViewController: UIViewController { +final class SettingsViewController: UIViewController { // MARK: - Properties - var selectedBtn: Int = 0 + var realmManager = RecentSearchRealmManager() + private var selectedBtn: Int = 0 // MARK: - Views @@ -21,6 +22,7 @@ class SettingsViewController: UIViewController { @IBOutlet weak var deleteAccountButton: UIButton! // MARK: - LifeCycle + override func viewDidLoad() { super.viewDidLoad() setUpNavigationBar() @@ -29,12 +31,11 @@ class SettingsViewController: UIViewController { // MARK: - Actions @IBAction func openTermsOfUse(_ sender: Any) { - // 배포 시 삭제(로그 확인 용) + // !! 배포 시 삭제(로그 확인 용) !! printUserData() guard let url = URL(string: "https://pochak.notion.site/6520996186464c36a8b3a04bc17fa000?pvs=74") else { return } let safariVC = SFSafariViewController(url: url) - /// delegate 지정 및 presentation style 설정 safariVC.transitioningDelegate = self safariVC.modalPresentationStyle = .pageSheet present(safariVC, animated: true, completion: nil) @@ -44,14 +45,14 @@ class SettingsViewController: UIViewController { @IBAction func openPrivacyPolicy(_ sender: Any) { guard let url = URL(string: "https://pochak.notion.site/e365e34f018949b88543adbe6b0b3746") else { return } let safariVC = SFSafariViewController(url: url) - /// delegate 지정 및 presentation style 설정 safariVC.transitioningDelegate = self safariVC.modalPresentationStyle = .pageSheet present(safariVC, animated: true, completion: nil) } @IBAction func viewBlockList(_ sender: Any) { - guard let blockedUserVC = self.storyboard?.instantiateViewController(withIdentifier: "BlockedUserVC") as? BlockedUserViewController else {return} + guard let blockedUserVC = self.storyboard?.instantiateViewController(withIdentifier: "BlockedUserVC") + as? BlockedUserViewController else { return } self.navigationController?.pushViewController(blockedUserVC, animated: true) } @@ -61,8 +62,7 @@ class SettingsViewController: UIViewController { titleText: "로그아웃 하시겠습니까?", messageText: "", cancelButtonText: "취소", - confirmButtonText: "확인" - ) + confirmButtonText: "확인") } @IBAction func deleteAccount(_ sender: Any) { @@ -71,8 +71,7 @@ class SettingsViewController: UIViewController { titleText: "회원탈퇴하시겠습니까?", messageText: "회원 탈퇴 시, 개인정보 및 기존에 업로드된 \n피드 정보가 모두 사라집니다.", cancelButtonText: "취소", - confirmButtonText: "탈퇴하기" - ) + confirmButtonText: "탈퇴하기") } // MARK: - Functions @@ -80,7 +79,7 @@ class SettingsViewController: UIViewController { private func setUpNavigationBar() { self.navigationController?.navigationBar.tintColor = .black self.navigationItem.title = "설정" - self.navigationController?.navigationBar.titleTextAttributes = [ NSAttributedString.Key.foregroundColor : UIColor.black, NSAttributedString.Key.font : UIFont(name: "Pretendard-Bold", size: 18) ?? UIFont.systemFont(ofSize: 18, weight: .bold)] + self.navigationController?.navigationBar.titleTextAttributes = [ NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font: UIFont(name: "Pretendard-Bold", size: 18) ?? UIFont.systemFont(ofSize: 18, weight: .bold)] } private func moveToMainPage() { @@ -112,7 +111,7 @@ class SettingsViewController: UIViewController { } private func printUserData() { - // 현재 저장된 모든 Keycahin : accessToken, refreshToken + // 현재 저장된 모든 Keycahin: accessToken, refreshToken let accessToken = GetToken.getAccessToken() let refreshToken = GetToken.getRefreshToken() @@ -125,8 +124,6 @@ class SettingsViewController: UIViewController { let handle = UserDefaultsManager.getData(type: String.self, forKey: .handle) ?? "handle not found" let message = UserDefaultsManager.getData(type: String.self, forKey: .message) ?? "message not found" let profileImgUrl = UserDefaultsManager.getData(type: String.self, forKey: .profileImgUrl) ?? "profileImgUrl not found" - let followerCount = UserDefaultsManager.getData(type: Int.self, forKey: .followerCount) ?? -1 - let followingCount = UserDefaultsManager.getData(type: Int.self, forKey: .followingCount) ?? -1 // Print values print("Access Token: \(accessToken)") @@ -140,33 +137,27 @@ class SettingsViewController: UIViewController { print("Handle: \(handle)") print("Message: \(message)") print("Profile Image URL: \(profileImgUrl)") - print("Follower Count: \(followerCount)") - print("Following Count: \(followingCount)") } } -// MARK: - Extension : CustomAlertDelegate, UIViewControllerTransitioningDelegate +// MARK: - Extension: CustomAlertDelegate, UIViewControllerTransitioningDelegate extension SettingsViewController: CustomAlertDelegate { func confirmAction() { if selectedBtn == 0 { - LogoutDataManager.shared.logoutDataManager( - { resultData in - let message = resultData.message - print(message) - }) - + AuthenticationService.logOut { data, failed in + guard let data = data else { return } + print(data.message) + } deleteUserData() moveToMainPage() } else if selectedBtn == 1 { - SignOutDataManager.shared.signOutDataManager( - { resultData in - let message = resultData.message - print(message) - }) - + AuthenticationService.signOut { data, failed in + guard let data = data else { return } + print(data.message) + } deleteUserData() moveToMainPage() } diff --git a/pochak/pochak/UI/Profile/MyProfile/UpdateProfileViewController.swift b/pochak/pochak/UI/Profile/UpdateProfileViewController.swift similarity index 58% rename from pochak/pochak/UI/Profile/MyProfile/UpdateProfileViewController.swift rename to pochak/pochak/UI/Profile/UpdateProfileViewController.swift index 38c2eba9..be3993d7 100644 --- a/pochak/pochak/UI/Profile/MyProfile/UpdateProfileViewController.swift +++ b/pochak/pochak/UI/Profile/UpdateProfileViewController.swift @@ -7,18 +7,18 @@ import UIKit -class UpdateProfileViewController: UIViewController { +final class UpdateProfileViewController: UIViewController { // MARK: - Properties let imagePickerController = UIImagePickerController() - let textViewPlaceHolder = "소개를 입력해주세요.\n(최대 50자, 3줄)" - let name = UserDefaultsManager.getData(type: String.self, forKey: .name) ?? "name not found" - let email = UserDefaultsManager.getData(type: String.self, forKey: .email) ?? "email not found" - let socialId = UserDefaultsManager.getData(type: String.self, forKey: .socialId) ?? "socialId not found" - let handle = UserDefaultsManager.getData(type: String.self, forKey: .handle) ?? "handle not found" - let message = UserDefaultsManager.getData(type: String.self, forKey: .message) ?? "message not found" - let profileImgUrl = UserDefaultsManager.getData(type: String.self, forKey: .profileImgUrl) ?? "profileImgUrl not found" + private let textViewPlaceHolder = "소개를 입력해주세요.\n(최대 50자, 3줄)" + private let name = UserDefaultsManager.getData(type: String.self, forKey: .name) ?? "name not found" + private let email = UserDefaultsManager.getData(type: String.self, forKey: .email) ?? "email not found" + private let socialId = UserDefaultsManager.getData(type: String.self, forKey: .socialId) ?? "socialId not found" + private let handle = UserDefaultsManager.getData(type: String.self, forKey: .handle) ?? "handle not found" + private let message = UserDefaultsManager.getData(type: String.self, forKey: .message) ?? "message not found" + private let profileImgUrl = UserDefaultsManager.getData(type: String.self, forKey: .profileImgUrl) ?? "profileImgUrl not found" // MARK: - Views @@ -36,29 +36,9 @@ class UpdateProfileViewController: UIViewController { } // MARK: - Actions - - @objc private func doneBtnTapped(_ sender: Any) { - // UserDefaults에 데이터 추가 - guard let name = nameTextField.text else {return} - guard let message = messageTextView.text else {return} - guard let profileImage = profileImg.image else {return} - - ProfileUpdateDataManager.shared.updateDataManager(name, - handle, - message, - profileImage, - {resultData in - UserDefaultsManager.setData(value: resultData.name, key: .name) - UserDefaultsManager.setData(value: resultData.handle, key: .handle) - UserDefaultsManager.setData(value: resultData.message, key: .message) - UserDefaultsManager.setData(value: resultData.profileImage, key: .profileImgUrl) - // 프로필 화면으로 전환 - self.navigationController?.popViewController(animated: true) - }) - } /* < 앨범 사진 선택 > - 1. 권한 설정 : Info.plist > Photo Library Usage 권한 추가 + 1. 권한 설정: Info.plist > Photo Library Usage 권한 추가 2. UIImagePickerController 선언 3. @IBAction 정의 4. 프로토콜 채택 @@ -69,6 +49,44 @@ class UpdateProfileViewController: UIViewController { present(self.imagePickerController, animated: true, completion: nil) } + @objc private func doneBtnTapped(_ sender: Any) { + guard let name = nameTextField.text else { return } + guard let message = messageTextView.text else { return } + let profileImage: Data? = profileImg.image?.jpegData(compressionQuality: 0.2) + + let request = ProfileUpdateRequest(name: name, message: message) + var files: [(Data, String, String)] = [] + if let profileImage = profileImage { + let fileTuple: (Data, String, String) = (profileImage, "profileImage", "image/jpeg") + files.append(fileTuple) + } + + ProfileService.profileUpdate(handle: handle, files: files, request: request) { [weak self] data, failed in + guard let data = data else { + switch failed { + case .disconnected: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .serverError: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + case .unknownError: + self?.present(UIAlertController.networkErrorAlert(title: failed!.localizedDescription), animated: true) + default: + self?.present(UIAlertController.networkErrorAlert(title: "요청에 실패하였습니다."), animated: true) + } + return + } + + // UserDefaults 정보 업데이트 + UserDefaultsManager.setData(value: data.result.name, key: .name) + UserDefaultsManager.setData(value: data.result.handle, key: .handle) + UserDefaultsManager.setData(value: data.result.message, key: .message) + UserDefaultsManager.setData(value: data.result.profileImage, key: .profileImgUrl) + + // 프로필 화면으로 전환 + self?.navigationController?.popViewController(animated: true) + } + } + // MARK: - Functions private func setUpNavigationBar() { @@ -84,7 +102,7 @@ class UpdateProfileViewController: UIViewController { // 네비게이션바 title 커스텀 self.navigationController?.navigationBar.tintColor = .black self.navigationItem.title = "프로필 수정" - self.navigationController?.navigationBar.titleTextAttributes = [ NSAttributedString.Key.foregroundColor : UIColor.black, NSAttributedString.Key.font : UIFont(name: "Pretendard-Bold", size: 18) ?? UIFont.systemFont(ofSize: 18, weight: .bold)] + self.navigationController?.navigationBar.titleTextAttributes = [ NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font: UIFont(name: "Pretendard-Bold", size: 18) ?? UIFont.systemFont(ofSize: 18, weight: .bold)] } private func setUpViewController() { @@ -95,37 +113,30 @@ class UpdateProfileViewController: UIViewController { handleTextField.isUserInteractionEnabled = false handleTextField.textColor = UIColor(named: "gray03") + // load 프로필 이미지 if let url = URL(string: profileImgUrl) { - self.profileImg.kf.setImage(with: url) { result in - switch result { - case .success(let value): - print("Image successfully loaded: \(value.image)") - case .failure(let error): - print("Image failed to load with error: \(error.localizedDescription)") - } - } + self.profileImg.load(with: url) } self.profileImg.contentMode = .scaleAspectFill self.profileImg.layer.cornerRadius = 58 messageTextView.delegate = self - /// textView 기본 마진 제거 - messageTextView.textContainer.lineFragmentPadding = 0 + messageTextView.textContainer.lineFragmentPadding = 0 // textView 기본 마진 제거 messageTextView.textContainerInset = .zero } } -// MARK: - Extension : UIImagePickerControllerDelegate, UINavigationControllerDelegate, UITextViewDelegate, CustomAlertDelegate +// MARK: - Extension: UIImagePickerControllerDelegate, UINavigationControllerDelegate, UITextViewDelegate, CustomAlertDelegate extension UpdateProfileViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { // 선택한 사진 사용 - func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage { profileImg.image = image } - picker.dismiss(animated: true, completion: nil) // 주의점 : picker 숨기기 위한 dismiss를 직접 해야함 + picker.dismiss(animated: true, completion: nil) // 주의점: picker 숨기기 위한 dismiss를 직접 해야함 } // 취소 @@ -162,31 +173,28 @@ extension UpdateProfileViewController: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { guard let text = textView.text else { return } - // 줄바꿈(들여쓰기) 제한 let maxNumberOfLines = 3 let lineBreakCharacter = "\n" let lines = text.components(separatedBy: lineBreakCharacter) var consecutiveLineBreakCount = 0 // 연속된 줄 바꿈 횟수 - print("lines == \(lines)") - for line in lines { + for _ in lines { consecutiveLineBreakCount += 1 if consecutiveLineBreakCount > maxNumberOfLines { textView.text = String(text.dropLast()) // 마지막 입력 문자를 제거 - break } } } } -extension UpdateProfileViewController : CustomAlertDelegate { - - func cancel() { - print("canceled") - } +extension UpdateProfileViewController: CustomAlertDelegate { func confirmAction() { print("confirmed") } + + func cancel() { + print("canceled") + } }