diff --git a/.run/newm-mobile_shared [assembleXCFramework].run.xml b/.run/newm-mobile_shared [assembleXCFramework].run.xml
new file mode 100644
index 00000000..f5b4bfec
--- /dev/null
+++ b/.run/newm-mobile_shared [assembleXCFramework].run.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+ false
+
+
+
\ No newline at end of file
diff --git a/android/app-newm/src/main/java/io/newm/di/android/Dependencies.kt b/android/app-newm/src/main/java/io/newm/di/android/Dependencies.kt
index 362ef669..8b088fde 100644
--- a/android/app-newm/src/main/java/io/newm/di/android/Dependencies.kt
+++ b/android/app-newm/src/main/java/io/newm/di/android/Dependencies.kt
@@ -55,7 +55,7 @@ val viewModule = module {
WelcomeScreenPresenter(
navigator = params.get(),
googleSignInLauncher = get(),
- repository = get(),
+ loginUseCase = get(),
activityResultContract = ActivityResultContracts.StartActivityForResult()
)
}
diff --git a/android/features/login/src/main/java/io/newm/feature/login/screen/welcome/WelcomeScreenPresenter.kt b/android/features/login/src/main/java/io/newm/feature/login/screen/welcome/WelcomeScreenPresenter.kt
index efa9fe14..6ee6eaba 100644
--- a/android/features/login/src/main/java/io/newm/feature/login/screen/welcome/WelcomeScreenPresenter.kt
+++ b/android/features/login/src/main/java/io/newm/feature/login/screen/welcome/WelcomeScreenPresenter.kt
@@ -1,7 +1,6 @@
package io.newm.feature.login.screen.welcome
import android.content.Intent
-import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContract
@@ -22,12 +21,12 @@ import io.newm.feature.login.screen.LoginScreen
import io.newm.feature.login.screen.authproviders.google.GoogleSignInLauncher
import io.newm.feature.login.screen.createaccount.CreateAccountScreen
import io.newm.shared.login.repository.KMMException
-import io.newm.shared.login.repository.LogInRepository
import io.newm.shared.login.repository.OAuthData
+import io.newm.shared.usecases.LoginUseCase
class WelcomeScreenPresenter(
private val navigator: Navigator,
- private val repository: LogInRepository,
+ private val loginUseCase: LoginUseCase,
private val googleSignInLauncher: GoogleSignInLauncher,
private val activityResultContract: ActivityResultContract,
) : Presenter {
@@ -56,7 +55,7 @@ class WelcomeScreenPresenter(
val idToken = account.idToken
idToken ?: throw IllegalStateException("Google sign in failed. idToken is null")
- repository.oAuthLogin(OAuthData.Google(idToken))
+ loginUseCase.logInWithGoogle(idToken)
navigator.goTo(HomeScreen)
} catch (e: ApiException) {
// The ApiException status code indicates the detailed failure reason.
diff --git a/gradle.properties b/gradle.properties
index 6d37e097..08f9aa8d 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -17,4 +17,4 @@ android.useAndroidX=true
#MPP
kotlin.mpp.enableCInteropCommonization=true
kotlin.native.binary.memoryModel=experimental
-kotlin.mpp.androidSourceSetLayoutVersion=2
\ No newline at end of file
+kotlin.mpp.androidSourceSetLayoutVersion=2
diff --git a/iosApp/Modules/DI/ModuleLinker/Extensions/Error+KMMException.swift b/iosApp/Modules/DI/ModuleLinker/Extensions/Error+KMMException.swift
new file mode 100644
index 00000000..f8228ab4
--- /dev/null
+++ b/iosApp/Modules/DI/ModuleLinker/Extensions/Error+KMMException.swift
@@ -0,0 +1,6 @@
+import Foundation
+import shared
+
+public extension Error {
+ var kmmException: KMMException? { (self as NSError).kotlinException as? KMMException }
+}
diff --git a/iosApp/Modules/DI/ModuleLinker/Extensions/Notification+KMM.swift b/iosApp/Modules/DI/ModuleLinker/Extensions/Notification+KMM.swift
new file mode 100644
index 00000000..90d679e5
--- /dev/null
+++ b/iosApp/Modules/DI/ModuleLinker/Extensions/Notification+KMM.swift
@@ -0,0 +1,7 @@
+import Foundation
+
+public extension NotificationCenter {
+ func publisher(for name: String) -> Publisher {
+ publisher(for: Notification.Name(name), object: nil)
+ }
+}
diff --git a/iosApp/Modules/DI/ModuleLinker/Protocols/Data.swift b/iosApp/Modules/DI/ModuleLinker/Protocols/Data.swift
index 2b2e2945..1acfa233 100644
--- a/iosApp/Modules/DI/ModuleLinker/Protocols/Data.swift
+++ b/iosApp/Modules/DI/ModuleLinker/Protocols/Data.swift
@@ -1,19 +1,19 @@
-import Foundation
-import Models
-
-public protocol UserManaging: ObservableObject {
- var currentUser: User? { get }
-
- func deleteUser() async throws
- func createUser(nickname: String, email: String, password: String, passwordConfirmation: String, verificationCode: String) async throws
- func resetPassword(email: String, password: String, confirmPassword: String, authCode: String) async throws
- func fetchCurrentUser() async throws
- func updateUserInfo(firstName: String?, lastName: String?, currentPassword: String?, newPassword: String?, confirmNewPassword: String?) async throws
-}
-
-public enum UserRepoError: LocalizedError {
- case accountExists
- case twoFAFailed
- case dataUnavailable
- case displayError(String)
-}
+//import Foundation
+//import Models
+//
+//public protocol UserManaging: ObservableObject {
+// var currentUser: User? { get }
+//
+// func deleteUser() async throws
+// func createUser(nickname: String, email: String, password: String, passwordConfirmation: String, verificationCode: String) async throws
+// func resetPassword(email: String, password: String, confirmPassword: String, authCode: String) async throws
+// func fetchCurrentUser() async throws
+// func updateUserInfo(firstName: String?, lastName: String?, currentPassword: String?, newPassword: String?, confirmNewPassword: String?) async throws
+//}
+//
+//public enum UserRepoError: LocalizedError {
+// case accountExists
+// case twoFAFailed
+// case dataUnavailable
+// case displayError(String)
+//}
diff --git a/iosApp/Modules/Data/DataModule.swift b/iosApp/Modules/Data/DataModule.swift
index f22be5ec..eb4cf306 100644
--- a/iosApp/Modules/Data/DataModule.swift
+++ b/iosApp/Modules/Data/DataModule.swift
@@ -7,9 +7,6 @@ public final class DataModule: ModuleProtocol {
public static let shared = DataModule()
public func registerAllServices() {
- Resolver.register {
- UserRepo.shared as any UserManaging
- }
}
}
diff --git a/iosApp/Modules/Data/Login/LoginRepo.swift b/iosApp/Modules/Data/Login/LoginRepo.swift
index a3375a84..a4db4b06 100644
--- a/iosApp/Modules/Data/Login/LoginRepo.swift
+++ b/iosApp/Modules/Data/Login/LoginRepo.swift
@@ -1,70 +1,67 @@
-import Foundation
-import API
-import KeychainSwift
-import FacebookLogin
-import GoogleSignIn
-import AuthenticationServices
-
-enum LoginError: Error {
- case facebookAccessTokenMissing
-}
-
-public class LoginRepo: ObservableObject {
- //TODO: update this with user info
- @Published public var userIsLoggedIn: Bool = false
- private let api: LoginAPI
-
- private var appleSignInID: String?
-
- //TODO: make actor
- public static let shared = LoginRepo()
-
- private init(api: LoginAPI = LoginAPI()) {
- self.api = api
- api.$userIsLoggedIn.assign(to: &$userIsLoggedIn)
- }
-
- public func loginWithFacebook(result: LoginManagerLoginResult) async throws {
- do {
- guard let token = result.token?.tokenString else {
- throw LoginError.facebookAccessTokenMissing
- }
- try await api.login(method: .facebook(accessToken: token))
- } catch {
- Task { @MainActor in
- FacebookLogin.LoginManager().logOut()
- }
- throw error
- }
- }
-
- public func loginWithGoogle(result: GIDSignInResult) async throws {
- let token = result.user.accessToken.tokenString
- try await api.login(method: .google(accessToken: token))
- }
-
- public func login(email: String, password: String) async throws {
- try await api.login(method: .email(email: email, password: password))
- }
-
- public func logOut() {
- api.logOut()
- FacebookLogin.LoginManager().logOut()
- GIDSignIn.sharedInstance.signOut()
- signOutOfApple()
- UserRepo.shared.currentUser = nil
- }
-
- public func requestEmailVerificationCode(for email: String) async throws {
- try await api.requestEmailVerificationCode(for: email)
- }
-
- public func loginWithApple(result: ASAuthorization) async throws {
- let token = String(data: (result.credential as! ASAuthorizationAppleIDCredential).identityToken!, encoding: .utf8)!
- try await api.login(method: .apple(idToken: token))
- }
-
- private func signOutOfApple() {
- //TODO:
- }
-}
+//import Foundation
+//import API
+//import KeychainSwift
+//import FacebookLogin
+//import GoogleSignIn
+//import AuthenticationServices
+//
+//enum LoginError: Error {
+// case facebookAccessTokenMissing
+//}
+//
+//public class LoginRepo: ObservableObject {
+// //TODO: update this with user info
+// @Published public var userIsLoggedIn: Bool = false
+// private let api: LoginAPI
+//
+// private var appleSignInID: String?
+//
+// private init(api: LoginAPI = LoginAPI()) {
+// self.api = api
+// api.$userIsLoggedIn.assign(to: &$userIsLoggedIn)
+// }
+//
+// public func loginWithFacebook(result: LoginManagerLoginResult) async throws {
+// do {
+// guard let token = result.token?.tokenString else {
+// throw LoginError.facebookAccessTokenMissing
+// }
+// try await api.login(method: .facebook(accessToken: token))
+// } catch {
+// Task { @MainActor in
+// FacebookLogin.LoginManager().logOut()
+// }
+// throw error
+// }
+// }
+//
+// public func loginWithGoogle(result: GIDSignInResult) async throws {
+// let token = result.user.accessToken.tokenString
+// try await api.login(method: .google(accessToken: token))
+// }
+//
+// public func login(email: String, password: String) async throws {
+// try await api.login(method: .email(email: email, password: password))
+// }
+//
+// public func logOut() {
+// api.logOut()
+// FacebookLogin.LoginManager().logOut()
+// GIDSignIn.sharedInstance.signOut()
+// signOutOfApple()
+// UserRepo.shared.currentUser = nil
+// }
+//
+// public func requestEmailVerificationCode(for email: String) async throws {
+// try await api.requestEmailVerificationCode(for: email)
+// }
+//
+// public func loginWithApple(result: ASAuthorization) async throws {
+// let token = String(data: (result.credential as! ASAuthorizationAppleIDCredential).identityToken!, encoding: .utf8)!
+// try await api.login(method: .apple(idToken: token))
+// }
+//
+// private func signOutOfApple() {
+// //TODO:
+// }
+//}
diff --git a/iosApp/Modules/Data/User/UserRepo.swift b/iosApp/Modules/Data/User/UserRepo.swift
index a726c0fb..16490e5a 100644
--- a/iosApp/Modules/Data/User/UserRepo.swift
+++ b/iosApp/Modules/Data/User/UserRepo.swift
@@ -1,84 +1,86 @@
-import Foundation
-import API
-import Models
-import ModuleLinker
-
-class UserRepo: UserManaging, ObservableObject {
- private let api = UserAPI()
-
- @Published public var currentUser: User?
-
- //TODO: make actor
- public static let shared = UserRepo()
-
- private init() {}
-
- public func deleteUser() async throws {
- do {
- try await api.delete()
- } catch {
- try handleError(error)
- }
- LoginRepo.shared.logOut()
- }
-
- public func createUser(nickname: String, email: String, password: String, passwordConfirmation: String, verificationCode: String) async throws {
- do {
- try await api.create(nickname: nickname, email: email, password: password, passwordConfirmation: passwordConfirmation, verificationCode: verificationCode)
- } catch {
- try handleError(error)
- }
- }
-
- public func resetPassword(email: String, password: String, confirmPassword: String, authCode: String) async throws {
- do {
- try await api.resetPassword(email: email, password: password, confirmPassword: confirmPassword, authCode: authCode)
- } catch {
- try handleError(error)
- }
- }
-
- public func fetchCurrentUser() async throws {
- do {
- currentUser = try await api.getCurrentUser()
- } catch {
- try handleError(error)
- }
- }
-
- public func updateUserInfo(firstName: String?, lastName: String?, currentPassword: String?, newPassword: String?, confirmNewPassword: String?) async throws {
- let updatedUser = try UpdatedUser(
- firstName: firstName?.nilIfEmpty,
- lastName: lastName?.nilIfEmpty,
- newPassword: newPassword?.nilIfEmpty,
- confirmPassword: confirmNewPassword?.nilIfEmpty,
- currentPassword: currentPassword?.nilIfEmpty
- )
- do {
- try await api.update(user: updatedUser)
- try await fetchCurrentUser()
- } catch {
- try handleError(error)
- }
- }
-
- private func handleError(_ error: Error) throws {
- if let error = error as? UserAPI.Error {
- switch error {
- case .accountExists:
- throw UserRepoError.accountExists
- case .twoFAFailed:
- throw UserRepoError.twoFAFailed
- case .unprocessableEntity(let cause):
- throw UserRepoError.displayError(cause)
- }
- } else if let error = error as? APIError {
- switch error {
- case .invalidResponse:
- throw UserRepoError.dataUnavailable
- case .httpError(_, let cause):
- throw UserRepoError.displayError(cause ?? "An unknown error occurred.")
- }
- }
- }
-}
+//import Foundation
+//import API
+//import Models
+//import ModuleLinker
+//import Resolver
+//
+//class UserRepo: UserManaging, ObservableObject {
+// private let api = UserAPI()
+// @Injected private var loginRepo: LoginRepo
+//
+// @Published public var currentUser: User?
+//
+// //TODO: make actor
+// public static let shared = UserRepo()
+//
+// private init() {}
+//
+// public func deleteUser() async throws {
+// do {
+// try await api.delete()
+// } catch {
+// try handleError(error)
+// }
+// loginRepo.logOut()
+// }
+//
+// public func createUser(nickname: String, email: String, password: String, passwordConfirmation: String, verificationCode: String) async throws {
+// do {
+// try await api.create(nickname: nickname, email: email, password: password, passwordConfirmation: passwordConfirmation, verificationCode: verificationCode)
+// } catch {
+// try handleError(error)
+// }
+// }
+//
+// public func resetPassword(email: String, password: String, confirmPassword: String, authCode: String) async throws {
+// do {
+// try await api.resetPassword(email: email, password: password, confirmPassword: confirmPassword, authCode: authCode)
+// } catch {
+// try handleError(error)
+// }
+// }
+//
+// public func fetchCurrentUser() async throws {
+// do {
+// currentUser = try await api.getCurrentUser()
+// } catch {
+// try handleError(error)
+// }
+// }
+//
+// public func updateUserInfo(firstName: String?, lastName: String?, currentPassword: String?, newPassword: String?, confirmNewPassword: String?) async throws {
+// let updatedUser = try UpdatedUser(
+// firstName: firstName?.nilIfEmpty,
+// lastName: lastName?.nilIfEmpty,
+// newPassword: newPassword?.nilIfEmpty,
+// confirmPassword: confirmNewPassword?.nilIfEmpty,
+// currentPassword: currentPassword?.nilIfEmpty
+// )
+// do {
+// try await api.update(user: updatedUser)
+// try await fetchCurrentUser()
+// } catch {
+// try handleError(error)
+// }
+// }
+//
+// private func handleError(_ error: Error) throws {
+// if let error = error as? UserAPI.Error {
+// switch error {
+// case .accountExists:
+// throw UserRepoError.accountExists
+// case .twoFAFailed:
+// throw UserRepoError.twoFAFailed
+// case .unprocessableEntity(let cause):
+// throw UserRepoError.displayError(cause)
+// }
+// } else if let error = error as? APIError {
+// switch error {
+// case .invalidResponse:
+// throw UserRepoError.dataUnavailable
+// case .httpError(_, let cause):
+// throw UserRepoError.displayError(cause ?? "An unknown error occurred.")
+// }
+// }
+// }
+//}
diff --git a/iosApp/Modules/Helpers/SharedUI/SwiftUI+Common.swift b/iosApp/Modules/Helpers/SharedUI/SwiftUI+Common.swift
index 7ce42d12..b773b912 100644
--- a/iosApp/Modules/Helpers/SharedUI/SwiftUI+Common.swift
+++ b/iosApp/Modules/Helpers/SharedUI/SwiftUI+Common.swift
@@ -88,8 +88,8 @@ public struct BorderOverlay: ViewModifier {
public func body(content: Content) -> some View {
content
- .overlay(RoundedRectangle(cornerRadius: radius)
- .stroke(color, lineWidth: width))
+// .overlay(RoundedRectangle(cornerRadius: radius)
+// .stroke(color, lineWidth: width))
.erased
}
}
diff --git a/iosApp/Modules/Screens/Home/HomeModule.swift b/iosApp/Modules/Screens/Home/HomeModule.swift
index 2f5ca5db..e0f07cf3 100644
--- a/iosApp/Modules/Screens/Home/HomeModule.swift
+++ b/iosApp/Modules/Screens/Home/HomeModule.swift
@@ -19,10 +19,6 @@ extension HomeModule {
mockResolver.register {
MockHomeViewUIModelProvider(actionHandler: $0.resolve()) as HomeViewUIModelProviding
}
-
- mockResolver.register {
- MockUserRepo() as any UserManaging
- }
}
}
#endif
diff --git a/iosApp/Modules/Screens/Home/Mocks/MockUserManager.swift b/iosApp/Modules/Screens/Home/Mocks/MockUserManager.swift
index 02899f68..ca156911 100644
--- a/iosApp/Modules/Screens/Home/Mocks/MockUserManager.swift
+++ b/iosApp/Modules/Screens/Home/Mocks/MockUserManager.swift
@@ -1,60 +1,60 @@
-import Foundation
-import Data
-import Models
-import ModuleLinker
-
-class MockUserRepo: UserManaging {
- func deleteUser() async throws {
-
- }
-
- func createUser(nickname: String, email: String, password: String, passwordConfirmation: String, verificationCode: String) async throws {
-
- }
-
- func resetPassword(email: String, password: String, confirmPassword: String, authCode: String) async throws {
-
- }
-
- func fetchCurrentUser() async throws {
- currentUser = User(
- id: "1",
- createdAt: "today",
- firstName: "Joe",
- lastName: "Blow",
- nickname: "Joe Blow",
- pictureUrl: URL(string: "https://resizing.flixster.com/xhyRkgdbTuATF4u0C2pFWZQZZtw=/300x300/v2/https://flxt.tmsimg.com/assets/p175884_k_v9_ae.jpg")!,
- bannerUrl: URL(string: "https://www.sonypictures.com/sites/default/files/styles/max_360x390/public/banner-images/2020-04/stepbrothers_banner_2572x1100_v2.png?h=abc6acbe&itok=7m7ZKbdz")!,
- email: "joe@blow.com"
- )
- }
-
- func updateUserInfo(firstName: String?, lastName: String?, currentPassword: String?, newPassword: String?, confirmNewPassword: String?) async throws {
- firstName.flatMap {
- guard let currentUser else { return }
- self.currentUser = User(id: currentUser.id,
- createdAt: currentUser.createdAt,
- firstName: $0,
- lastName: currentUser.lastName,
- nickname: currentUser.nickname,
- pictureUrl: currentUser.pictureUrl,
- bannerUrl: currentUser.bannerUrl,
- email: currentUser.email)
- }
- lastName.flatMap {
- guard let currentUser else { return }
- self.currentUser = User(id: currentUser.id,
- createdAt: currentUser.createdAt,
- firstName: currentUser.firstName,
- lastName: $0,
- nickname: currentUser.nickname,
- pictureUrl: currentUser.pictureUrl,
- bannerUrl: currentUser.bannerUrl,
- email: currentUser.email)
- }
- }
-
- init() {}
-
- var currentUser: User?
-}
+//import Foundation
+//import Data
+//import Models
+//import ModuleLinker
+//
+//class MockUserRepo: UserManaging {
+// func deleteUser() async throws {
+//
+// }
+//
+// func createUser(nickname: String, email: String, password: String, passwordConfirmation: String, verificationCode: String) async throws {
+//
+// }
+//
+// func resetPassword(email: String, password: String, confirmPassword: String, authCode: String) async throws {
+//
+// }
+//
+// func fetchCurrentUser() async throws {
+// currentUser = User(
+// id: "1",
+// createdAt: "today",
+// firstName: "Joe",
+// lastName: "Blow",
+// nickname: "Joe Blow",
+// pictureUrl: URL(string: "https://resizing.flixster.com/xhyRkgdbTuATF4u0C2pFWZQZZtw=/300x300/v2/https://flxt.tmsimg.com/assets/p175884_k_v9_ae.jpg")!,
+// bannerUrl: URL(string: "https://www.sonypictures.com/sites/default/files/styles/max_360x390/public/banner-images/2020-04/stepbrothers_banner_2572x1100_v2.png?h=abc6acbe&itok=7m7ZKbdz")!,
+// email: "joe@blow.com"
+// )
+// }
+//
+// func updateUserInfo(firstName: String?, lastName: String?, currentPassword: String?, newPassword: String?, confirmNewPassword: String?) async throws {
+// firstName.flatMap {
+// guard let currentUser else { return }
+// self.currentUser = User(id: currentUser.id,
+// createdAt: currentUser.createdAt,
+// firstName: $0,
+// lastName: currentUser.lastName,
+// nickname: currentUser.nickname,
+// pictureUrl: currentUser.pictureUrl,
+// bannerUrl: currentUser.bannerUrl,
+// email: currentUser.email)
+// }
+// lastName.flatMap {
+// guard let currentUser else { return }
+// self.currentUser = User(id: currentUser.id,
+// createdAt: currentUser.createdAt,
+// firstName: currentUser.firstName,
+// lastName: $0,
+// nickname: currentUser.nickname,
+// pictureUrl: currentUser.pictureUrl,
+// bannerUrl: currentUser.bannerUrl,
+// email: currentUser.email)
+// }
+// }
+//
+// init() {}
+//
+// var currentUser: User?
+//}
diff --git a/iosApp/Modules/Screens/Home/UI/Profile/ProfileMoreViewModel.swift b/iosApp/Modules/Screens/Home/UI/Profile/ProfileMoreViewModel.swift
index 338ac268..996e573d 100644
--- a/iosApp/Modules/Screens/Home/UI/Profile/ProfileMoreViewModel.swift
+++ b/iosApp/Modules/Screens/Home/UI/Profile/ProfileMoreViewModel.swift
@@ -1,13 +1,14 @@
import Foundation
import Data
import Resolver
+import shared
@MainActor
class ProfileMoreViewModel: ObservableObject {
typealias Row = (title: String, url: String)
typealias Section = (title: String, rows: [Row])
- private let loginRepo = LoginRepo.shared
+ @Injected private var loginUseCase: LoginUseCase
var sections: [Section] {
[
@@ -24,6 +25,6 @@ class ProfileMoreViewModel: ObservableObject {
}
func logOut() {
- loginRepo.logOut()
+ loginUseCase.logOut()
}
}
diff --git a/iosApp/Modules/Screens/Home/UI/Profile/ProfileView.swift b/iosApp/Modules/Screens/Home/UI/Profile/ProfileView.swift
index 4f0de655..8b83d277 100644
--- a/iosApp/Modules/Screens/Home/UI/Profile/ProfileView.swift
+++ b/iosApp/Modules/Screens/Home/UI/Profile/ProfileView.swift
@@ -26,7 +26,7 @@ struct ProfileView: View {
ZStack {
ScrollView {
VStack(alignment: .leading) {
- HeaderImageSection(viewModel.user?.bannerUrl?.absoluteString)
+// HeaderImageSection(viewModel.user?.bannerUrl?.absoluteString)
artistImage
bottomSection
.addSidePadding()
@@ -76,7 +76,7 @@ struct ProfileView: View {
@ViewBuilder
private var artistImage: some View {
- AsyncImage(url: viewModel.user?.pictureUrl)
+ AsyncImage(url: /*viewModel.user?.pictureUrl*/nil)
.circle(size: userImageSize)
.padding(.top, -(sectionSpacing+userImageSize/2))
}
diff --git a/iosApp/Modules/Screens/Home/UI/Profile/ProfileViewModel.swift b/iosApp/Modules/Screens/Home/UI/Profile/ProfileViewModel.swift
index 3ee672a2..759e0f9c 100644
--- a/iosApp/Modules/Screens/Home/UI/Profile/ProfileViewModel.swift
+++ b/iosApp/Modules/Screens/Home/UI/Profile/ProfileViewModel.swift
@@ -4,12 +4,13 @@ import Resolver
import Models
import ModuleLinker
import Combine
+import shared
@MainActor
final class ProfileViewModel: ObservableObject {
- @Injected private var userRepo: any UserManaging
+// @Injected private var userRepo: any UserManaging
- var user: User? { userRepo.currentUser }
+// var user: User? { userRepo.currentUser }
@Published var firstName: String = ""
@Published var lastName: String = ""
@@ -26,27 +27,28 @@ final class ProfileViewModel: ObservableObject {
var ptrOffset: CGFloat = 0
var showSaveButton: Bool {
- guard let user = user else { return false }
-
- func hasNewPassword() -> Bool {
- currentPassword.isEmpty == false &&
- newPassword.isEmpty == false &&
- confirmPassword.isEmpty == false
- }
-
- func infoFieldsAreEmpty() -> Bool {
- firstName.isEmpty &&
- lastName.isEmpty &&
- currentPassword.isEmpty
- }
-
- func hasNewInfo() -> Bool {
- firstName != user.firstName ||
- lastName != user.lastName ||
- email != user.email
- }
-
- return hasNewPassword() || (infoFieldsAreEmpty() == false && hasNewInfo())
+// guard let user = user else { return false }
+//
+// func hasNewPassword() -> Bool {
+// currentPassword.isEmpty == false &&
+// newPassword.isEmpty == false &&
+// confirmPassword.isEmpty == false
+// }
+//
+// func infoFieldsAreEmpty() -> Bool {
+// firstName.isEmpty &&
+// lastName.isEmpty &&
+// currentPassword.isEmpty
+// }
+//
+// func hasNewInfo() -> Bool {
+// firstName != user.firstName ||
+// lastName != user.lastName ||
+// email != user.email
+// }
+//
+// return hasNewPassword() || (infoFieldsAreEmpty() == false && hasNewInfo())
+ false
}
init() {
@@ -58,37 +60,37 @@ final class ProfileViewModel: ObservableObject {
}
func loadUser() async {
- // don't set loading state here, since this might be called from the view's "refreshable"
- do {
- try await userRepo.fetchCurrentUser()
- updateUserFields()
- } catch {
- self.error = error.localizedDescription
- }
+// // don't set loading state here, since this might be called from the view's "refreshable"
+// do {
+//// try await userRepo.fetchCurrentUser()
+// updateUserFields()
+// } catch {
+// self.error = error.localizedDescription
+// }
}
func save() {
Task {
isLoading = true
- do {
- try await userRepo.updateUserInfo(
- firstName: firstName,
- lastName: lastName,
- currentPassword: currentPassword,
- newPassword: newPassword,
- confirmNewPassword: confirmPassword
- )
- } catch {
- self.error = error.localizedDescription
- }
+// do {
+//// try await userRepo.updateUserInfo(
+//// firstName: firstName,
+//// lastName: lastName,
+//// currentPassword: currentPassword,
+//// newPassword: newPassword,
+//// confirmNewPassword: confirmPassword
+//// )
+// } catch {
+// self.error = error.localizedDescription
+// }
isLoading = false
}
}
private func updateUserFields() {
- guard let user = user else { return }
- firstName = user.firstName.emptyIfNil
- lastName = user.lastName.emptyIfNil
- email = user.email.emptyIfNil
+// guard let user = user else { return }
+// firstName = user.firstName.emptyIfNil
+// lastName = user.lastName.emptyIfNil
+// email = user.email.emptyIfNil
}
}
diff --git a/iosApp/Modules/Screens/Login/LoginModule.swift b/iosApp/Modules/Screens/Login/LoginModule.swift
index 806ee5a2..0f39dd30 100644
--- a/iosApp/Modules/Screens/Login/LoginModule.swift
+++ b/iosApp/Modules/Screens/Login/LoginModule.swift
@@ -2,6 +2,7 @@ import Foundation
import ModuleLinker
import Resolver
import SwiftUI
+import shared
public final class LoginModule: ModuleProtocol {
public static var shared = LoginModule()
@@ -10,23 +11,20 @@ public final class LoginModule: ModuleProtocol {
Resolver.register {
self as LoginViewProviding
}
+
+ Resolver.register {
+ LoginUseCaseProvider().get() as LoginUseCase
+ }
+
+ Resolver.register {
+ SignupUseCaseProvider().get() as SignupUseCase
+ }
}
}
#if DEBUG
extension LoginModule {
public func registerAllMockedServices(mockResolver: Resolver) {
-// mockResolver.register {
-// MockLogInLogOutUseCase.shared as LoggedInUserUseCaseProtocol
-// }
-
-// mockResolver.register {
-// MockLogInLogOutUseCase.shared as LoginRepo
-// }
-
-// mockResolver.register {
-// MockLogInLogOutUseCase.shared as LogOutUseCaseProtocol
-// }
}
}
#endif
diff --git a/iosApp/Modules/Screens/Login/UI/ViewModels/LandingViewModel+ErrorHandling.swift b/iosApp/Modules/Screens/Login/UI/ViewModels/LandingViewModel+ErrorHandling.swift
new file mode 100644
index 00000000..e249c818
--- /dev/null
+++ b/iosApp/Modules/Screens/Login/UI/ViewModels/LandingViewModel+ErrorHandling.swift
@@ -0,0 +1,53 @@
+import Foundation
+import shared
+
+extension LandingViewModel {
+ func handleError(_ error: Error) {
+ if let error = error.kmmException {
+ handleKotlinError(error)
+ } else {
+ self.error = error.localizedDescription
+ }
+ }
+
+ private func handleKotlinError(_ kmmException: KMMException) {
+ switch kmmException {
+ case let kmmException as RegisterException:
+ handleRegisterException(kmmException)
+ case let kmmException as LoginException:
+ handleLoginException(kmmException)
+ default:
+ break
+ }
+ self.error = kmmException.message
+ }
+
+ private func handleRegisterException(_ exception: RegisterException) {
+ switch exception {
+ case is RegisterException.UserAlreadyExists:
+ email = ""
+ navigateBackTo(.createAccount)
+ case is RegisterException.TwoFactorAuthenticationFailed:
+ confirmationCode = ""
+ navigateBackTo(.codeConfirmation)
+ default:
+ break
+ }
+ }
+
+ private func handleLoginException(_ exception: LoginException) {
+ switch exception {
+ case is LoginException.UserNotFound:
+ email = ""
+ password = ""
+ default:
+ break
+ }
+ }
+}
+
+extension String: Error {
+ var localizedDescription: String {
+ self
+ }
+}
diff --git a/iosApp/Modules/Screens/Login/UI/ViewModels/LandingViewModel.swift b/iosApp/Modules/Screens/Login/UI/ViewModels/LandingViewModel.swift
index 9bcaa47d..3053ea00 100644
--- a/iosApp/Modules/Screens/Login/UI/ViewModels/LandingViewModel.swift
+++ b/iosApp/Modules/Screens/Login/UI/ViewModels/LandingViewModel.swift
@@ -5,6 +5,7 @@ import GoogleSignIn
import FacebookLogin
import AuthenticationServices
import ModuleLinker
+import shared
@MainActor
class LandingViewModel: ObservableObject {
@@ -19,9 +20,10 @@ class LandingViewModel: ObservableObject {
@Published var confirmPassword: String = ""
@Published var nickname: String = ""
- private let logInRepo = LoginRepo.shared
- @Injected private var userRepo: any UserManaging
- private let loginFieldValidator = LoginFieldValidator()
+ @Injected private var logInUseCase: any LoginUseCase
+ @Injected private var signUpUseCase: any SignupUseCase
+// @Injected private var userRepo: any UserManaging
+ private let loginFieldValidator = shared.LoginFieldValidator()
var nicknameIsValid: Bool {
nickname.count > 0
@@ -48,9 +50,9 @@ class LandingViewModel: ObservableObject {
isLoading = true
Task {
do {
- try await logInRepo.login(email: email, password: password)
+ try await logInUseCase.logIn(email: email, password: password)
} catch {
- self.error = error.localizedDescription
+ handleError(error)
}
isLoading = false
}
@@ -64,22 +66,22 @@ class LandingViewModel: ObservableObject {
navPath.append(.enterNewPassword)
Task {
do {
- try await logInRepo.requestEmailVerificationCode(for: email)
+ try await signUpUseCase.requestEmailConfirmationCode(email: email)
} catch {
- self.error = error.localizedDescription
+ handleError(error)
}
}
}
func resetPassword() {
- Task {
- do {
- try await userRepo.resetPassword(email: email, password: password, confirmPassword: confirmPassword, authCode: confirmationCode)
- try await logInRepo.login(email: email, password: password)
- } catch {
- self.error = error.localizedDescription
- }
- }
+// Task {
+// do {
+// try await userRepo.resetPassword(email: email, password: password, confirmPassword: confirmPassword, authCode: confirmationCode)
+// try await logInUseCase.logIn(email: email, password: password)
+// } catch {
+// handleError(error)
+// }
+// }
}
func createAccount() {
@@ -92,9 +94,9 @@ class LandingViewModel: ObservableObject {
}
Task {
do {
- try await logInRepo.requestEmailVerificationCode(for: email)
+ try await signUpUseCase.requestEmailConfirmationCode(email: email)
} catch {
- self.error = error.localizedDescription
+ handleError(error)
}
}
}
@@ -107,23 +109,10 @@ class LandingViewModel: ObservableObject {
isLoading = true
Task {
do {
- try await userRepo.createUser(nickname: nickname, email: email, password: password, passwordConfirmation: confirmPassword, verificationCode: confirmationCode)
+ try await signUpUseCase.registerUser(nickname: nickname, email: email, password: password, passwordConfirmation: confirmPassword, verificationCode: confirmationCode)
navPath.append(.done)
- } catch let error as UserRepoError {
- switch error {
- case .accountExists:
- self.error = "An account with this email already exists."
- navigateBackTo(.createAccount)
- case .twoFAFailed:
- self.error = "You entered the incorrect verification code."
- navigateBackTo(.codeConfirmation)
- case .dataUnavailable:
- self.error = "Failed to fetch data."
- case .displayError(let errorString):
- self.error = errorString
- }
} catch {
- self.error = error.localizedDescription
+ handleError(error)
}
isLoading = false
@@ -141,36 +130,45 @@ class LandingViewModel: ObservableObject {
func handleFacebookLogin(result: Result) {
switch result {
case .success(let loginResult):
+ guard let token = loginResult.token?.tokenString else {
+ //TODO: localize
+ self.error = "Failed to log in with Facebook"
+ return
+ }
isLoading = true
Task {
do {
- try await logInRepo.loginWithFacebook(result: loginResult)
+ try await logInUseCase.logInWithFacebook(accessToken: token)
} catch {
self.error = error.localizedDescription
}
isLoading = false
}
case .failure(let error):
- self.error = error.localizedDescription
+ handleError(error)
}
}
func handleFacebookLogout() {
- logInRepo.logOut()
+ logInUseCase.logOut()
}
func handleGoogleSignIn(result: GIDSignInResult?, error: Error?) {
- guard let result = result, error == nil else {
- self.error = error?.localizedDescription
- return
+ guard let idToken = result?.user.accessToken.tokenString else {
+ //TODO: localize
+ handleError("Failed to sign in with Google"); return
+ }
+
+ guard error == nil else {
+ handleError(error!); return
}
isLoading = true
Task {
do {
- try await logInRepo.loginWithGoogle(result: result)
+ try await logInUseCase.logInWithGoogle(idToken: idToken)
} catch {
- self.error = error.localizedDescription
+ handleError(error)
}
isLoading = false
}
@@ -179,17 +177,24 @@ class LandingViewModel: ObservableObject {
func handleAppleSignIn(result: Result) {
switch result {
case .success(let authResults):
+ guard
+ let identityToken = (authResults.credential as? ASAuthorizationAppleIDCredential)?.identityToken,
+ let token = String(data: identityToken, encoding: .utf8)
+ else {
+ self.error = "Could not sign in with Apple"
+ return
+ }
isLoading = true
Task {
do {
- try await logInRepo.loginWithApple(result: authResults)
+ try await logInUseCase.logInWithApple(idToken: token)
} catch {
- self.error = "\(error)"
+ handleError(error)
}
isLoading = false
}
case .failure(let error):
- self.error = "\(error)"
+ handleError(error)
}
}
}
diff --git a/iosApp/Modules/Screens/Login/UI/Views/LandingView.swift b/iosApp/Modules/Screens/Login/UI/Views/LandingView.swift
index fb99ae77..13654de4 100644
--- a/iosApp/Modules/Screens/Login/UI/Views/LandingView.swift
+++ b/iosApp/Modules/Screens/Login/UI/Views/LandingView.swift
@@ -58,7 +58,7 @@ extension LandingView {
Group {
loginButton
createAccountButton
- facebookLoginButton
+// facebookLoginButton
googleSignInButton
signInWithAppleButton
}
@@ -92,15 +92,15 @@ extension LandingView {
.borderOverlay(color: NEWMColor.grey500(), radius: 4, width: 2)
}
- @ViewBuilder
- private var facebookLoginButton: some View {
- FacebookLoginButton(logInCompletionHandler: viewModel.handleFacebookLogin, logOutCompletionHandler: viewModel.handleFacebookLogout)
- .frame(height: socialSignInButtonHeight)
- .addSidePadding()
- .background(Color(red: 24 / 255, green: 119 / 255, blue: 242 / 255))
- .cornerRadius(4)
- .padding(.top)
- }
+// @ViewBuilder
+// private var facebookLoginButton: some View {
+// FacebookLoginButton(logInCompletionHandler: viewModel.handleFacebookLogin, logOutCompletionHandler: viewModel.handleFacebookLogout)
+// .frame(height: socialSignInButtonHeight)
+// .addSidePadding()
+// .background(Color(red: 24 / 255, green: 119 / 255, blue: 242 / 255))
+// .cornerRadius(4)
+// .padding(.top)
+// }
@ViewBuilder
private var signInWithAppleButton: some View {
diff --git a/iosApp/Modules/Screens/Main/UI/MainView.swift b/iosApp/Modules/Screens/Main/UI/MainView.swift
index 7514f023..08ade1d8 100644
--- a/iosApp/Modules/Screens/Main/UI/MainView.swift
+++ b/iosApp/Modules/Screens/Main/UI/MainView.swift
@@ -16,8 +16,6 @@ public struct MainView: View {
@State var route: MainViewRoute?
- public init() {}
-
public var body: some View {
GeometryReader { geometry in
if viewModel.shouldShowLogin {
diff --git a/iosApp/Modules/Screens/Main/UI/MainViewModel.swift b/iosApp/Modules/Screens/Main/UI/MainViewModel.swift
index 3739616a..b76e22ba 100644
--- a/iosApp/Modules/Screens/Main/UI/MainViewModel.swift
+++ b/iosApp/Modules/Screens/Main/UI/MainViewModel.swift
@@ -4,18 +4,27 @@ import ModuleLinker
import Resolver
import Data
import API
+import shared
@MainActor
class MainViewModel: ObservableObject {
@Published var selectedTab: MainViewModelTab = .home
+ //This value isn't used, it's just for triggering a view refresh.
+ @Published var updateLoginState: Bool = false
+ @Injected private var loginUseCase: LoginUseCase
- @Published var shouldShowLogin: Bool = false
-
- private let loginRepo = LoginRepo.shared
-
- private var cancelables = Set()
+ private var cancels = Set()
+
+ var shouldShowLogin: Bool {
+ loginUseCase.userIsLoggedIn == false
+ }
- init() {
- loginRepo.$userIsLoggedIn.map { !$0 }.assign(to: &$shouldShowLogin)
+ public init() {
+ NotificationCenter.default.publisher(for: shared.Notification().loginStateChanged)
+ .receive(on: RunLoop.main)
+ .sink { [weak self] _ in
+ self?.updateLoginState.toggle()
+ }
+ .store(in: &cancels)
}
}
diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj
index 2154b3a7..3cb86565 100644
--- a/iosApp/iosApp.xcodeproj/project.pbxproj
+++ b/iosApp/iosApp.xcodeproj/project.pbxproj
@@ -62,8 +62,6 @@
7618FD6528164D99007573B4 /* Artist.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7618FD5E28164D99007573B4 /* Artist.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
7618FD7728164DA2007573B4 /* Login.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7618FD7128164DA1007573B4 /* Login.framework */; };
7618FD7828164DA2007573B4 /* Login.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7618FD7128164DA1007573B4 /* Login.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- 7618FD8728164DAB007573B4 /* PlaylistList.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7618FD8128164DAB007573B4 /* PlaylistList.framework */; };
- 7618FD8828164DAB007573B4 /* PlaylistList.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7618FD8128164DAB007573B4 /* PlaylistList.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
7618FD9728164DBB007573B4 /* NowPlaying.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7618FD9128164DBB007573B4 /* NowPlaying.framework */; };
7618FD9828164DBB007573B4 /* NowPlaying.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7618FD9128164DBB007573B4 /* NowPlaying.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
7618FDA728164DC6007573B4 /* Tips.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7618FDA128164DC6007573B4 /* Tips.framework */; };
@@ -84,6 +82,10 @@
7618FE0B28164E72007573B4 /* ModuleLinker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76D01D9E28164B2100305605 /* ModuleLinker.framework */; platformFilter = ios; };
7618FE1A28164E7F007573B4 /* ModuleLinker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76D01D9E28164B2100305605 /* ModuleLinker.framework */; platformFilter = ios; };
7618FE1F28164E83007573B4 /* ModuleLinker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76D01D9E28164B2100305605 /* ModuleLinker.framework */; platformFilter = ios; };
+ 761ADD3B2AE734AA001B56CF /* shared.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 761ADD3A2AE734AA001B56CF /* shared.xcframework */; };
+ 761ADD422AE88A56001B56CF /* Notification+KMM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761ADD412AE88A56001B56CF /* Notification+KMM.swift */; };
+ 761ADD442AEA0746001B56CF /* Error+KMMException.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761ADD432AEA0746001B56CF /* Error+KMMException.swift */; };
+ 761ADD472AEA0ECF001B56CF /* LandingViewModel+ErrorHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761ADD452AEA0EAA001B56CF /* LandingViewModel+ErrorHandling.swift */; };
7625CE2F294088110033C669 /* ArtistCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7625CE2E294088110033C669 /* ArtistCell.swift */; };
7625CE31294089990033C669 /* AudioPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76C76132291F445B0054D2F3 /* AudioPlayer.framework */; };
7625CE3529408A3B0033C669 /* Colors.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7618FD1728164CBD007573B4 /* Colors.framework */; };
@@ -218,7 +220,6 @@
76A24DE72A3FE76900559460 /* APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76A24DE62A3FE76900559460 /* APIError.swift */; };
76A24DE92A3FEDD300559460 /* DataModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76A24DE82A3FEDD300559460 /* DataModule.swift */; };
76A24DED2A3FF07E00559460 /* Resolver in Frameworks */ = {isa = PBXBuildFile; productRef = 76A24DEC2A3FF07E00559460 /* Resolver */; };
- 76A24DEE2A3FF22300559460 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76A24DEA2A3FEF7E00559460 /* Data.swift */; };
76A24DEF2A3FF26600559460 /* ModuleLinker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76D01D9E28164B2100305605 /* ModuleLinker.framework */; };
76A24DF02A3FF26600559460 /* ModuleLinker.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 76D01D9E28164B2100305605 /* ModuleLinker.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
76A24DF52A4026A000559460 /* MockUserManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76A24DF42A4026A000559460 /* MockUserManager.swift */; };
@@ -243,12 +244,8 @@
76B21D13293F4B17003692F7 /* HorizontalScrollingGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76B21D12293F4B17003692F7 /* HorizontalScrollingGridView.swift */; };
76B43C6627D875E900F21076 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; };
76B43C6727D875E900F21076 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; };
- 76B52D4829F8688200EB6357 /* Data.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76B52D4229F8688200EB6357 /* Data.framework */; };
- 76B52D4929F8688200EB6357 /* Data.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 76B52D4229F8688200EB6357 /* Data.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
76B52D5629F86B7200EB6357 /* Data.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76B52D4229F8688200EB6357 /* Data.framework */; };
76B52D5E29F86C0400EB6357 /* LoginRepo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76B52D5D29F86C0400EB6357 /* LoginRepo.swift */; };
- 76B52D6A29F86D4100EB6357 /* API.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76B52D6429F86D4000EB6357 /* API.framework */; };
- 76B52D6B29F86D4100EB6357 /* API.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 76B52D6429F86D4000EB6357 /* API.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
76B52D7229F86F0800EB6357 /* API.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76B52D6429F86D4000EB6357 /* API.framework */; };
76B52D7929F8793600EB6357 /* Data.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76B52D4229F8688200EB6357 /* Data.framework */; };
76B52D8229FE4F0F00EB6357 /* FacebookLogin in Frameworks */ = {isa = PBXBuildFile; productRef = 76B52D8129FE4F0F00EB6357 /* FacebookLogin */; };
@@ -426,13 +423,6 @@
remoteGlobalIDString = 7618FD7028164DA1007573B4;
remoteInfo = Login;
};
- 7618FD8528164DAB007573B4 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 7555FF73242A565900829871 /* Project object */;
- proxyType = 1;
- remoteGlobalIDString = 7618FD8028164DAB007573B4;
- remoteInfo = PlaylistList;
- };
7618FD9528164DBB007573B4 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 7555FF73242A565900829871 /* Project object */;
@@ -713,13 +703,6 @@
remoteGlobalIDString = 7555FF7A242A565900829871;
remoteInfo = iosApp;
};
- 76B52D4629F8688200EB6357 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 7555FF73242A565900829871 /* Project object */;
- proxyType = 1;
- remoteGlobalIDString = 76B52D4129F8688200EB6357;
- remoteInfo = Auth;
- };
76B52D5829F86B7200EB6357 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 7555FF73242A565900829871 /* Project object */;
@@ -727,13 +710,6 @@
remoteGlobalIDString = 76B52D4129F8688200EB6357;
remoteInfo = Auth;
};
- 76B52D6829F86D4100EB6357 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 7555FF73242A565900829871 /* Project object */;
- proxyType = 1;
- remoteGlobalIDString = 76B52D6329F86D4000EB6357;
- remoteInfo = API;
- };
76B52D7429F86F0800EB6357 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 7555FF73242A565900829871 /* Project object */;
@@ -818,13 +794,6 @@
remoteGlobalIDString = 7555FF7A242A565900829871;
remoteInfo = iosApp;
};
- 76CB88E828A275F800878D8C /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 7555FF73242A565900829871 /* Project object */;
- proxyType = 1;
- remoteGlobalIDString = 76CB88C328A273DB00878D8C;
- remoteInfo = SharedExtensions;
- };
76D01D4328164A8400305605 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 7555FF73242A565900829871 /* Project object */;
@@ -905,7 +874,6 @@
dstSubfolderSpec = 10;
files = (
7618FD6528164D99007573B4 /* Artist.framework in Embed Frameworks */,
- 76B52D6B29F86D4100EB6357 /* API.framework in Embed Frameworks */,
7618FCD428164C5C007573B4 /* SharedUI.framework in Embed Frameworks */,
76C76139291F445B0054D2F3 /* AudioPlayer.framework in Embed Frameworks */,
7618FD9828164DBB007573B4 /* NowPlaying.framework in Embed Frameworks */,
@@ -917,8 +885,6 @@
7618FCF528164C86007573B4 /* TabBar.framework in Embed Frameworks */,
76D01DA528164B2100305605 /* ModuleLinker.framework in Embed Frameworks */,
76D01D4628164A8400305605 /* Home.framework in Embed Frameworks */,
- 7618FD8828164DAB007573B4 /* PlaylistList.framework in Embed Frameworks */,
- 76B52D4929F8688200EB6357 /* Data.framework in Embed Frameworks */,
76D01D8028164AE700305605 /* Main.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
@@ -1014,6 +980,10 @@
7618FDDB28164E23007573B4 /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = ""; };
7618FDDD28164E32007573B4 /* WalletModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletModule.swift; sourceTree = ""; };
7618FDDE28164E32007573B4 /* WalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletView.swift; sourceTree = ""; };
+ 761ADD3A2AE734AA001B56CF /* shared.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = shared.xcframework; path = ../shared/build/XCFrameworks/debug/shared.xcframework; sourceTree = ""; };
+ 761ADD412AE88A56001B56CF /* Notification+KMM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+KMM.swift"; sourceTree = ""; };
+ 761ADD432AEA0746001B56CF /* Error+KMMException.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Error+KMMException.swift"; sourceTree = ""; };
+ 761ADD452AEA0EAA001B56CF /* LandingViewModel+ErrorHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LandingViewModel+ErrorHandling.swift"; sourceTree = ""; };
7625CE2E294088110033C669 /* ArtistCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArtistCell.swift; sourceTree = ""; };
7625D14D29429CCC0033C669 /* Filters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filters.swift; sourceTree = ""; };
7625D1512942B0740033C669 /* LibraryRoutes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibraryRoutes.swift; sourceTree = ""; };
@@ -1219,17 +1189,15 @@
76D01D4528164A8400305605 /* Home.framework in Frameworks */,
76D01DA428164B2100305605 /* ModuleLinker.framework in Frameworks */,
76C76138291F445B0054D2F3 /* AudioPlayer.framework in Frameworks */,
- 76B52D6A29F86D4100EB6357 /* API.framework in Frameworks */,
7618FD7728164DA2007573B4 /* Login.framework in Frameworks */,
7618FD3728164CE2007573B4 /* Fonts.framework in Frameworks */,
7618FCF428164C86007573B4 /* TabBar.framework in Frameworks */,
76562530284BF6E7004B6E4B /* Library.framework in Frameworks */,
76D01D7F28164AE700305605 /* Main.framework in Frameworks */,
+ 761ADD3B2AE734AA001B56CF /* shared.xcframework in Frameworks */,
7618FCD328164C5C007573B4 /* SharedUI.framework in Frameworks */,
7618FD1D28164CBD007573B4 /* Colors.framework in Frameworks */,
- 76B52D4829F8688200EB6357 /* Data.framework in Frameworks */,
76F823C528BEC2B3003A524C /* Resolver in Frameworks */,
- 7618FD8728164DAB007573B4 /* PlaylistList.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1490,6 +1458,7 @@
7555FF72242A565900829871 = {
isa = PBXGroup;
children = (
+ 761ADD3A2AE734AA001B56CF /* shared.xcframework */,
76EAFC5C2A0AE33E00ACD862 /* Secrets.xcconfig */,
76B990BB2835203300F818E4 /* Fonts-Info.plist */,
7627329028295E8C009D3C8A /* Documentation */,
@@ -1564,9 +1533,11 @@
760C2C7128165D920030ACF4 /* Extensions */ = {
isa = PBXGroup;
children = (
- 7618FCDC28164C6B007573B4 /* UIImage+Extensions.swift */,
+ 761ADD432AEA0746001B56CF /* Error+KMMException.swift */,
+ 761ADD412AE88A56001B56CF /* Notification+KMM.swift */,
76DB3B972A13252F001AF2AD /* String+NilIfEmpty.swift */,
760C2C7228165DE80030ACF4 /* SwiftUIView+Erased.swift */,
+ 7618FCDC28164C6B007573B4 /* UIImage+Extensions.swift */,
);
path = Extensions;
sourceTree = "";
@@ -1893,12 +1864,12 @@
765A35E829E9052E00B939BD /* CodeConfirmationView.swift */,
765A35E429E9052E00B939BD /* CreateAccountView.swift */,
765A35EB29E9052E00B939BD /* DoneView.swift */,
+ 76EAFC512A08F09600ACD862 /* EnterNewPasswordView.swift */,
+ 76FB0E6429F6695000C23F18 /* FacebookLoginButton.swift */,
+ 76EAFC4F2A08ED0900ACD862 /* ForgotPasswordView.swift */,
765A35DE29E9052E00B939BD /* LandingView.swift */,
765A35F529E9052E00B939BD /* LoginView.swift */,
765A35F129E9052E00B939BD /* UsernameView.swift */,
- 76FB0E6429F6695000C23F18 /* FacebookLoginButton.swift */,
- 76EAFC4F2A08ED0900ACD862 /* ForgotPasswordView.swift */,
- 76EAFC512A08F09600ACD862 /* EnterNewPasswordView.swift */,
);
path = Views;
sourceTree = "";
@@ -2288,8 +2259,8 @@
76CE046529EE3F13002AF94B /* Utilities */ = {
isa = PBXGroup;
children = (
- 766B1EC62A15C74B0083DE3E /* KeyboardObserver.swift */,
76CE046629EE3F30002AF94B /* Binding.swift */,
+ 766B1EC62A15C74B0083DE3E /* KeyboardObserver.swift */,
);
path = Utilities;
sourceTree = "";
@@ -2297,6 +2268,7 @@
76CE046A29EE6596002AF94B /* ViewModels */ = {
isa = PBXGroup;
children = (
+ 761ADD452AEA0EAA001B56CF /* LandingViewModel+ErrorHandling.swift */,
765A35DF29E9052E00B939BD /* LandingViewModel.swift */,
);
path = ViewModels;
@@ -2581,14 +2553,10 @@
7618FD1C28164CBD007573B4 /* PBXTargetDependency */,
7618FD3628164CE2007573B4 /* PBXTargetDependency */,
7618FD7628164DA2007573B4 /* PBXTargetDependency */,
- 7618FD8628164DAB007573B4 /* PBXTargetDependency */,
7618FD9628164DBB007573B4 /* PBXTargetDependency */,
7618FDA628164DC6007573B4 /* PBXTargetDependency */,
76562519284BEF30004B6E4B /* PBXTargetDependency */,
- 76CB88E928A275F800878D8C /* PBXTargetDependency */,
76C76137291F445B0054D2F3 /* PBXTargetDependency */,
- 76B52D4729F8688200EB6357 /* PBXTargetDependency */,
- 76B52D6929F86D4100EB6357 /* PBXTargetDependency */,
);
name = iosApp;
packageProductDependencies = (
@@ -3115,6 +3083,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 76D01DA628164B2100305605 /* Build configuration list for PBXNativeTarget "ModuleLinker" */;
buildPhases = (
+ 761ADD102AE389F0001B56CF /* ShellScript */,
76D01D9A28164B2100305605 /* Sources */,
76D01D9928164B2100305605 /* Headers */,
76D01D9B28164B2100305605 /* Frameworks */,
@@ -3508,6 +3477,23 @@
shellPath = /bin/sh;
shellScript = "#swiftgen\ncd Modules/Resources/Colors\nswiftgen\n";
};
+ 761ADD102AE389F0001B56CF /* ShellScript */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ );
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/bash;
+ shellScript = "#cd \"$SRCROOT/..\"\n#./gradlew :shared:assembleXCFramework\n";
+ };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -3628,6 +3614,7 @@
76FB0E6529F6695000C23F18 /* FacebookLoginButton.swift in Sources */,
765A360529E9052E00B939BD /* LoginMocks.swift in Sources */,
765A361729E9052E00B939BD /* LoginView.swift in Sources */,
+ 761ADD472AEA0ECF001B56CF /* LandingViewModel+ErrorHandling.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -3833,8 +3820,10 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 761ADD422AE88A56001B56CF /* Notification+KMM.swift in Sources */,
76562535284BF758004B6E4B /* Library.swift in Sources */,
76764AF3281D015B0095BEF9 /* Playlist.swift in Sources */,
+ 761ADD442AEA0746001B56CF /* Error+KMMException.swift in Sources */,
76D9F1F92A1714E300C93E1C /* KeyboardObserver.swift in Sources */,
760C2C7D281660F70030ACF4 /* Tips.swift in Sources */,
76DC3A4D281A0F1D004D5021 /* ModuleLinkerModule.swift in Sources */,
@@ -3846,7 +3835,6 @@
7618FCC228164BB5007573B4 /* ModuleProtocol.swift in Sources */,
76CE046729EE3F30002AF94B /* Binding.swift in Sources */,
76C76147291F4D8D0054D2F3 /* AudioPlayer.swift in Sources */,
- 76A24DEE2A3FF22300559460 /* Data.swift in Sources */,
760C2C7528165F450030ACF4 /* NowPlaying.swift in Sources */,
76DC3A1928167276004D5021 /* UIImage+Extensions.swift in Sources */,
761837AB294A706F0033250B /* Artist.swift in Sources */,
@@ -3927,11 +3915,6 @@
target = 7618FD7028164DA1007573B4 /* Login */;
targetProxy = 7618FD7528164DA2007573B4 /* PBXContainerItemProxy */;
};
- 7618FD8628164DAB007573B4 /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- target = 7618FD8028164DAB007573B4 /* PlaylistList */;
- targetProxy = 7618FD8528164DAB007573B4 /* PBXContainerItemProxy */;
- };
7618FD9628164DBB007573B4 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 7618FD9028164DBB007573B4 /* NowPlaying */;
@@ -4159,21 +4142,11 @@
target = 7555FF7A242A565900829871 /* iosApp */;
targetProxy = 76ADDCF328981B03008AA39A /* PBXContainerItemProxy */;
};
- 76B52D4729F8688200EB6357 /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- target = 76B52D4129F8688200EB6357 /* Data */;
- targetProxy = 76B52D4629F8688200EB6357 /* PBXContainerItemProxy */;
- };
76B52D5929F86B7200EB6357 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 76B52D4129F8688200EB6357 /* Data */;
targetProxy = 76B52D5829F86B7200EB6357 /* PBXContainerItemProxy */;
};
- 76B52D6929F86D4100EB6357 /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- target = 76B52D6329F86D4000EB6357 /* API */;
- targetProxy = 76B52D6829F86D4100EB6357 /* PBXContainerItemProxy */;
- };
76B52D7529F86F0800EB6357 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 76B52D6329F86D4000EB6357 /* API */;
@@ -4236,11 +4209,6 @@
target = 7555FF7A242A565900829871 /* iosApp */;
targetProxy = 76CB88D128A273DB00878D8C /* PBXContainerItemProxy */;
};
- 76CB88E928A275F800878D8C /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- platformFilter = ios;
- targetProxy = 76CB88E828A275F800878D8C /* PBXContainerItemProxy */;
- };
76D01D4428164A8400305605 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 76D01D3E28164A8400305605 /* Home */;
@@ -4464,6 +4432,7 @@
"\"$(SRCROOT)/Modules\"/**",
);
INFOPLIST_FILE = iosApp/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = NEWM;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -4498,6 +4467,7 @@
"\"$(SRCROOT)/Modules\"/**",
);
INFOPLIST_FILE = iosApp/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = NEWM;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
diff --git a/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index f6c17459..63031fcf 100644
--- a/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -14,8 +14,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/facebook/facebook-ios-sdk",
"state" : {
- "revision" : "2ef0af2c7d8b6d6c3a14edf5299b78730555c679",
- "version" : "16.1.0"
+ "revision" : "ebfaa5eb867c9ad3e0b54ef8b03883f024cfc18a",
+ "version" : "16.2.1"
}
},
{
diff --git a/iosApp/iosApp/App/iOSApp.swift b/iosApp/iosApp/App/iOSApp.swift
index f782f7cf..4776cbea 100644
--- a/iosApp/iosApp/App/iOSApp.swift
+++ b/iosApp/iosApp/App/iOSApp.swift
@@ -4,12 +4,14 @@ import ModuleLinker
import FacebookCore
import Data
import UIKit
+import shared
@main
struct iOSApp: App {
let mainViewProvider: MainViewProviding
init() {
+ KoinKt.doInitKoin()
#if DEBUG
UserDefaults.standard.set(false, forKey: "_UIConstraintBasedLayoutLogUnsatisfiable")
#endif
diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts
index 201b5646..a8eed08a 100644
--- a/shared/build.gradle.kts
+++ b/shared/build.gradle.kts
@@ -33,8 +33,8 @@ kotlin {
val xcf = XCFramework()
listOf(
- iosX64(),
- iosArm64(),
+// iosX64(),
+// iosArm64(),
iosSimulatorArm64()
).forEach {
it.binaries.framework {
@@ -56,6 +56,7 @@ kotlin {
implementation(Ktor.clientContentNegotiation)
implementation(Ktor.kotlinXJson)
implementation(Ktor.clientAuth)
+ implementation("com.liftric:kvault:1.12.0")
}
}
val commonTest by getting {
@@ -80,26 +81,26 @@ kotlin {
}
}
- val iosX64Main by getting
- val iosArm64Main by getting
+// val iosX64Main by getting
+// val iosArm64Main by getting
val iosSimulatorArm64Main by getting
val iosMain by creating {
dependsOn(commonMain)
- iosX64Main.dependsOn(this)
- iosArm64Main.dependsOn(this)
+// iosX64Main.dependsOn(this)
+// iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
dependencies {
implementation(Ktor.iosDarwin)
implementation(SqlDelight.nativeDriver)
}
}
- val iosX64Test by getting
- val iosArm64Test by getting
+// val iosX64Test by getting
+// val iosArm64Test by getting
val iosSimulatorArm64Test by getting
val iosTest by creating {
dependsOn(commonTest)
- iosX64Test.dependsOn(this)
- iosArm64Test.dependsOn(this)
+// iosX64Test.dependsOn(this)
+// iosArm64Test.dependsOn(this)
iosSimulatorArm64Test.dependsOn(this)
}
}
diff --git a/shared/src/androidMain/kotlin/shared/NotificationCenter.kt b/shared/src/androidMain/kotlin/shared/NotificationCenter.kt
new file mode 100644
index 00000000..4682616b
--- /dev/null
+++ b/shared/src/androidMain/kotlin/shared/NotificationCenter.kt
@@ -0,0 +1,6 @@
+package shared
+
+actual fun postNotification(name: String) {
+ //TODO
+ println("postNotification: $name")
+}
diff --git a/shared/src/androidMain/kotlin/shared/SecureStorage.kt b/shared/src/androidMain/kotlin/shared/SecureStorage.kt
deleted file mode 100644
index 37a1d2ac..00000000
--- a/shared/src/androidMain/kotlin/shared/SecureStorage.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-package shared
-
-import android.content.Context
-import android.content.SharedPreferences
-import androidx.security.crypto.EncryptedSharedPreferences
-import androidx.security.crypto.MasterKey
-
-actual class SecureStorage(private val context: Context) {
- private val masterKey = MasterKey.Builder(context)
- .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
- .build()
-
- private val sharedPreferences: SharedPreferences = EncryptedSharedPreferences.create(
- context,
- "newm_encrypted_shared_prefs",
- masterKey,
- EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
- EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
- )
-
- actual fun store(key: String, value: String) {
- with(sharedPreferences.edit()) {
- putString(key, value)
- apply()
- }
- }
-
- actual fun retrieve(key: String): String? {
- return sharedPreferences.getString(key, null)
- }
-
- actual fun remove(key: String) {
- with(sharedPreferences.edit()) {
- remove(key)
- apply()
- }
- }
-}
\ No newline at end of file
diff --git a/shared/src/androidMain/kotlin/shared/actual.kt b/shared/src/androidMain/kotlin/shared/actual.kt
index 8e6d9d9a..c876c15e 100644
--- a/shared/src/androidMain/kotlin/shared/actual.kt
+++ b/shared/src/androidMain/kotlin/shared/actual.kt
@@ -1,9 +1,10 @@
package shared
+import com.liftric.kvault.KVault
import com.squareup.sqldelight.android.AndroidSqliteDriver
-import io.ktor.client.engine.android.*
-import io.newm.shared.db.cache.NewmDatabase
+import io.ktor.client.engine.android.Android
import io.newm.shared.db.NewmDatabaseWrapper
+import io.newm.shared.db.cache.NewmDatabase
import org.koin.dsl.module
actual fun platformModule() = module {
@@ -12,5 +13,5 @@ actual fun platformModule() = module {
NewmDatabaseWrapper(NewmDatabase(driver))
}
single { Android.create() }
- single { SecureStorage(get()) }
+ single { KVault(get(), "user-account") }
}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/io.newm.shared/TokenManagerImpl.kt b/shared/src/commonMain/kotlin/io.newm.shared/TokenManagerImpl.kt
index 9d5869e0..4ac5204c 100644
--- a/shared/src/commonMain/kotlin/io.newm.shared/TokenManagerImpl.kt
+++ b/shared/src/commonMain/kotlin/io.newm.shared/TokenManagerImpl.kt
@@ -1,12 +1,12 @@
package io.newm.shared
import co.touchlab.kermit.Logger
+import com.liftric.kvault.KVault
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
-import shared.SecureStorage
internal class TokenManagerImpl() : KoinComponent, TokenManager {
- private val storage: SecureStorage by inject()
+ private val storage: KVault by inject()
private val logger = Logger.withTag("NewmKMM-TokenManagerImpl")
override fun hasTokens(): Boolean {
@@ -14,27 +14,27 @@ internal class TokenManagerImpl() : KoinComponent, TokenManager {
}
override fun getAccessToken(): String? {
- return storage.retrieve(ACCESS_TOKEN_KEY) ?: run {
+ return storage.string(ACCESS_TOKEN_KEY) ?: run {
logger.d("No Access Token found - Time to Login")
null
}
}
override fun getRefreshToken(): String? {
- return storage.retrieve(REFRESH_TOKEN_KEY) ?: run {
+ return storage.string(REFRESH_TOKEN_KEY) ?: run {
logger.d("No Refresh Token found - Time to Login")
null
}
}
override fun clearToken() {
- storage.remove(ACCESS_TOKEN_KEY)
- storage.remove(REFRESH_TOKEN_KEY)
+ storage.deleteObject(ACCESS_TOKEN_KEY)
+ storage.deleteObject(REFRESH_TOKEN_KEY)
}
override fun setAuthTokens(accessToken: String, refreshToken: String) {
- storage.store(ACCESS_TOKEN_KEY, accessToken)
- storage.store(REFRESH_TOKEN_KEY, refreshToken)
+ storage.set(ACCESS_TOKEN_KEY, accessToken)
+ storage.set(REFRESH_TOKEN_KEY, refreshToken)
}
companion object {
diff --git a/shared/src/commonMain/kotlin/io.newm.shared/di/Koin.kt b/shared/src/commonMain/kotlin/io.newm.shared/di/Koin.kt
index 2adeb5b5..99713689 100644
--- a/shared/src/commonMain/kotlin/io.newm.shared/di/Koin.kt
+++ b/shared/src/commonMain/kotlin/io.newm.shared/di/Koin.kt
@@ -1,6 +1,6 @@
package io.newm.shared.di
-import io.ktor.client.engine.*
+import io.ktor.client.engine.HttpClientEngine
import io.newm.shared.TokenManager
import io.newm.shared.TokenManagerImpl
import io.newm.shared.login.UserSession
@@ -8,12 +8,11 @@ import io.newm.shared.login.UserSessionImpl
import io.newm.shared.login.repository.LogInRepository
import io.newm.shared.login.repository.LogInRepositoryImpl
import io.newm.shared.login.service.LoginAPI
-import io.newm.shared.usecases.LoginUseCase
-import io.newm.shared.usecases.LoginUseCaseImpl
-import io.newm.shared.usecases.SignupUseCase
-import io.newm.shared.usecases.SignupUseCaseImpl
-import io.newm.shared.repositories.*
+import io.newm.shared.repositories.CardanoWalletRepository
+import io.newm.shared.repositories.CardanoWalletRepositoryImpl
+import io.newm.shared.repositories.GenresRepository
import io.newm.shared.repositories.GenresRepositoryImpl
+import io.newm.shared.repositories.PlaylistRepository
import io.newm.shared.repositories.PlaylistRepositoryImpl
import io.newm.shared.repositories.UserRepository
import io.newm.shared.repositories.UserRepositoryImpl
@@ -23,6 +22,10 @@ import io.newm.shared.services.UserAPI
import io.newm.shared.services.CardanoWalletAPI
import io.newm.shared.usecases.GetGenresUseCase
import io.newm.shared.usecases.GetGenresUseCaseImpl
+import io.newm.shared.usecases.LoginUseCase
+import io.newm.shared.usecases.LoginUseCaseImpl
+import io.newm.shared.usecases.SignupUseCase
+import io.newm.shared.usecases.SignupUseCaseImpl
import io.newm.shared.usecases.WalletNFTSongsUseCase
import io.newm.shared.usecases.WalletNFTSongsUseCaseImpl
import kotlinx.coroutines.CoroutineScope
@@ -50,7 +53,6 @@ fun commonModule(enableNetworkLogs: Boolean) = module {
single { CoroutineScope(Dispatchers.Default + SupervisorJob()) }
// Internal API Services
single { LoginAPI(get()) }
- single { LoginAPI(get()) }
single { GenresAPI(get()) }
single { UserAPI(get()) }
single { PlaylistAPI(get()) }
diff --git a/shared/src/commonMain/kotlin/io.newm.shared/login/models/LoginResponse.kt b/shared/src/commonMain/kotlin/io.newm.shared/login/models/LoginResponse.kt
index 8abc8f23..ec09757c 100644
--- a/shared/src/commonMain/kotlin/io.newm.shared/login/models/LoginResponse.kt
+++ b/shared/src/commonMain/kotlin/io.newm.shared/login/models/LoginResponse.kt
@@ -11,7 +11,6 @@ data class LoginResponse(
sealed class LoginException(message: String): KMMException(message) {
data class WrongPassword(override val message: String) : LoginException(message)
-
data class UserNotFound(override val message: String) : LoginException(message)
}
@@ -21,6 +20,5 @@ fun LoginResponse.isValid(): Boolean {
sealed class RegisterException(message: String): KMMException(message) {
data class UserAlreadyExists(override val message: String) : RegisterException(message)
-
data class TwoFactorAuthenticationFailed(override val message: String) : RegisterException(message)
}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/io.newm.shared/login/models/Users.kt b/shared/src/commonMain/kotlin/io.newm.shared/login/models/Users.kt
index 8f98b120..85b13320 100644
--- a/shared/src/commonMain/kotlin/io.newm.shared/login/models/Users.kt
+++ b/shared/src/commonMain/kotlin/io.newm.shared/login/models/Users.kt
@@ -9,7 +9,16 @@ data class LogInUser(
)
@Serializable
-data class GoogleSignInRequest(val idToken: String)
+data class GoogleSignInRequest(val accessToken: String)
+
+@Serializable
+data class AppleSignInRequest(val idToken: String)
+
+@Serializable
+data class FacebookSignInRequest(val accessToken: String)
+
+@Serializable
+data class LinkedInSignInRequest(val accessToken: String)
@Serializable
data class NewUser(
diff --git a/shared/src/commonMain/kotlin/io.newm.shared/login/repository/LogInRepository.kt b/shared/src/commonMain/kotlin/io.newm.shared/login/repository/LogInRepository.kt
index e29cf937..078bea75 100644
--- a/shared/src/commonMain/kotlin/io.newm.shared/login/repository/LogInRepository.kt
+++ b/shared/src/commonMain/kotlin/io.newm.shared/login/repository/LogInRepository.kt
@@ -7,11 +7,13 @@ import io.newm.shared.login.models.*
import io.newm.shared.login.service.LoginAPI
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
+import shared.Notification
+import shared.postNotification
import kotlin.coroutines.cancellation.CancellationException
open class KMMException(message: String) : Throwable(message)
-interface LogInRepository {
+internal interface LogInRepository {
@Throws(Exception::class)
suspend fun requestEmailConfirmationCode(email: String)
@@ -21,6 +23,10 @@ interface LogInRepository {
@Throws(KMMException::class, CancellationException::class)
suspend fun logIn(email: String, password: String)
+ fun logOut()
+
+ fun userIsLoggedIn(): Boolean
+
@Throws(KMMException::class, CancellationException::class)
suspend fun oAuthLogin(oAuthData: OAuthData)
@@ -60,19 +66,31 @@ internal class LogInRepositoryImpl : KoinComponent, LogInRepository {
return handleLoginResponse { service.logIn(LogInUser(email = email, password = password)) }
}
+ override fun logOut() {
+ tokenManager.clearToken()
+ postNotification(Notification.loginStateChanged)
+ }
+
+ override fun userIsLoggedIn(): Boolean {
+ return tokenManager.getAccessToken() != null
+ }
+
@Throws(KMMException::class, CancellationException::class)
override suspend fun oAuthLogin(oAuthData: OAuthData) = handleLoginResponse {
logger.d { "logIn: oAuth" }
when (oAuthData) {
- is OAuthData.Facebook -> TODO()
- is OAuthData.Google -> service.loginWithGoogle(GoogleSignInRequest(idToken = oAuthData.idToken))
- is OAuthData.LinkedIn -> TODO()
+ is OAuthData.Facebook -> service.loginWithFacebook(FacebookSignInRequest(accessToken = oAuthData.accessToken))
+ is OAuthData.Google -> service.loginWithGoogle(GoogleSignInRequest(accessToken = oAuthData.idToken))
+ is OAuthData.Apple -> service.loginWithApple(AppleSignInRequest(idToken = oAuthData.idToken))
+ is OAuthData.LinkedIn -> service.loginWithLinkedIn(LinkedInSignInRequest(accessToken = oAuthData.accessToken))
}
}
+ @Throws(KMMException::class, CancellationException::class)
private suspend fun handleLoginResponse(request: suspend () -> LoginResponse) {
try {
storeAccessToken(request())
+ postNotification(Notification.loginStateChanged)
} catch (e: ClientRequestException) {
when (e.response.status.value) {
404 -> {
diff --git a/shared/src/commonMain/kotlin/io.newm.shared/login/repository/OAuthData.kt b/shared/src/commonMain/kotlin/io.newm.shared/login/repository/OAuthData.kt
index eede53c0..6ba53415 100644
--- a/shared/src/commonMain/kotlin/io.newm.shared/login/repository/OAuthData.kt
+++ b/shared/src/commonMain/kotlin/io.newm.shared/login/repository/OAuthData.kt
@@ -4,4 +4,5 @@ sealed interface OAuthData {
data class Google(val idToken: String) : OAuthData
data class Facebook(val accessToken: String) : OAuthData
data class LinkedIn(val accessToken: String) : OAuthData
+ data class Apple(val idToken: String) : OAuthData
}
diff --git a/shared/src/commonMain/kotlin/io.newm.shared/login/service/LoginAPI.kt b/shared/src/commonMain/kotlin/io.newm.shared/login/service/LoginAPI.kt
index 6cb7fcb3..356417b0 100644
--- a/shared/src/commonMain/kotlin/io.newm.shared/login/service/LoginAPI.kt
+++ b/shared/src/commonMain/kotlin/io.newm.shared/login/service/LoginAPI.kt
@@ -12,7 +12,10 @@ import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.http.contentType
import io.newm.shared.di.NetworkClientFactory
+import io.newm.shared.login.models.AppleSignInRequest
+import io.newm.shared.login.models.FacebookSignInRequest
import io.newm.shared.login.models.GoogleSignInRequest
+import io.newm.shared.login.models.LinkedInSignInRequest
import io.newm.shared.login.models.LogInUser
import io.newm.shared.login.models.LoginResponse
import io.newm.shared.login.models.NewUser
@@ -85,6 +88,51 @@ internal class LoginAPI(
}
}
+ @Throws(KMMException::class, CancellationException::class)
+ suspend fun loginWithApple(request: AppleSignInRequest): LoginResponse {
+ val response = httpClient.post("/v1/auth/login/apple") {
+ contentType(ContentType.Application.Json)
+ setBody(request)
+ }
+
+ return when (response.status) {
+ HttpStatusCode.OK -> response.body()
+ else -> {
+ throw KMMException("HTTP Error ${response.status}: ${response.bodyAsText()}")
+ }
+ }
+ }
+
+ @Throws(KMMException::class, CancellationException::class)
+ suspend fun loginWithFacebook(request: FacebookSignInRequest): LoginResponse {
+ val response = httpClient.post("/v1/auth/login/facebook") {
+ contentType(ContentType.Application.Json)
+ setBody(request)
+ }
+
+ return when (response.status) {
+ HttpStatusCode.OK -> response.body()
+ else -> {
+ throw KMMException("HTTP Error ${response.status}: ${response.bodyAsText()}")
+ }
+ }
+ }
+
+ @Throws(KMMException::class, CancellationException::class)
+ suspend fun loginWithLinkedIn(request: LinkedInSignInRequest): LoginResponse {
+ val response = httpClient.post("/v1/auth/login/linkedin") {
+ contentType(ContentType.Application.Json)
+ setBody(request)
+ }
+
+ return when (response.status) {
+ HttpStatusCode.OK -> response.body()
+ else -> {
+ throw KMMException("HTTP Error ${response.status}: ${response.bodyAsText()}")
+ }
+ }
+ }
+
@Throws(KMMException::class, CancellationException::class)
suspend fun resetPassword(
email: String,
diff --git a/shared/src/commonMain/kotlin/io.newm.shared/usecases/GetCurrentUserUseCase.kt b/shared/src/commonMain/kotlin/io.newm.shared/usecases/GetCurrentUserUseCase.kt
new file mode 100644
index 00000000..fb295f92
--- /dev/null
+++ b/shared/src/commonMain/kotlin/io.newm.shared/usecases/GetCurrentUserUseCase.kt
@@ -0,0 +1,4 @@
+package io.newm.shared.usecases
+
+class GetCurrentUserUseCase {
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/io.newm.shared/usecases/LoginUseCase.kt b/shared/src/commonMain/kotlin/io.newm.shared/usecases/LoginUseCase.kt
index d68abe90..e2ca83ff 100644
--- a/shared/src/commonMain/kotlin/io.newm.shared/usecases/LoginUseCase.kt
+++ b/shared/src/commonMain/kotlin/io.newm.shared/usecases/LoginUseCase.kt
@@ -2,11 +2,29 @@ package io.newm.shared.usecases
import io.newm.shared.login.repository.KMMException
import io.newm.shared.login.repository.LogInRepository
+import io.newm.shared.login.repository.OAuthData
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.inject
import kotlin.coroutines.cancellation.CancellationException
interface LoginUseCase {
@Throws(KMMException::class, CancellationException::class)
suspend fun logIn(email: String, password: String)
+
+ @Throws(KMMException::class, CancellationException::class)
+ suspend fun logInWithGoogle(idToken: String)
+
+ @Throws(KMMException::class, CancellationException::class)
+ suspend fun logInWithFacebook(accessToken: String)
+
+ @Throws(KMMException::class, CancellationException::class)
+ suspend fun logInWithLinkedIn(accessToken: String)
+
+ @Throws(KMMException::class, CancellationException::class)
+ suspend fun logInWithApple(idToken: String)
+
+ fun logOut()
+ val userIsLoggedIn: Boolean
}
internal class LoginUseCaseImpl(private val repository: LogInRepository) : LoginUseCase {
@@ -14,4 +32,39 @@ internal class LoginUseCaseImpl(private val repository: LogInRepository) : Login
override suspend fun logIn(email: String, password: String) {
return repository.logIn(email = email, password = password)
}
+
+ @Throws(KMMException::class, CancellationException::class)
+ override suspend fun logInWithGoogle(idToken: String) {
+ return repository.oAuthLogin(OAuthData.Google(idToken))
+ }
+
+ @Throws(KMMException::class, CancellationException::class)
+ override suspend fun logInWithFacebook(accessToken: String) {
+ return repository.oAuthLogin(OAuthData.Facebook(accessToken))
+ }
+
+ @Throws(KMMException::class, CancellationException::class)
+ override suspend fun logInWithLinkedIn(accessToken: String) {
+ return repository.oAuthLogin(OAuthData.LinkedIn(accessToken))
+ }
+
+ @Throws(KMMException::class, CancellationException::class)
+ override suspend fun logInWithApple(idToken: String) {
+ return repository.oAuthLogin(OAuthData.Apple(idToken))
+ }
+
+ override fun logOut() {
+ repository.logOut()
+ }
+
+ override val userIsLoggedIn: Boolean
+ get() = repository.userIsLoggedIn()
+}
+
+class LoginUseCaseProvider(): KoinComponent {
+ private val loginUseCase: LoginUseCase by inject()
+
+ fun get(): LoginUseCase {
+ return this.loginUseCase
+ }
}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/io.newm.shared/usecases/SignupUseCase.kt b/shared/src/commonMain/kotlin/io.newm.shared/usecases/SignupUseCase.kt
index 6bdf6317..b3d881bf 100644
--- a/shared/src/commonMain/kotlin/io.newm.shared/usecases/SignupUseCase.kt
+++ b/shared/src/commonMain/kotlin/io.newm.shared/usecases/SignupUseCase.kt
@@ -3,6 +3,8 @@ package io.newm.shared.usecases
import io.newm.shared.login.models.NewUser
import io.newm.shared.login.repository.KMMException
import io.newm.shared.login.repository.LogInRepository
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.inject
import kotlin.coroutines.cancellation.CancellationException
interface SignupUseCase {
@@ -43,5 +45,12 @@ internal class SignupUseCaseImpl(private val repository: LogInRepository) : Sign
)
repository.registerUser(newUser)
}
-
}
+
+class SignupUseCaseProvider(): KoinComponent {
+ private val signUpUseCase: SignupUseCase by inject()
+
+ fun get(): SignupUseCase {
+ return signUpUseCase
+ }
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/io.newm.shared/usecases/WalletNFTSongsUseCase.kt b/shared/src/commonMain/kotlin/io.newm.shared/usecases/WalletNFTSongsUseCase.kt
index 6e6090f4..3fe2ba05 100644
--- a/shared/src/commonMain/kotlin/io.newm.shared/usecases/WalletNFTSongsUseCase.kt
+++ b/shared/src/commonMain/kotlin/io.newm.shared/usecases/WalletNFTSongsUseCase.kt
@@ -1,15 +1,10 @@
package io.newm.shared.usecases
-import co.touchlab.kermit.Logger
-import io.newm.shared.login.repository.LogInRepository
import io.newm.shared.models.Song
import io.newm.shared.repositories.CardanoWalletRepository
import io.newm.shared.repositories.NFTTrack
import io.newm.shared.repositories.testdata.MockSongs
-import io.newm.shared.services.LedgerAssetMetadata
-import io.newm.shared.services.UserAPI
import kotlinx.coroutines.flow.Flow
-import org.koin.core.component.inject
interface WalletNFTSongsUseCase {
fun getAllWalletNFTSongs(xPub: String): Flow>
diff --git a/shared/src/commonMain/kotlin/shared/NotificationCenter.kt b/shared/src/commonMain/kotlin/shared/NotificationCenter.kt
new file mode 100644
index 00000000..ad9f6868
--- /dev/null
+++ b/shared/src/commonMain/kotlin/shared/NotificationCenter.kt
@@ -0,0 +1,7 @@
+package shared
+
+expect fun postNotification(name: String)
+
+object Notification {
+ const val loginStateChanged = "login state changed"
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/shared/SecureStorage.kt b/shared/src/commonMain/kotlin/shared/SecureStorage.kt
deleted file mode 100644
index c0bf984f..00000000
--- a/shared/src/commonMain/kotlin/shared/SecureStorage.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package shared
-
-expect class SecureStorage {
- fun store(key: String, value: String)
- fun retrieve(key: String): String?
- fun remove(key: String)
-}
\ No newline at end of file
diff --git a/shared/src/iosMain/kotlin/shared/NotificationCenter.kt b/shared/src/iosMain/kotlin/shared/NotificationCenter.kt
new file mode 100644
index 00000000..d4bc89e4
--- /dev/null
+++ b/shared/src/iosMain/kotlin/shared/NotificationCenter.kt
@@ -0,0 +1,7 @@
+package shared
+
+import platform.Foundation.NSNotificationCenter
+
+actual fun postNotification(name: String) {
+ NSNotificationCenter.defaultCenter.postNotificationName(name, null)
+}
diff --git a/shared/src/iosMain/kotlin/shared/SecureStorage.kt b/shared/src/iosMain/kotlin/shared/SecureStorage.kt
deleted file mode 100644
index ffd4d8af..00000000
--- a/shared/src/iosMain/kotlin/shared/SecureStorage.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-package shared
-
-import platform.Foundation.NSData
-import platform.Foundation.NSString
-import platform.Foundation.create
-import platform.Foundation.kCFBooleanTrue
-import platform.Security.SecItemAdd
-import platform.Security.SecItemCopyMatching
-import platform.Security.SecItemDelete
-import platform.Security.kSecAttrAccount
-import platform.Security.kSecAttrService
-import platform.Security.kSecClass
-import platform.Security.kSecClassGenericPassword
-import platform.Security.kSecMatchLimit
-import platform.Security.kSecMatchLimitOne
-import platform.Security.kSecReturnData
-import platform.Security.kSecValueData
-import platform.darwin.errSecSuccess
-
-actual class SecureStorage(private val account: String) {
- actual fun store(key: String, value: String) {
- val data = value.encodeToByteArray().toNSData()
- val query = dictOf(
- kSecClass to kSecClassGenericPassword,
- kSecAttrAccount to key,
- kSecValueData to data
- )
-
- SecItemDelete(query)
- SecItemAdd(query, null)
- }
-
- actual fun retrieve(key: String): String? {
- val query = dictOf(
- kSecClass to kSecClassGenericPassword,
- kSecAttrAccount to key,
- kSecReturnData to kCFBooleanTrue,
- kSecMatchLimit to kSecMatchLimitOne
- )
-
- memScoped {
- val dataRef = alloc>()
- val status = SecItemCopyMatching(query, dataRef.ptr.reinterpret())
-
- if (status == errSecSuccess) {
- dataRef.value?.let { data ->
- return data.toByteArray().decodeToString()
- }
- }
- }
-
- return null
- }
-
- actual fun remove(key: String) {
- val query = dictOf(
- kSecClass to kSecClassGenericPassword,
- kSecAttrAccount to key
- )
-
- SecItemDelete(query)
- }
-}
diff --git a/shared/src/iosMain/kotlin/shared/actual.kt b/shared/src/iosMain/kotlin/shared/actual.kt
index 71afad5c..96e998ce 100644
--- a/shared/src/iosMain/kotlin/shared/actual.kt
+++ b/shared/src/iosMain/kotlin/shared/actual.kt
@@ -1,9 +1,10 @@
package shared
+import com.liftric.kvault.KVault
import com.squareup.sqldelight.drivers.native.NativeSqliteDriver
-import io.ktor.client.engine.darwin.*
-import io.newm.shared.db.cache.NewmDatabase
+import io.ktor.client.engine.darwin.Darwin
import io.newm.shared.db.NewmDatabaseWrapper
+import io.newm.shared.db.cache.NewmDatabase
import org.koin.dsl.module
actual fun platformModule() = module {
@@ -12,5 +13,5 @@ actual fun platformModule() = module {
NewmDatabaseWrapper(NewmDatabase(driver))
}
single { Darwin.create() }
- single { SecureStorage("user-account") }
+ single { KVault() }
}
\ No newline at end of file