diff --git a/DontBe-iOS/DontBe-iOS.xcodeproj/project.pbxproj b/DontBe-iOS/DontBe-iOS.xcodeproj/project.pbxproj index 92f5ca70..8444a351 100644 --- a/DontBe-iOS/DontBe-iOS.xcodeproj/project.pbxproj +++ b/DontBe-iOS/DontBe-iOS.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 052555BC2C1218A800D38E34 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 052555BB2C1218A800D38E34 /* GoogleService-Info.plist */; }; + 052555BF2C121A5200D38E34 /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 052555BE2C121A5200D38E34 /* FirebaseMessaging */; }; + 052555C42C187A7400D38E34 /* DontBePushAlarmHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 052555C32C187A7400D38E34 /* DontBePushAlarmHelper.swift */; }; + 05D2B97F2C0A3D8400453615 /* Config.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 05D2B97E2C0A3D8400453615 /* Config.xcconfig */; }; 2A0A730A2B541555004478C1 /* HttpMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A0A73092B541555004478C1 /* HttpMethod.swift */; }; 2A0A730C2B541A43004478C1 /* NetworkServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A0A730B2B541A43004478C1 /* NetworkServiceType.swift */; }; 2A0A730E2B5438B5004478C1 /* KeychainWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A0A730D2B5438B5004478C1 /* KeychainWrapper.swift */; }; @@ -169,6 +173,9 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 052555BB2C1218A800D38E34 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 052555C32C187A7400D38E34 /* DontBePushAlarmHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DontBePushAlarmHelper.swift; sourceTree = ""; }; + 05D2B97E2C0A3D8400453615 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; 2A0A73092B541555004478C1 /* HttpMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HttpMethod.swift; sourceTree = ""; }; 2A0A730B2B541A43004478C1 /* NetworkServiceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkServiceType.swift; sourceTree = ""; }; 2A0A730D2B5438B5004478C1 /* KeychainWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainWrapper.swift; sourceTree = ""; }; @@ -180,7 +187,6 @@ 2A2671FE2B4C3AF0009D214F /* Publisher+UIControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+UIControl.swift"; sourceTree = ""; }; 2A2672012B4C3B44009D214F /* ViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelType.swift; sourceTree = ""; }; 2A2672042B4C3C00009D214F /* CancelBag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelBag.swift; sourceTree = ""; }; - 2A26720D2B4C40CE009D214F /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; 2A28453D2B531DDE0023F9B5 /* NetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkService.swift; sourceTree = ""; }; 2A28453F2B531F0A0023F9B5 /* BaseResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseResponse.swift; sourceTree = ""; }; 2A2845422B5320070023F9B5 /* SocialLoginResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialLoginResponseDTO.swift; sourceTree = ""; }; @@ -334,6 +340,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 052555BF2C121A5200D38E34 /* FirebaseMessaging in Frameworks */, 2AAA845F2B494AC50098A824 /* SnapKit in Frameworks */, 2A8D70B12B4C4D8F009F4C6C /* KakaoSDK in Frameworks */, 3CFB30472BB305EE00A3F70A /* Amplitude in Frameworks */, @@ -778,7 +785,7 @@ 3C6192E02B3A719A00220CEB = { isa = PBXGroup; children = ( - 2A26720D2B4C40CE009D214F /* Config.xcconfig */, + 05D2B97E2C0A3D8400453615 /* Config.xcconfig */, 3C6192EB2B3A719A00220CEB /* DontBe-iOS */, 3C6192EA2B3A719A00220CEB /* Products */, ); @@ -795,6 +802,7 @@ 3C6192EB2B3A719A00220CEB /* DontBe-iOS */ = { isa = PBXGroup; children = ( + 052555BB2C1218A800D38E34 /* GoogleService-Info.plist */, 2AE95D702B81EC52009C6336 /* DontBe-iOS.entitlements */, 3C6193002B3A771E00220CEB /* Application */, 3C6193012B3A772B00220CEB /* Global */, @@ -1128,6 +1136,7 @@ 2A5220EB2B507F2A001510B7 /* UITableViewCellRegisterable.swift */, 2F65B72E2B84D68500775853 /* CopyableLabel.swift */, 2FD9276B2B9224FA0046193D /* DontBeLoadingView.swift */, + 052555C32C187A7400D38E34 /* DontBePushAlarmHelper.swift */, ); path = Helpers; sourceTree = ""; @@ -1227,6 +1236,7 @@ 2FD927692B92245B0046193D /* Lottie */, 3CFB30462BB305EE00A3F70A /* Amplitude */, 3C5F82AF2BFB133700D2FE3F /* Kingfisher */, + 052555BE2C121A5200D38E34 /* FirebaseMessaging */, ); productName = DontBe; productReference = 3C6192E92B3A719A00220CEB /* DontBe-iOS.app */; @@ -1262,6 +1272,7 @@ 2FD927682B92245B0046193D /* XCRemoteSwiftPackageReference "lottie-spm" */, 3CFB30452BB305EE00A3F70A /* XCRemoteSwiftPackageReference "Amplitude-iOS" */, 3C5F82AE2BFB133700D2FE3F /* XCRemoteSwiftPackageReference "Kingfisher" */, + 052555BD2C121A5100D38E34 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, ); productRefGroup = 3C6192EA2B3A719A00220CEB /* Products */; projectDirPath = ""; @@ -1278,9 +1289,11 @@ buildActionMask = 2147483647; files = ( 3C6192F62B3A719C00220CEB /* Assets.xcassets in Resources */, + 05D2B97F2C0A3D8400453615 /* Config.xcconfig in Resources */, 2A6D54C72B493E3F00F9891E /* Pretendard-Regular.otf in Resources */, 2A6D54C62B493E3F00F9891E /* Pretendard-SemiBold.otf in Resources */, 2FD9276E2B9225EE0046193D /* loading_yesbe.json in Resources */, + 052555BC2C1218A800D38E34 /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1314,6 +1327,7 @@ 3CF4651B2B58398900997FCA /* PostTransparencyRequestDTO.swift in Sources */, 3C2F544E2B50F65500E7BF01 /* DontBeBottomSheetView.swift in Sources */, 2F05B1A72B5799B700AC368D /* PostDetailResponseDTO.swift in Sources */, + 052555C42C187A7400D38E34 /* DontBePushAlarmHelper.swift in Sources */, 3C61930C2B3A782100220CEB /* StringLiterals.swift in Sources */, 2FB64FF22B5318C50082A414 /* WriteReplyContentView.swift in Sources */, 3C2245292BF34C1A0095D3FB /* DontBePhotoDetailView.swift in Sources */, @@ -1451,7 +1465,6 @@ /* Begin XCBuildConfiguration section */ 3C6192FB2B3A719C00220CEB /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 2A26720D2B4C40CE009D214F /* Config.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; @@ -1515,7 +1528,6 @@ }; 3C6192FC2B3A719C00220CEB /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 2A26720D2B4C40CE009D214F /* Config.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; @@ -1572,6 +1584,7 @@ }; 3C6192FE2B3A719C00220CEB /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 05D2B97E2C0A3D8400453615 /* Config.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -1610,6 +1623,7 @@ }; 3C6192FF2B3A719C00220CEB /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 05D2B97E2C0A3D8400453615 /* Config.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -1670,6 +1684,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 052555BD2C121A5100D38E34 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 10.27.0; + }; + }; 2A8D70AF2B4C4D8F009F4C6C /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/kakao/kakao-ios-sdk.git"; @@ -1713,6 +1735,11 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 052555BE2C121A5200D38E34 /* FirebaseMessaging */ = { + isa = XCSwiftPackageProductDependency; + package = 052555BD2C121A5100D38E34 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseMessaging; + }; 2A8D70B02B4C4D8F009F4C6C /* KakaoSDK */ = { isa = XCSwiftPackageProductDependency; package = 2A8D70AF2B4C4D8F009F4C6C /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; diff --git a/DontBe-iOS/DontBe-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DontBe-iOS/DontBe-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ee21a54e..36843c88 100644 --- a/DontBe-iOS/DontBe-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DontBe-iOS/DontBe-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,6 +1,15 @@ { - "originHash" : "74a68703609c00724d8fa5d77c9ef0723fac1493ecbf4612b13c4182b56fbd1f", + "originHash" : "d422629a9ac3cdec9ee477062856b08ee42e219c655a61567899a1d16c894513", "pins" : [ + { + "identity" : "abseil-cpp-binary", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/abseil-cpp-binary.git", + "state" : { + "revision" : "748c7837511d0e6a507737353af268484e1745e2", + "version" : "1.2024011601.1" + } + }, { "identity" : "alamofire", "kind" : "remoteSourceControl", @@ -28,6 +37,78 @@ "version" : "1.0.3" } }, + { + "identity" : "app-check", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/app-check.git", + "state" : { + "revision" : "076b241a625e25eac22f8849be256dfb960fcdfe", + "version" : "10.19.1" + } + }, + { + "identity" : "firebase-ios-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/firebase-ios-sdk.git", + "state" : { + "revision" : "8bcaf973b1d84e119b7c7c119abad72ed460979f", + "version" : "10.27.0" + } + }, + { + "identity" : "googleappmeasurement", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleAppMeasurement.git", + "state" : { + "revision" : "70df02431e216bed98dd461e0c4665889245ba70", + "version" : "10.27.0" + } + }, + { + "identity" : "googledatatransport", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleDataTransport.git", + "state" : { + "revision" : "a637d318ae7ae246b02d7305121275bc75ed5565", + "version" : "9.4.0" + } + }, + { + "identity" : "googleutilities", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleUtilities.git", + "state" : { + "revision" : "57a1d307f42df690fdef2637f3e5b776da02aad6", + "version" : "7.13.3" + } + }, + { + "identity" : "grpc-binary", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/grpc-binary.git", + "state" : { + "revision" : "e9fad491d0673bdda7063a0341fb6b47a30c5359", + "version" : "1.62.2" + } + }, + { + "identity" : "gtm-session-fetcher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/gtm-session-fetcher.git", + "state" : { + "revision" : "0382ca27f22fb3494cf657d8dc356dc282cd1193", + "version" : "3.4.1" + } + }, + { + "identity" : "interop-ios-for-google-sdks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/interop-ios-for-google-sdks.git", + "state" : { + "revision" : "2d12673670417654f08f5f90fdd62926dc3a2648", + "version" : "100.0.0" + } + }, { "identity" : "kakao-ios-sdk", "kind" : "remoteSourceControl", @@ -46,6 +127,15 @@ "version" : "7.11.0" } }, + { + "identity" : "leveldb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/leveldb.git", + "state" : { + "revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1", + "version" : "1.22.5" + } + }, { "identity" : "lottie-spm", "kind" : "remoteSourceControl", @@ -55,6 +145,24 @@ "version" : "4.4.3" } }, + { + "identity" : "nanopb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/nanopb.git", + "state" : { + "revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1", + "version" : "2.30910.0" + } + }, + { + "identity" : "promises", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/promises.git", + "state" : { + "revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac", + "version" : "2.4.0" + } + }, { "identity" : "snapkit", "kind" : "remoteSourceControl", @@ -63,6 +171,15 @@ "revision" : "2842e6e84e82eb9a8dac0100ca90d9444b0307f4", "version" : "5.7.1" } + }, + { + "identity" : "swift-protobuf", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-protobuf.git", + "state" : { + "revision" : "9f0c76544701845ad98716f3f6a774a892152bcb", + "version" : "1.26.0" + } } ], "version" : 3 diff --git a/DontBe-iOS/DontBe-iOS/Application/AppDelegate.swift b/DontBe-iOS/DontBe-iOS/Application/AppDelegate.swift index fe80a616..e44bebd0 100644 --- a/DontBe-iOS/DontBe-iOS/Application/AppDelegate.swift +++ b/DontBe-iOS/DontBe-iOS/Application/AppDelegate.swift @@ -10,6 +10,8 @@ import UIKit import Amplitude import KakaoSDKCommon +import FirebaseCore +import FirebaseMessaging @main class AppDelegate: UIResponder, UIApplicationDelegate { @@ -38,13 +40,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { sceneDelegate.window?.rootViewController = UINavigationController(rootViewController: rootViewController) } } - saveUserData(UserInfo(isSocialLogined: false, - isFirstUser: false, - isJoinedApp: true, - isOnboardingFinished: true, - userNickname: loadUserData()?.userNickname ?? "", - memberId: loadUserData()?.memberId ?? 0, - userProfileImage: loadUserData()?.userProfileImage ?? StringLiterals.Network.baseImageURL)) // KeychainWrapper에 Access Token 저장하고 소셜로그인 화면으로 let accessToken = KeychainWrapper.loadToken(forKey: "accessToken") ?? "" KeychainWrapper.saveToken(accessToken, forKey: "accessToken") @@ -56,7 +51,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate { self.window = UIWindow(frame: UIScreen.main.bounds) self.window?.makeKeyAndVisible() - + FirebaseApp.configure() + + Messaging.messaging().delegate = self + Messaging.messaging().isAutoInitEnabled = true + + UNUserNotificationCenter.current().delegate = self + + application.registerForRemoteNotifications() + return true } @@ -77,3 +80,54 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } +extension AppDelegate: UNUserNotificationCenterDelegate { + /// 푸시클릭시 + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async { + let notiInfomation = response.notification.request.content.userInfo + if let contentID = notiInfomation["relateContentId"] as? String { + let pushAlarmHelper = DontBePushAlarmHelper(contentID: Int(contentID) ?? 0) + pushAlarmHelper.start() + } + print("🟢", #function) + } + + /// 앱화면 보고있는중에 푸시올 때 + func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + completionHandler([.banner, .sound, .badge]) + } + + /// FCMToken 업데이트시 + func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { + let dataDict: [String: String] = ["token": fcmToken ?? ""] + NotificationCenter.default.post( + name: Notification.Name("FCMToken"), + object: nil, + userInfo: dataDict + ) + saveUserData(UserInfo(isSocialLogined: loadUserData()?.isSocialLogined ?? false, + isFirstUser: loadUserData()?.isFirstUser ?? false, + isJoinedApp: loadUserData()?.isJoinedApp ?? false, + isOnboardingFinished: loadUserData()?.isOnboardingFinished ?? false, + userNickname: loadUserData()?.userNickname ?? "", + memberId: loadUserData()?.memberId ?? 0, + userProfileImage: loadUserData()?.userProfileImage ?? StringLiterals.Network.baseImageURL, + fcmToken: fcmToken ?? "", + isPushAlarmAllowed: loadUserData()?.isPushAlarmAllowed ?? false)) + print("🟢", #function, fcmToken) + } + + /// 스위즐링 NO시, APNs등록, APNs토큰값가져옴 + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + Messaging.messaging().apnsToken = deviceToken + print("🟢", #function) + } + + /// error발생시 + func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { + print("🟢", error) + } +} + +extension AppDelegate: MessagingDelegate { + +} diff --git a/DontBe-iOS/DontBe-iOS/DontBe-iOS.entitlements b/DontBe-iOS/DontBe-iOS/DontBe-iOS.entitlements index a812db50..80b5221d 100644 --- a/DontBe-iOS/DontBe-iOS/DontBe-iOS.entitlements +++ b/DontBe-iOS/DontBe-iOS/DontBe-iOS.entitlements @@ -2,6 +2,8 @@ + aps-environment + development com.apple.developer.applesignin Default diff --git a/DontBe-iOS/DontBe-iOS/Global/Resources/Info.plist b/DontBe-iOS/DontBe-iOS/Global/Resources/Info.plist index e09a1879..e6f64887 100644 --- a/DontBe-iOS/DontBe-iOS/Global/Resources/Info.plist +++ b/DontBe-iOS/DontBe-iOS/Global/Resources/Info.plist @@ -2,6 +2,8 @@ + FirebaseAppDelegateProxyEnabled + NO BASE_URL $(BASE_URL) CFBundleURLTypes @@ -51,5 +53,9 @@ + UIBackgroundModes + + remote-notification + diff --git a/DontBe-iOS/DontBe-iOS/Network/SocialLogin/DTO/ResponseDTO/SocialLoginResponseDTO.swift b/DontBe-iOS/DontBe-iOS/Network/SocialLogin/DTO/ResponseDTO/SocialLoginResponseDTO.swift index 383b91d3..bf594b01 100644 --- a/DontBe-iOS/DontBe-iOS/Network/SocialLogin/DTO/ResponseDTO/SocialLoginResponseDTO.swift +++ b/DontBe-iOS/DontBe-iOS/Network/SocialLogin/DTO/ResponseDTO/SocialLoginResponseDTO.swift @@ -15,4 +15,5 @@ struct SocialLoginResponseDTO: Decodable { let accessToken, refreshToken: String let memberProfileUrl: String let isNewUser: Bool + let isPushAlarmAllowed: Bool? } diff --git a/DontBe-iOS/DontBe-iOS/Network/UserProfile/DTO/RequestDTO/UserProfileRequestDTO.swift b/DontBe-iOS/DontBe-iOS/Network/UserProfile/DTO/RequestDTO/UserProfileRequestDTO.swift index 14c34599..eec0735e 100644 --- a/DontBe-iOS/DontBe-iOS/Network/UserProfile/DTO/RequestDTO/UserProfileRequestDTO.swift +++ b/DontBe-iOS/DontBe-iOS/Network/UserProfile/DTO/RequestDTO/UserProfileRequestDTO.swift @@ -18,8 +18,10 @@ struct UserProfileRequestDTO: Encodable { } struct EditUserProfileRequestDTO { - let nickname: String - let is_alarm_allowed: Bool - let member_intro: String - let profile_image: UIImage + let nickname: String? + let is_alarm_allowed: Bool? + let member_intro: String? + let is_push_alarm_allowed: Bool? + let fcmToken: String? + let profile_image: UIImage? } diff --git a/DontBe-iOS/DontBe-iOS/Presentation/Helpers/DontBePushAlarmHelper.swift b/DontBe-iOS/DontBe-iOS/Presentation/Helpers/DontBePushAlarmHelper.swift new file mode 100644 index 00000000..af8c370c --- /dev/null +++ b/DontBe-iOS/DontBe-iOS/Presentation/Helpers/DontBePushAlarmHelper.swift @@ -0,0 +1,36 @@ +// +// DontBePushAlarmHelper.swift +// DontBe-iOS +// +// Created by 박윤빈 on 6/11/24. +// + +import UIKit + +final class DontBePushAlarmHelper { + + private var contentID = Int() + + init(contentID: Int) { + self.contentID = contentID + } + + func start() { + load() + } + + private func load() { + if let window = UIApplication.shared.windows.first { + if let rootViewController = window.rootViewController as? UINavigationController { + let targetViewController = PostDetailViewController(viewModel: PostDetailViewModel(networkProvider: NetworkService())) + // 데이터 전달 + targetViewController.contentId = Int(self.contentID ?? 0) + rootViewController.pushViewController(targetViewController, animated: true) + } + } + } + + func checkUserLoginState() { + + } +} diff --git a/DontBe-iOS/DontBe-iOS/Presentation/Helpers/UserInfo.swift b/DontBe-iOS/DontBe-iOS/Presentation/Helpers/UserInfo.swift index c23a797c..75a214c5 100644 --- a/DontBe-iOS/DontBe-iOS/Presentation/Helpers/UserInfo.swift +++ b/DontBe-iOS/DontBe-iOS/Presentation/Helpers/UserInfo.swift @@ -15,6 +15,8 @@ struct UserInfo: Codable { let userNickname: String let memberId: Int let userProfileImage: String + let fcmToken: String + let isPushAlarmAllowed: Bool } // 구조체를 UserDefault에 저장하는 함수 diff --git a/DontBe-iOS/DontBe-iOS/Presentation/Home/ViewControllers/HomeViewController.swift b/DontBe-iOS/DontBe-iOS/Presentation/Home/ViewControllers/HomeViewController.swift index 946e1677..12754ab8 100644 --- a/DontBe-iOS/DontBe-iOS/Presentation/Home/ViewControllers/HomeViewController.swift +++ b/DontBe-iOS/DontBe-iOS/Presentation/Home/ViewControllers/HomeViewController.swift @@ -8,6 +8,7 @@ import Combine import SafariServices import UIKit +import UserNotifications import SnapKit @@ -42,7 +43,8 @@ final class HomeViewController: UIViewController { private lazy var fourthReason = self.transparentReasonView.fourthReasonView.radioButton.publisher(for: .touchUpInside).map { _ in }.eraseToAnyPublisher() private lazy var fifthReason = self.transparentReasonView.fifthReasonView.radioButton.publisher(for: .touchUpInside).map { _ in }.eraseToAnyPublisher() private lazy var sixthReason = self.transparentReasonView.sixthReasonView.radioButton.publisher(for: .touchUpInside).map { _ in }.eraseToAnyPublisher() - + private lazy var isPushAllowed = PassthroughSubject() + let warnUserURL = NSURL(string: "\(StringLiterals.Network.warnUserGoogleFormURL)") // MARK: - UI Components @@ -84,6 +86,7 @@ final class HomeViewController: UIViewController { setDelegate() setAddTarget() setRefreshControl() + requestNotificationAuthorization() } override func viewWillAppear(_ animated: Bool) { @@ -356,7 +359,8 @@ extension HomeViewController { thirdReasonButtonTapped: thirdReason, fourthReasonButtonTapped: fourthReason, fifthReasonButtonTapped: fifthReason, - sixthReasonButtonTapped: sixthReason) + sixthReasonButtonTapped: sixthReason, + isPushNotiAllowed: isPushAllowed.eraseToAnyPublisher()) let output = homeViewModel.transform(from: input, cancelBag: cancelBag) @@ -445,7 +449,8 @@ extension HomeViewController { thirdReasonButtonTapped: thirdReason, fourthReasonButtonTapped: fourthReason, fifthReasonButtonTapped: fifthReason, - sixthReasonButtonTapped: sixthReason) + sixthReasonButtonTapped: sixthReason, + isPushNotiAllowed: nil) let output = self.homeViewModel.transform(from: input, cancelBag: self.cancelBag) @@ -453,6 +458,43 @@ extension HomeViewController { .sink { _ in } .store(in: self.cancelBag) } + + private func requestNotificationAuthorization() { + let center = UNUserNotificationCenter.current() + let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] + center.requestAuthorization(options: authOptions) { granted, _ in + + if granted { + print("알림 등록이 완료되었습니다.") + DispatchQueue.main.async { + UIApplication.shared.registerForRemoteNotifications() + } + self.isPushAllowed.send(granted) + saveUserData(UserInfo(isSocialLogined: loadUserData()?.isSocialLogined ?? false, + isFirstUser: loadUserData()?.isFirstUser ?? false, + isJoinedApp: loadUserData()?.isJoinedApp ?? false, + isOnboardingFinished: loadUserData()?.isOnboardingFinished ?? false, + userNickname: loadUserData()?.userNickname ?? "", + memberId: loadUserData()?.memberId ?? 0, + userProfileImage: loadUserData()?.userProfileImage ?? StringLiterals.Network.baseImageURL, + fcmToken: loadUserData()?.fcmToken ?? "", + isPushAlarmAllowed: true)) + } else { + print("알림 등록이 거부되었습니다.") + self.isPushAllowed.send(granted) + saveUserData(UserInfo(isSocialLogined: loadUserData()?.isSocialLogined ?? false, + isFirstUser: loadUserData()?.isFirstUser ?? false, + isJoinedApp: loadUserData()?.isJoinedApp ?? false, + isOnboardingFinished: loadUserData()?.isOnboardingFinished ?? false, + userNickname: loadUserData()?.userNickname ?? "", + memberId: loadUserData()?.memberId ?? 0, + userProfileImage: loadUserData()?.userProfileImage ?? StringLiterals.Network.baseImageURL, + fcmToken: loadUserData()?.fcmToken ?? "", + isPushAlarmAllowed: false)) + } + } + } + } extension HomeViewController: UICollectionViewDelegate { } diff --git a/DontBe-iOS/DontBe-iOS/Presentation/Home/ViewModel/HomeViewModel.swift b/DontBe-iOS/DontBe-iOS/Presentation/Home/ViewModel/HomeViewModel.swift index 9b177920..3afacef0 100644 --- a/DontBe-iOS/DontBe-iOS/Presentation/Home/ViewModel/HomeViewModel.swift +++ b/DontBe-iOS/DontBe-iOS/Presentation/Home/ViewModel/HomeViewModel.swift @@ -40,6 +40,7 @@ final class HomeViewModel: ViewModelType { let fourthReasonButtonTapped: AnyPublisher? let fifthReasonButtonTapped: AnyPublisher? let sixthReasonButtonTapped: AnyPublisher? + let isPushNotiAllowed: AnyPublisher? } struct Output { @@ -138,6 +139,16 @@ final class HomeViewModel: ViewModelType { } .store(in: cancelBag) + input.isPushNotiAllowed? + .sink { value in + Task { + do { + self.patchUserInfoDataAPI(isPushAlarmAllowed: value) + } + } + } + .store(in: cancelBag) + return Output(getData: getData, toggleLikeButton: toggleLikeButton, clickedButtonState: clickedRadioButtonState) @@ -266,4 +277,59 @@ extension HomeViewModel { return nil } } + + func patchUserInfoDataAPI(isPushAlarmAllowed: Bool) { + guard let url = URL(string: Config.baseURL + "/user-profile2") else { return } + guard let accessToken = KeychainWrapper.loadToken(forKey: "accessToken") else { return } + print("👻👻 -> 엑세스토큰 \(accessToken)") + + let parameters: [String: Any] = [ + "isPushAlarmAllowed": isPushAlarmAllowed, + "fcmToken": isPushAlarmAllowed ? loadUserData()?.fcmToken ?? "" : "" + ] + + var request = URLRequest(url: url) + request.httpMethod = "PATCH" + + // Multipart form data 생성 + let boundary = "Boundary-\(UUID().uuidString)" + request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") + + var requestBodyData = Data() + + // JSON 데이터를 포함한 프로필 정보 추가 + requestBodyData.append("--\(boundary)\r\n".data(using: .utf8)!) + requestBodyData.append("Content-Disposition: form-data; name=\"info\"\r\n".data(using: .utf8)!) + requestBodyData.append("Content-Type: application/json\r\n\r\n".data(using: .utf8)!) + let jsonData = try! JSONSerialization.data(withJSONObject: parameters, options: []) + requestBodyData.append(jsonData) + requestBodyData.append("\r\n".data(using: .utf8)!) + + requestBodyData.append("--\(boundary)--\r\n".data(using: .utf8)!) + + // HTTP body에 데이터 설정 + request.httpBody = requestBodyData + + // URLSession으로 요청 보내기 + let task = URLSession.shared.dataTask(with: request) { (data, response, error) in + if let error = error { + print("Error:", error) + return + } + + // 응답 처리 + if let response = response as? HTTPURLResponse { + print(response) + print("Response status code:", response.statusCode) + } + + if let data = data { + // 서버 응답 데이터 처리 + print("Response data:", String(data: data, encoding: .utf8) ?? "Empty response") + } + } + + task.resume() + } } diff --git a/DontBe-iOS/DontBe-iOS/Presentation/Join/ViewControllers/JoinProfileViewController.swift b/DontBe-iOS/DontBe-iOS/Presentation/Join/ViewControllers/JoinProfileViewController.swift index 3f6a0711..9ee1ad7e 100644 --- a/DontBe-iOS/DontBe-iOS/Presentation/Join/ViewControllers/JoinProfileViewController.swift +++ b/DontBe-iOS/DontBe-iOS/Presentation/Join/ViewControllers/JoinProfileViewController.swift @@ -26,7 +26,10 @@ final class JoinProfileViewController: UIViewController { return EditUserProfileRequestDTO(nickname: self.originView.nickNameTextField.text ?? "", is_alarm_allowed: true, member_intro: "", - profile_image: self.originView.profileImage.image ?? ImageLiterals.Common.imgProfile) + is_push_alarm_allowed: loadUserData()?.isPushAlarmAllowed, + fcmToken: loadUserData()?.fcmToken, + profile_image: self.originView.profileImage.image ?? ImageLiterals.Common.imgProfile + ) }.eraseToAnyPublisher() diff --git a/DontBe-iOS/DontBe-iOS/Presentation/Join/ViewModels/JoinProfileViewModel.swift b/DontBe-iOS/DontBe-iOS/Presentation/Join/ViewModels/JoinProfileViewModel.swift index c0928607..8399c55e 100644 --- a/DontBe-iOS/DontBe-iOS/Presentation/Join/ViewModels/JoinProfileViewModel.swift +++ b/DontBe-iOS/DontBe-iOS/Presentation/Join/ViewModels/JoinProfileViewModel.swift @@ -67,22 +67,25 @@ final class JoinProfileViewModel: ViewModelType { .sink { value in // 회원가입 서버통신 Task { - self.patchUserInfoDataAPI(nickname: value.nickname, - isAlarmAllowed: value.is_alarm_allowed, - memberIntro: value.member_intro, - profileImage: value.profile_image) + self.patchUserInfoDataAPI(nickname: value.nickname ?? "", + isAlarmAllowed: value.is_alarm_allowed ?? false, + memberIntro: value.member_intro ?? "", + profileImage: value.profile_image ?? UIImage()) self.pushOrPopViewController.send(1) Amplitude.instance().logEvent("click_account_join_done") } + let fcmToken = loadUserData()?.fcmToken saveUserData(UserInfo(isSocialLogined: true, isFirstUser: true, isJoinedApp: true, isOnboardingFinished: false, - userNickname: value.nickname, + userNickname: value.nickname ?? "", memberId: loadUserData()?.memberId ?? 0, - userProfileImage: loadUserData()?.userProfileImage ?? StringLiterals.Network.baseImageURL)) + userProfileImage: loadUserData()?.userProfileImage ?? StringLiterals.Network.baseImageURL, + fcmToken: fcmToken ?? "", + isPushAlarmAllowed: loadUserData()?.isPushAlarmAllowed ?? false)) } .store(in: self.cancelBag) diff --git a/DontBe-iOS/DontBe-iOS/Presentation/Login/ViewModels/LoginViewModel.swift b/DontBe-iOS/DontBe-iOS/Presentation/Login/ViewModels/LoginViewModel.swift index 1b5ca5fd..c0bb6cbe 100644 --- a/DontBe-iOS/DontBe-iOS/Presentation/Login/ViewModels/LoginViewModel.swift +++ b/DontBe-iOS/DontBe-iOS/Presentation/Login/ViewModels/LoginViewModel.swift @@ -121,14 +121,16 @@ extension LoginViewModel { let userNickname = data?.data?.nickName ?? "" let isNewUser = data?.data?.isNewUser ?? true let memberId = data?.data?.memberId ?? 0 + let fcmToken = loadUserData()?.fcmToken saveUserData(UserInfo(isSocialLogined: true, isFirstUser: isNewUser, isJoinedApp: false, isOnboardingFinished: false, userNickname: userNickname, memberId: memberId, - userProfileImage: StringLiterals.Network.baseImageURL)) - + userProfileImage: StringLiterals.Network.baseImageURL, + fcmToken: fcmToken ?? "", + isPushAlarmAllowed: loadUserData()?.isPushAlarmAllowed ?? false)) // KeychainWrapper에 Access Token 저장 let accessToken = data?.data?.accessToken ?? "" print(accessToken) diff --git a/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewControllers/MyPageAccountInfoViewController.swift b/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewControllers/MyPageAccountInfoViewController.swift index f34c3d26..60260d43 100644 --- a/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewControllers/MyPageAccountInfoViewController.swift +++ b/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewControllers/MyPageAccountInfoViewController.swift @@ -217,7 +217,9 @@ extension MyPageAccountInfoViewController { isOnboardingFinished: true, userNickname: loadUserData()?.userNickname ?? "", memberId: loadUserData()?.memberId ?? 0, - userProfileImage: loadUserData()?.userProfileImage ?? StringLiterals.Network.baseImageURL)) + userProfileImage: loadUserData()?.userProfileImage ?? StringLiterals.Network.baseImageURL, + fcmToken: loadUserData()?.fcmToken ?? "", + isPushAlarmAllowed: loadUserData()?.isPushAlarmAllowed ?? false)) OnboardingViewController.pushCount = 0 } diff --git a/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewControllers/MyPageContentViewController.swift b/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewControllers/MyPageContentViewController.swift index 9a3089f0..f27b0154 100644 --- a/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewControllers/MyPageContentViewController.swift +++ b/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewControllers/MyPageContentViewController.swift @@ -226,7 +226,8 @@ extension MyPageContentViewController { thirdReasonButtonTapped: nil, fourthReasonButtonTapped: nil, fifthReasonButtonTapped: nil, - sixthReasonButtonTapped: nil) + sixthReasonButtonTapped: nil, + isPushNotiAllowed: nil) let output = self.viewModel.transform(from: input, cancelBag: self.cancelBag) diff --git a/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewControllers/MyPageEditProfileViewController.swift b/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewControllers/MyPageEditProfileViewController.swift index 5746f048..cc2b3793 100644 --- a/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewControllers/MyPageEditProfileViewController.swift +++ b/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewControllers/MyPageEditProfileViewController.swift @@ -29,6 +29,8 @@ final class MyPageEditProfileViewController: UIViewController { return EditUserProfileRequestDTO(nickname: self.nicknameEditView.nickNameTextField.text ?? "", is_alarm_allowed: true, member_intro: self.introductionEditView.contentTextView.text ?? "", + is_push_alarm_allowed: loadUserData()?.isPushAlarmAllowed, + fcmToken: loadUserData()?.fcmToken, profile_image: self.nicknameEditView.profileImage.image ?? ImageLiterals.Common.imgProfile) }.eraseToAnyPublisher() diff --git a/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewControllers/MyPageSignOutConfirmViewController.swift b/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewControllers/MyPageSignOutConfirmViewController.swift index de2807e1..4033c4ee 100644 --- a/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewControllers/MyPageSignOutConfirmViewController.swift +++ b/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewControllers/MyPageSignOutConfirmViewController.swift @@ -124,7 +124,9 @@ extension MyPageSignOutConfirmViewController { isOnboardingFinished: true, userNickname: loadUserData()?.userNickname ?? "", memberId: loadUserData()?.memberId ?? 0, - userProfileImage: loadUserData()?.userProfileImage ?? StringLiterals.Network.baseImageURL)) + userProfileImage: loadUserData()?.userProfileImage ?? StringLiterals.Network.baseImageURL, + fcmToken: loadUserData()?.fcmToken ?? "", + isPushAlarmAllowed: loadUserData()?.isPushAlarmAllowed ?? false)) OnboardingViewController.pushCount = 0 } diff --git a/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewControllers/MyPageViewController.swift b/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewControllers/MyPageViewController.swift index cf032306..503b7819 100644 --- a/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewControllers/MyPageViewController.swift +++ b/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewControllers/MyPageViewController.swift @@ -412,7 +412,8 @@ extension MyPageViewController { thirdReasonButtonTapped: thirdReason, fourthReasonButtonTapped: fourthReason, fifthReasonButtonTapped: fifthReason, - sixthReasonButtonTapped: sixthReason) + sixthReasonButtonTapped: sixthReason, + isPushNotiAllowed: nil) let output = homeViewModel.transform(from: input, cancelBag: cancelBag) @@ -498,7 +499,9 @@ extension MyPageViewController { isOnboardingFinished: true, userNickname: data.nickname, memberId: loadUserData()?.memberId ?? 0, - userProfileImage: data.memberProfileUrl)) + userProfileImage: data.memberProfileUrl, + fcmToken: loadUserData()?.fcmToken ?? "", + isPushAlarmAllowed: loadUserData()?.isPushAlarmAllowed ?? false)) } } @@ -799,7 +802,9 @@ extension MyPageViewController: DontBePopupDelegate { isOnboardingFinished: true, userNickname: loadUserData()?.userNickname ?? "", memberId: loadUserData()?.memberId ?? 0, - userProfileImage: loadUserData()?.userProfileImage ?? StringLiterals.Network.baseImageURL)) + userProfileImage: loadUserData()?.userProfileImage ?? StringLiterals.Network.baseImageURL, + fcmToken: loadUserData()?.fcmToken ?? "", + isPushAlarmAllowed: loadUserData()?.isPushAlarmAllowed ?? false)) OnboardingViewController.pushCount = 0 } else { diff --git a/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewModels/MyPageProfileViewModel.swift b/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewModels/MyPageProfileViewModel.swift index 25c7c88d..0f4b8aef 100644 --- a/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewModels/MyPageProfileViewModel.swift +++ b/DontBe-iOS/DontBe-iOS/Presentation/MyPage/ViewModels/MyPageProfileViewModel.swift @@ -84,19 +84,21 @@ final class MyPageProfileViewModel: ViewModelType { input.finishButtonTapped .sink { value in Task { - self.uploadData(nickname: value.nickname, - isAlarmAllowed: value.is_alarm_allowed, - memberIntro: value.member_intro, - profileImage: value.profile_image) + self.uploadData(nickname: value.nickname ?? "", + isAlarmAllowed: value.is_alarm_allowed ?? false, + memberIntro: value.member_intro ?? "", + profileImage: value.profile_image ?? UIImage()) } saveUserData(UserInfo(isSocialLogined: true, isFirstUser: false, isJoinedApp: true, isOnboardingFinished: true, - userNickname: value.nickname, + userNickname: value.nickname ?? "", memberId: loadUserData()?.memberId ?? 0, - userProfileImage: loadUserData()?.userProfileImage ?? StringLiterals.Network.baseImageURL)) + userProfileImage: loadUserData()?.userProfileImage ?? StringLiterals.Network.baseImageURL, + fcmToken: loadUserData()?.fcmToken ?? "", + isPushAlarmAllowed: loadUserData()?.isPushAlarmAllowed ?? false)) self.popViewController.send() } diff --git a/DontBe-iOS/DontBe-iOS/Presentation/Onboarding/ViewModels/OnboardingEndingViewModel.swift b/DontBe-iOS/DontBe-iOS/Presentation/Onboarding/ViewModels/OnboardingEndingViewModel.swift index 44f5a61c..7f5469da 100644 --- a/DontBe-iOS/DontBe-iOS/Presentation/Onboarding/ViewModels/OnboardingEndingViewModel.swift +++ b/DontBe-iOS/DontBe-iOS/Presentation/Onboarding/ViewModels/OnboardingEndingViewModel.swift @@ -53,7 +53,9 @@ final class OnboardingEndingViewModel: ViewModelType { isOnboardingFinished: false, userNickname: data.nickname, memberId: loadUserData()?.memberId ?? 0, - userProfileImage: data.memberProfileUrl)) + userProfileImage: data.memberProfileUrl, + fcmToken: loadUserData()?.fcmToken ?? "", + isPushAlarmAllowed: loadUserData()?.isPushAlarmAllowed ?? false)) } } } catch { @@ -92,7 +94,9 @@ final class OnboardingEndingViewModel: ViewModelType { isOnboardingFinished: true, userNickname: loadUserData()?.userNickname ?? "", memberId: loadUserData()?.memberId ?? 0, - userProfileImage: loadUserData()?.userProfileImage ?? StringLiterals.Network.baseImageURL)) + userProfileImage: loadUserData()?.userProfileImage ?? StringLiterals.Network.baseImageURL, + fcmToken: loadUserData()?.fcmToken ?? "", + isPushAlarmAllowed: loadUserData()?.isPushAlarmAllowed ?? false)) } .store(in: self.cancelBag) @@ -107,7 +111,9 @@ final class OnboardingEndingViewModel: ViewModelType { isOnboardingFinished: true, userNickname: loadUserData()?.userNickname ?? "", memberId: loadUserData()?.memberId ?? 0, - userProfileImage: loadUserData()?.userProfileImage ?? StringLiterals.Network.baseImageURL)) + userProfileImage: loadUserData()?.userProfileImage ?? StringLiterals.Network.baseImageURL, + fcmToken: loadUserData()?.fcmToken ?? "", + isPushAlarmAllowed: loadUserData()?.isPushAlarmAllowed ?? false)) } .store(in: self.cancelBag)