Skip to content
Merged
4 changes: 0 additions & 4 deletions Wable-iOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@
DD69C5902D71A3BE000A3349 /* OAuthTokenProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD69C58F2D71A3B6000A3349 /* OAuthTokenProvider.swift */; };
DD765D9A2D8746E30029A317 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD765D992D8746DD0029A317 /* LoginViewController.swift */; };
DD765D9D2D8747950029A317 /* SplashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD765D9C2D8747900029A317 /* SplashViewController.swift */; };
DD7E797E2DBF6D1E00570C23 /* AppDelegate+Firebase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7E797D2DBF6D0500570C23 /* AppDelegate+Firebase.swift */; };
DD7E79822DBFA48300570C23 /* UpdateUserBadgeUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7E79812DBFA47000570C23 /* UpdateUserBadgeUseCase.swift */; };
DD8326962D95836F0014B62D /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8326952D95836B0014B62D /* HomeViewModel.swift */; };
DD8B42A42DA15DB0007DC1CA /* UITextView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8B42A32DA15DA9007DC1CA /* UITextView+.swift */; };
Expand Down Expand Up @@ -500,7 +499,6 @@
DD69C58F2D71A3B6000A3349 /* OAuthTokenProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthTokenProvider.swift; sourceTree = "<group>"; };
DD765D992D8746DD0029A317 /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = "<group>"; };
DD765D9C2D8747900029A317 /* SplashViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashViewController.swift; sourceTree = "<group>"; };
DD7E797D2DBF6D0500570C23 /* AppDelegate+Firebase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Firebase.swift"; sourceTree = "<group>"; };
DD7E79812DBFA47000570C23 /* UpdateUserBadgeUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateUserBadgeUseCase.swift; sourceTree = "<group>"; };
DD8326952D95836B0014B62D /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = "<group>"; };
DD8B42A32DA15DA9007DC1CA /* UITextView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextView+.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1273,7 +1271,6 @@
isa = PBXGroup;
children = (
DD8CEF472D6A007900DBE580 /* AppDelegate.swift */,
DD7E797D2DBF6D0500570C23 /* AppDelegate+Firebase.swift */,
DE6459CE2D97AA9D005569B8 /* AppDelegate+InjectDependency.swift */,
DD8CEF482D6A007900DBE580 /* SceneDelegate.swift */,
);
Expand Down Expand Up @@ -2616,7 +2613,6 @@
DE3CA27D2DD4294000BF5E87 /* MyProfileViewModel.swift in Sources */,
DEA9E7152DA92E7100D35646 /* CommunityView.swift in Sources */,
DD29686F2D6DAD5F00143851 /* MoyaLoggingPlugin.swift in Sources */,
DD7E797E2DBF6D1E00570C23 /* AppDelegate+Firebase.swift in Sources */,
DEDFCED82DDC566A00B3CD97 /* OtherProfileViewModel.swift in Sources */,
DE98919B2D993D7200D538D8 /* InformationNotificationViewModel.swift in Sources */,
DD2968702D6DAD5F00143851 /* TokenStorage.swift in Sources */,
Expand Down
129 changes: 0 additions & 129 deletions Wable-iOS/App/AppDelegate+Firebase.swift

This file was deleted.

165 changes: 154 additions & 11 deletions Wable-iOS/App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import UIKit

import FirebaseCore
import FirebaseMessaging
import KakaoSDKCommon

Comment on lines 8 to 13
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Missing mandatory imports cause compilation failure

UNUserNotificationCenter (and its delegate) and Combine are both referenced later in the file, but the headers are not imported:

import UserNotifications   // for UNUserNotificationCenter & delegate
import Combine             // for CancelBag, AnyPublisher, sink, …

Without these, the file will not compile.

🤖 Prompt for AI Agents
In Wable-iOS/App/AppDelegate.swift around lines 8 to 13, the imports for
UserNotifications and Combine are missing, causing compilation errors due to
undefined references to UNUserNotificationCenter and Combine types. Add the
lines 'import UserNotifications' and 'import Combine' alongside the existing
imports to resolve these missing dependencies and enable successful compilation.

@main
Expand All @@ -27,14 +29,35 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

var activeID = -1

// MARK: - didFinishLaunchingWithOptions
/// 앱 실행 후 초기화 시 호출
/// Firebase 초기화 & 알람 관련 delegate 설정 및 알람 권한 요청 (Alert 표시)
///

func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
injectDependency()
fetchActiveID()
configureFirebase(application: application)
requestNotificationPermission()

FirebaseApp.configure()
Messaging.messaging().delegate = self
UNUserNotificationCenter.current().delegate = self

UNUserNotificationCenter.current().requestAuthorization(
options: [.alert, .badge, .sound],
completionHandler: { [weak self] granted, error in
guard let self = self,
let activeSession = userSessionRepository.fetchActiveUserSession() else { return
}

self.userSessionRepository.updateUserSession(userID: activeSession.id, isPushAlarmAllowed: granted)
}
)


application.registerForRemoteNotifications()

KakaoSDK.initSDK(appKey: Bundle.kakaoAppKey)

Expand All @@ -50,6 +73,135 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}

// MARK: - didRegisterForRemoteNotificationsWithDeviceToken
/// APNs 토큰을 먼저 발급받고, 그 토큰을 FCM SDK로 전달하는 메서드

func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
Messaging.messaging().apnsToken = deviceToken
print("APNs token retrieved: \(deviceToken)")
let deviceTokenString = deviceToken.map{ String(format: "%02x", $0) }.joined()
print(deviceTokenString)
}

// MARK: - didFailToRegisterForRemoteNotificationsWithError
/// 앱이 APNs 등록에 실패하거나, Remote Notification을 위한 구성에 실패한 경우 호출되는 에러 핸들러

func application(
_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error
) {
print("Unable to register for remote notifications: \(error.localizedDescription)")
}

// MARK: - didReceiveRemoteNotification
/// Silent Push 수신 시 호출

func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable : Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
print("🟠", #function)
completionHandler(.newData)
}

}

// MARK: - MessagingDelegate
/// 정상적으로 FCM 토큰이 발급되면 호출되는 메서드

extension AppDelegate: MessagingDelegate {
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
guard let fcmToken = fcmToken else { return }

print("Firebase registration token: \(String(describing: fcmToken))")

self.profileRepository.updateFCMToken(token: fcmToken)
}
}

// MARK: - UNUserNotificationCenterDelegate

extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse
) async {
guard let aps = response.notification.request.content.userInfo["aps"] as? [String: Any],
let badge = aps["badge"] as? Int
else {
return
}

userBadgeUseCase.execute(number: badge - 1)
.receive(on: DispatchQueue.main)
.sink { completion in
if case .failure(let error) = completion {
WableLogger.log("뱃지 수정 중 오류 발생: \(error)", for: .error)
}
} receiveValue: { [weak self] _ in
guard let self = self else { return }

self.userSessionRepository.updateUserSession(userID: activeID, notificationBadgeCount: badge - 1)
WableLogger.log("뱃지 수정 완료: \(badge - 1)개", for: .debug)
UIApplication.shared.applicationIconBadgeNumber = badge - 1
}
.store(in: cancelBag)

guard let relateContentID = response.notification.request.content.userInfo["relateContentId"] as? String,
let contentID = Int(relateContentID),
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootViewController = windowScene.windows.first?.rootViewController
else {
return
}

let detailViewController = HomeDetailViewController(
viewModel: HomeDetailViewModel(
contentID: contentID,
fetchContentInfoUseCase: FetchContentInfoUseCase(repository: contentRepository),
fetchContentCommentListUseCase: FetchContentCommentListUseCase(repository: commentRepository),
createCommentUseCase: CreateCommentUseCase(repository: commentRepository),
deleteCommentUseCase: DeleteCommentUseCase(repository: commentRepository),
createContentLikedUseCase: CreateContentLikedUseCase(repository: contentLikedRepository),
deleteContentLikedUseCase: DeleteContentLikedUseCase(repository: contentLikedRepository),
createCommentLikedUseCase: CreateCommentLikedUseCase(repository: commentLikedRepository),
deleteCommentLikedUseCase: DeleteCommentLikedUseCase(repository: commentLikedRepository),
fetchUserInformationUseCase: FetchUserInformationUseCase(repository: userSessionRepository),
fetchGhostUseCase: FetchGhostUseCase(repository: GhostRepositoryImpl()),
createReportUseCase: CreateReportUseCase(repository: reportRepository),
createBannedUseCase: CreateBannedUseCase(repository: reportRepository),
deleteContentUseCase: DeleteContentUseCase(repository: contentRepository)
),
cancelBag: CancelBag()
)

if let tabBarController = rootViewController as? TabBarController {
guard let viewController = tabBarController.selectedViewController as? UINavigationController else { return }

tabBarController.selectedIndex = 0
viewController.pushViewController(detailViewController, animated: true)

WableLogger.log("상세 화면으로 이동 완료", for: .debug)
} else
if let viewController = rootViewController as? UINavigationController {
viewController.pushViewController(detailViewController, animated: true)

WableLogger.log("상세 화면으로 이동 완료", for: .debug)
}
}

func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification
) async -> UNNotificationPresentationOptions {
print("🟢", #function)
return [.sound, .banner, .badge]
}
}

// MARK: - Helper Method
Expand All @@ -65,13 +217,4 @@ private extension AppDelegate {

activeID = id
}

func requestNotificationPermission() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { [weak self] granted, error in
guard let self = self,
let activeSession = userSessionRepository.fetchActiveUserSession() else { return }

self.userSessionRepository.updateUserSession(userID: activeSession.id, isPushAlarmAllowed: granted)
}
}
}
3 changes: 2 additions & 1 deletion Wable-iOS/App/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ private extension SceneDelegate {
loginRepository: loginRepository,
userSessionRepository: userSessionRepository
),
updateUserSessionUseCase: FetchUserInformationUseCase(repository: userSessionRepository)
updateUserSessionUseCase: FetchUserInformationUseCase(repository: userSessionRepository),
userProfileUseCase: UserProfileUseCase(repository: ProfileRepositoryImpl())
)
Comment on lines +94 to 96
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

⚠️ Inconsistent naming between parameter label and injected type

updateUserSessionUseCase is being initialised with FetchUserInformationUseCase(...).
This will compile if the constructor actually expects FetchUserInformationUseCase, but the label implies a different responsibility (update vs fetch). The mismatch is confusing for future readers and makes the dependency graph harder to reason about.

Consider either:

- updateUserSessionUseCase: FetchUserInformationUseCase(repository: userSessionRepository),
+ fetchUserInformationUseCase: FetchUserInformationUseCase(repository: userSessionRepository),

or injecting the real UpdateUserSessionUseCase if that is what the view-model needs.


🏁 Script executed:

#!/bin/bash
set -e
# Search for any definition or usage of UpdateUserSessionUseCase
rg "UpdateUserSessionUseCase" -n .

Length of output: 43


🏁 Script executed:

#!/bin/bash
set -e
# Locate SceneDelegate.swift
scene_file=$(fd SceneDelegate.swift -t f)
echo "SceneDelegate file: $scene_file"
# Show lines 1-200 to inspect initializer context
sed -n '1,200p' "$scene_file"

Length of output: 5767


🏁 Script executed:

#!/bin/bash
set -e
# Locate LoginViewModel.swift
login_vm_file=$(fd LoginViewModel.swift -t f)
echo "LoginViewModel file: $login_vm_file"
# Show the initializer signature and surrounding context
sed -n '1,200p' "$login_vm_file"

Length of output: 5948


🏁 Script executed:

#!/bin/bash
set -e
# Find definition of FetchUserInformationUseCase
rg "struct FetchUserInformationUseCase" -n .
rg "class FetchUserInformationUseCase" -n .

# Show its file contents for context
file=$(rg "FetchUserInformationUseCase" -l . | head -n 1)
echo "File: $file"
sed -n '1,200p' "$file"

# Search for updateUserSession method within this file
rg "updateUserSession" -n "$file"

Length of output: 55


🏁 Script executed:

#!/bin/bash
set -e
# Case-insensitive search for FetchUserInformationUseCase
rg -i "FetchUserInformationUseCase" -n .

Length of output: 5419


Rename updateUserSessionUseCase to match the injected FetchUserInformationUseCase

The parameter label (updateUserSessionUseCase) currently does not match the type being injected (FetchUserInformationUseCase), which is confusing. Since there is no UpdateUserSessionUseCase in the codebase, you should rename the label (and corresponding property/parameter in LoginViewModel) to fetchUserInformationUseCase.

• SceneDelegate.swift (around lines 94–96):

- updateUserSessionUseCase: FetchUserInformationUseCase(repository: userSessionRepository),
+ fetchUserInformationUseCase: FetchUserInformationUseCase(repository: userSessionRepository),

• Presentation/Login/LoginViewModel.swift:

- private let updateUserSessionUseCase: FetchUserInformationUseCase
+ private let fetchUserInformationUseCase: FetchUserInformationUseCase

 init(
     updateFCMTokenUseCase: UpdateFCMTokenUseCase,
     fetchUserAuthUseCase: FetchUserAuthUseCase,
-    updateUserSessionUseCase: FetchUserInformationUseCase,
+    fetchUserInformationUseCase: FetchUserInformationUseCase,
     userProfileUseCase: UserProfileUseCase
 ) { … }

 // Inside transform(...)
-    self.updateUserSessionUseCase.updateUserSession(…)
+    self.fetchUserInformationUseCase.updateUserSession(…)

Make sure to update all related call sites (AppDelegate, other ViewControllers/ViewModels) for consistency.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
updateUserSessionUseCase: FetchUserInformationUseCase(repository: userSessionRepository),
userProfileUseCase: UserProfileUseCase(repository: ProfileRepositoryImpl())
)
fetchUserInformationUseCase: FetchUserInformationUseCase(repository: userSessionRepository),
userProfileUseCase: UserProfileUseCase(repository: ProfileRepositoryImpl())
🤖 Prompt for AI Agents
In Wable-iOS/App/SceneDelegate.swift around lines 94 to 96, rename the parameter
label and any related property named updateUserSessionUseCase to
fetchUserInformationUseCase to match the injected type
FetchUserInformationUseCase. Also update the corresponding parameter and
property names in Presentation/Login/LoginViewModel.swift and all related call
sites such as AppDelegate and other ViewControllers/ViewModels to maintain
consistency and avoid confusion.

)
}
Expand Down
Loading