Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 149 additions & 16 deletions Wable-iOS.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 13 additions & 9 deletions Wable-iOS/App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,26 @@

import UIKit

import KakaoSDKCommon

@main
class AppDelegate: UIResponder, UIApplicationDelegate {



func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
KakaoSDK.initSDK(appKey: Bundle.kakaoAppKey)

return true
}

// MARK: UISceneSession Lifecycle

func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
func application(
_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions
) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
Expand All @@ -30,7 +37,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}


}

38 changes: 38 additions & 0 deletions Wable-iOS/App/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,53 @@
// Created by YOUJIM on 2/13/25.
//

import Combine
import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
private let cancelBag = CancelBag()
private let userSessionRepository: UserSessionRepository = UserSessionRepositoryImpl(
userDefaults: UserDefaultsStorage(
userDefaults: UserDefaults.standard,
jsonEncoder: JSONEncoder(),
jsonDecoder: JSONDecoder()
)
)

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
self.window = UIWindow(windowScene: windowScene)

userSessionRepository.checkAutoLogin()
.sink { completion in
switch completion {
case .finished:
break
case .failure(_):
self.configureLoginScreen()
}
} receiveValue: { isAutoLoginEnabled in
if isAutoLoginEnabled {
self.configureMainScreen()
} else {
self.configureLoginScreen()
}
}
.store(in: cancelBag)
}
}

// MARK: - Extension
// TODO: 각각 VC로 화면 이동하는 로직 구현 필요

private extension SceneDelegate {
func configureLoginScreen() {
self.window?.rootViewController = ViewController()
self.window?.makeKeyAndVisible()
}

func configureMainScreen() {
self.window?.rootViewController = ViewController()
self.window?.makeKeyAndVisible()
}
Expand Down
36 changes: 36 additions & 0 deletions Wable-iOS/Core/Bundle/Bundle+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// Bundle+.swift
// Wable-iOS
//
// Created by YOUJIM on 2/18/25.
//

import Foundation

extension Bundle {
static let baseURL: URL = {
guard let urlString = main.object(forInfoDictionaryKey: "BASE_URL") as? String,
let url = URL(string: urlString)
else {
fatalError("BASE_URL을 찾을 수 없습니다.")
}

return url
}()

static let kakaoAppKey: String = {
guard let key = main.object(forInfoDictionaryKey: "NATIVE_APP_KEY") as? String else {
fatalError("NATIVE_APP_KEY를 찾을 수 없습니다.")
}

return key
}()

static let amplitudeAppKey: String = {
guard let key = main.object(forInfoDictionaryKey: "AMPLITUDE_APP_KEY") as? String else {
fatalError("AMPLITUDE_APP_KEY를 찾을 수 없습니다.")
}

return key
}()
}
14 changes: 14 additions & 0 deletions Wable-iOS/Data/Provider/AuthProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// AuthProvider.swift
// Wable-iOS
//
// Created by YOUJIM on 3/6/25.
//


import Combine
import Foundation

protocol AuthProvider {
func authenticate() -> AnyPublisher<String?, WableError>
}
Comment on lines +12 to +14
Copy link
Collaborator

Choose a reason for hiding this comment

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

authenticate 메서드명 좋은 것 같습니다.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// LocalKeyValueStorage.swift
// LocalKeyValueProvider.swift
// Wable-iOS
//
// Created by YOUJIM on 3/5/25.
Expand All @@ -8,7 +8,7 @@

import Foundation

protocol LocalKeyValueStorage {
protocol LocalKeyValueProvider {
func setValue<T: Codable>(_ value: T, for key: String) throws
func getValue<T: Codable>(for key: String) throws -> T?
func removeValue(for key: String) throws
Expand Down
39 changes: 28 additions & 11 deletions Wable-iOS/Data/RepositoryImpl/LoginRepositoryImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,37 @@ import Moya

final class LoginRepositoryImpl {
private let provider = APIProvider<LoginTargetType>()
private let authProviders: [SocialPlatform: AuthProvider]

init(authProviders: [SocialPlatform: AuthProvider] = [
.apple: AppleAuthProvider(),
.kakao: KakaoAuthProvider()
]) {
self.authProviders = authProviders
}
}

extension LoginRepositoryImpl: LoginRepository {
func fetchUserAuth(platform: String, userName: String) -> AnyPublisher<Account, WableError> {
return provider.request(
.fetchUserAuth(
request: DTO.Request.CreateAccount(
socialPlatform: platform,
userName: userName
func fetchUserAuth(platform: SocialPlatform, userName: String?) -> AnyPublisher<Account, WableError> {
guard let provider = authProviders[platform] else {
return .fail(.unknownError)
}

return provider.authenticate()
.withUnretained(self)
.flatMap { owner, _ -> AnyPublisher<Account, WableError> in
return owner.provider.request(
.fetchUserAuth(
request: DTO.Request.CreateAccount(
socialPlatform: platform.rawValue,
userName: userName
)
),
for: DTO.Response.CreateAccount.self
)
),
for: DTO.Response.CreateAccount.self
)
.map(LoginMapper.toDomain)
.mapWableError()
.map(LoginMapper.toDomain)
.mapWableError()
}
.eraseToAnyPublisher()
}
}
58 changes: 43 additions & 15 deletions Wable-iOS/Data/RepositoryImpl/UserSessionRepositoryImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//


import Combine
import Foundation

class UserSessionRepositoryImpl {
Expand All @@ -14,21 +15,22 @@ class UserSessionRepositoryImpl {
static let activeUserID = "activeID"
}

private let userDefaults = UserDefaultsStorage(
userDefaults: UserDefaults.standard,
jsonEncoder: JSONEncoder(),
jsonDecoder: JSONDecoder()
)
private let userDefaults: LocalKeyValueProvider
private let tokenStorage = TokenStorage(keyChainStorage: KeychainStorage())

init(userDefaults: LocalKeyValueProvider) {
self.userDefaults = userDefaults
}
}

// MARK: - UserSessionRepository

extension UserSessionRepositoryImpl: UserSessionRepository {
func fetchAllUserSessions() -> [String: UserSession] {
func fetchAllUserSessions() -> [Int: UserSession] {
return (try? userDefaults.getValue(for: Keys.userSessions)) ?? [:]
}

func fetchUserSession(forUserID userID: String) -> UserSession? {
func fetchUserSession(forUserID userID: Int) -> UserSession? {
return fetchAllUserSessions()[userID]
}

Expand All @@ -39,23 +41,23 @@ extension UserSessionRepositoryImpl: UserSessionRepository {
return fetchUserSession(forUserID: activeUserID)
}

func fetchActiveUserID() -> String? {
func fetchActiveUserID() -> Int? {
return try? userDefaults.getValue(for: Keys.activeUserID)
}

func updateUserSession(_ session: UserSession, forUserID userID: String) {
func updateUserSession(_ session: UserSession, forUserID userID: Int) {
var sessions = fetchAllUserSessions()

sessions[userID] = session

try? userDefaults.setValue(sessions, for: Keys.userSessions)

if fetchActiveUserID() == nil {
updateActiveUserID(forUserID: userID)
updateActiveUserID(userID)
}
}

func updateAutoLogin(enabled: Bool, forUserID userID: String) {
func updateAutoLogin(enabled: Bool, forUserID userID: Int) {
var sessions = fetchAllUserSessions()

if let session = sessions[userID] {
Expand All @@ -74,7 +76,7 @@ extension UserSessionRepositoryImpl: UserSessionRepository {
}
}

func updateNotificationBadge(count: Int, forUserID userID: String) {
func updateNotificationBadge(count: Int, forUserID userID: Int) {
var sessions = fetchAllUserSessions()

if let session = sessions[userID] {
Expand All @@ -92,20 +94,46 @@ extension UserSessionRepositoryImpl: UserSessionRepository {
}
}

func updateActiveUserID(forUserID userID: String?) {
func updateActiveUserID(_ userID: Int?) {
if let userID = userID {
try? userDefaults.setValue(userID, for: Keys.activeUserID)
}
}

func removeUserSession(forUserID userID: String) {
func removeUserSession(forUserID userID: Int) {
var sessions = fetchAllUserSessions()

sessions.removeValue(forKey: userID)
try? userDefaults.setValue(sessions, for: Keys.userSessions)

if fetchActiveUserID() == userID {
updateActiveUserID(forUserID: nil)
updateActiveUserID(nil)
}
}
}

// MARK: - 자동 로그인 관련 Extension

extension UserSessionRepositoryImpl {
func checkAutoLogin() -> AnyPublisher<Bool, Error> {
guard let userSession = fetchActiveUserSession(),
userSession.isAutoLoginEnabled == true else {
return .just(false)
}

do {
let _ = try tokenStorage.load(.wableAccessToken), _ = try tokenStorage.load(.wableRefreshToken)
return .just(true)
} catch {
return .fail(error)
}
}

func enableAutoLogin(for userID: Int) {
updateAutoLogin(enabled: true, forUserID: userID)
}

func disableAutoLogin(for userID: Int) {
updateAutoLogin(enabled: false, forUserID: userID)
}
}
2 changes: 1 addition & 1 deletion Wable-iOS/Domain/RepositoryInterface/LoginRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ import Combine
import Foundation

protocol LoginRepository {
func fetchUserAuth(platform: String, userName: String) -> AnyPublisher<Account, WableError>
func fetchUserAuth(platform: SocialPlatform, userName: String?) -> AnyPublisher<Account, WableError>
}
18 changes: 10 additions & 8 deletions Wable-iOS/Domain/RepositoryInterface/UserSessionRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,19 @@


import Foundation
import Combine

// MARK: - UserSessionRepository

protocol UserSessionRepository {
func fetchAllUserSessions() -> [String: UserSession]
func fetchUserSession(forUserID userID: String) -> UserSession?
func fetchAllUserSessions() -> [Int: UserSession]
func fetchUserSession(forUserID userID: Int) -> UserSession?
func fetchActiveUserSession() -> UserSession?
func fetchActiveUserID() -> String?
func updateUserSession(_ session: UserSession, forUserID userID: String)
func updateAutoLogin(enabled: Bool, forUserID userID: String)
func updateNotificationBadge(count: Int, forUserID userID: String)
func updateActiveUserID(forUserID userID: String?)
func removeUserSession(forUserID userID: String)
func fetchActiveUserID() -> Int?
func updateUserSession(_ session: UserSession, forUserID userID: Int)
func updateAutoLogin(enabled: Bool, forUserID userID: Int)
func updateNotificationBadge(count: Int, forUserID userID: Int)
func updateActiveUserID(_ userID: Int?)
func removeUserSession(forUserID userID: Int)
func checkAutoLogin() -> AnyPublisher<Bool, Error>
}
Loading