diff --git a/CoreData/ApplicationSettingsHandler.swift b/CoreData/ApplicationSettingsHandler.swift index 523a0ab..06791f6 100644 --- a/CoreData/ApplicationSettingsHandler.swift +++ b/CoreData/ApplicationSettingsHandler.swift @@ -35,9 +35,9 @@ class ApplicationSettingsHandler { } } - func set(accountData: AccountData?) { + func set(accountId: String?) { let defaultSettings = self.get() - defaultSettings.currentAccount = accountData?.id + defaultSettings.currentAccount = accountId CoreDataHandler.shared.save() } diff --git a/Vernissage.xcodeproj/project.pbxproj b/Vernissage.xcodeproj/project.pbxproj index 2b03b5c..1b3f591 100644 --- a/Vernissage.xcodeproj/project.pbxproj +++ b/Vernissage.xcodeproj/project.pbxproj @@ -1308,7 +1308,7 @@ CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 88; + CURRENT_PROJECT_VERSION = 89; DEVELOPMENT_TEAM = B2U9FEKYP8; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = VernissageWidget/Info.plist; @@ -1336,7 +1336,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 88; + CURRENT_PROJECT_VERSION = 89; DEVELOPMENT_TEAM = B2U9FEKYP8; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = VernissageWidget/Info.plist; @@ -1484,7 +1484,7 @@ CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 88; + CURRENT_PROJECT_VERSION = 89; DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\""; DEVELOPMENT_TEAM = B2U9FEKYP8; ENABLE_PREVIEWS = YES; @@ -1524,7 +1524,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 88; + CURRENT_PROJECT_VERSION = 89; DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\""; DEVELOPMENT_TEAM = B2U9FEKYP8; ENABLE_PREVIEWS = YES; diff --git a/Vernissage/Services/AuthorizationService.swift b/Vernissage/Services/AuthorizationService.swift index 699723f..c5e7050 100644 --- a/Vernissage/Services/AuthorizationService.swift +++ b/Vernissage/Services/AuthorizationService.swift @@ -15,28 +15,28 @@ public class AuthorizationService { private init() { } /// Access token verification. - public func verifyAccount(session: AuthorizationSession, currentAccount: AccountData?, _ result: @escaping (AccountData?) -> Void) async { + public func verifyAccount(session: AuthorizationSession, accountModel: AccountModel, _ result: @escaping (AccountModel?) -> Void) async { // When we dont have even one account stored in database then we have to ask user to enter server and sign in. - guard let currentAccount, let accessToken = currentAccount.accessToken else { + guard let accessToken = accountModel.accessToken else { result(nil) return } // When we have at least one account then we have to verify access token. - let client = PixelfedClient(baseURL: currentAccount.serverUrl).getAuthenticated(token: accessToken) + let client = PixelfedClient(baseURL: accountModel.serverUrl).getAuthenticated(token: accessToken) do { let account = try await client.verifyCredentials() - await self.update(accountId: currentAccount.id, - basedOn: account, - accessToken: accessToken, - refreshToken: currentAccount.refreshToken) + let signedInAccountModel = await self.update(accountId: accountModel.id, + basedOn: account, + accessToken: accessToken, + refreshToken: accountModel.refreshToken) - result(currentAccount) + result(signedInAccountModel) } catch { do { - try await self.refreshCredentials(for: currentAccount, presentationContextProvider: session) - result(currentAccount) + let signedInAccountModel = try await self.refreshCredentials(for: accountModel, presentationContextProvider: session) + result(signedInAccountModel) } catch { ErrorService.shared.handle(error, message: "Issues during refreshing credentials.", showToastr: true) } @@ -44,7 +44,7 @@ public class AuthorizationService { } /// Sign in to the Pixelfed server. - public func sign(in serverAddress: String, session: AuthorizationSession, _ result: @escaping (AccountData) -> Void) async throws { + public func sign(in serverAddress: String, session: AuthorizationSession, _ result: @escaping (AccountModel) -> Void) async throws { guard let baseUrl = URL(string: serverAddress) else { throw AuthorisationError.badServerUrl @@ -124,7 +124,8 @@ public class AuthorizationService { CoreDataHandler.shared.save(viewContext: backgroundContext) // Return account data. - result(accountData) + let accountModel = AccountModel(accountData: accountData) + result(accountModel) } public func refreshAccessTokens() async { @@ -134,7 +135,7 @@ public class AuthorizationService { for account in accounts { group.addTask { do { - try await self.refreshAccessToken(accountData: account) + _ = try await self.refreshAccessToken(accountData: account) #if DEBUG ToastrService.shared.showSuccess("New access tokens has been retrieved.", imageSystemName: "key.fill") @@ -151,11 +152,11 @@ public class AuthorizationService { } } - private func refreshAccessToken(accountData: AccountData) async throws { + private func refreshAccessToken(accountData: AccountData) async throws -> AccountModel? { let client = PixelfedClient(baseURL: accountData.serverUrl) guard let refreshToken = accountData.refreshToken else { - return + return nil } let oAuthSwiftCredential = try await client.refreshToken(clientId: accountData.clientId, @@ -167,21 +168,22 @@ public class AuthorizationService { // Get account information from server. let account = try await authenticatedClient.verifyCredentials() - await self.update(accountId: accountData.id, - basedOn: account, - accessToken: oAuthSwiftCredential.oauthToken, - refreshToken: oAuthSwiftCredential.oauthRefreshToken) + + return await self.update(accountId: accountData.id, + basedOn: account, + accessToken: oAuthSwiftCredential.oauthToken, + refreshToken: oAuthSwiftCredential.oauthRefreshToken) } - private func refreshCredentials(for accountData: AccountData, + private func refreshCredentials(for accountModel: AccountModel, presentationContextProvider: ASWebAuthenticationPresentationContextProviding - ) async throws { + ) async throws -> AccountModel? { - let client = PixelfedClient(baseURL: accountData.serverUrl) + let client = PixelfedClient(baseURL: accountModel.serverUrl) // Create application (we will get clientId and clientSecret). - let oAuthApp = Application(clientId: accountData.clientId, - clientSecret: accountData.clientSecret, + let oAuthApp = Application(clientId: accountModel.clientId, + clientSecret: accountModel.clientSecret, redirectUri: AppConstants.oauthRedirectUri) // Authorize a user (browser, we will get clientCode). @@ -195,20 +197,21 @@ public class AuthorizationService { // Get account information from server. let account = try await authenticatedClient.verifyCredentials() - await self.update(accountId: accountData.id, - basedOn: account, - accessToken: oAuthSwiftCredential.oauthToken, - refreshToken: oAuthSwiftCredential.oauthRefreshToken) + + return await self.update(accountId: accountModel.id, + basedOn: account, + accessToken: oAuthSwiftCredential.oauthToken, + refreshToken: oAuthSwiftCredential.oauthRefreshToken) } private func update(accountId: String, basedOn account: Account, accessToken: String, refreshToken: String? - ) async { + ) async -> AccountModel? { let backgroundContext = CoreDataHandler.shared.newBackgroundContext() guard let dbAccount = AccountDataHandler.shared.getAccountData(accountId: accountId, viewContext: backgroundContext) else { - return + return nil } dbAccount.username = account.username @@ -241,6 +244,8 @@ public class AuthorizationService { // Save account data in database and in application state. CoreDataHandler.shared.save(viewContext: backgroundContext) + + return AccountModel(accountData: dbAccount) } private func getAccountData(account: Account, backgroundContext: NSManagedObjectContext) -> AccountData { diff --git a/Vernissage/VernissageApp.swift b/Vernissage/VernissageApp.swift index 4096b8c..e7a0d57 100644 --- a/Vernissage/VernissageApp.swift +++ b/Vernissage/VernissageApp.swift @@ -34,8 +34,8 @@ struct VernissageApp: App { .withAppRouteur() .withSheetDestinations(sheetDestinations: $routerPath.presentedSheet) case .signIn: - SignInView { accountData in - self.setApplicationState(accountData: accountData) + SignInView { accountModel in + self.setApplicationState(accountModel: accountModel) } .withAppRouteur() .withSheetDestinations(sheetDestinations: $routerPath.presentedSheet) @@ -91,6 +91,7 @@ struct VernissageApp: App { } } + @MainActor private func onApplicationStart() async { UIPageControl.appearance().currentPageIndicatorTintColor = UIColor.white.withAlphaComponent(0.7) UIPageControl.appearance().pageIndicatorTintColor = UIColor.white.withAlphaComponent(0.4) @@ -104,22 +105,29 @@ struct VernissageApp: App { // Refresh other access tokens. await self.refreshAccessTokens() + // When user doesn't exists then we have to open sign in view. + guard let currentAccount = AccountDataHandler.shared.getCurrentAccountData() else { + self.applicationViewMode = .signIn + return + } + + // Create model based on core data entity. + let accountModel = AccountModel(accountData: currentAccount) + // Verify access token correctness. let authorizationSession = AuthorizationSession() - let currentAccount = AccountDataHandler.shared.getCurrentAccountData() - await AuthorizationService.shared.verifyAccount(session: authorizationSession, currentAccount: currentAccount) { accountData in - guard let accountData = accountData else { + await AuthorizationService.shared.verifyAccount(session: authorizationSession, accountModel: accountModel) { signedInAccountModel in + guard let signedInAccountModel else { self.applicationViewMode = .signIn return } - self.setApplicationState(accountData: accountData, checkNewPhotos: true) + self.setApplicationState(accountModel: signedInAccountModel, checkNewPhotos: true) } } - private func setApplicationState(accountData: AccountData, checkNewPhotos: Bool = false) { + private func setApplicationState(accountModel: AccountModel, checkNewPhotos: Bool = false) { Task { @MainActor in - let accountModel = AccountModel(accountData: accountData) let instance = try? await self.client.instances.instance(url: accountModel.serverUrl) // Refresh client state. @@ -128,7 +136,7 @@ struct VernissageApp: App { // Refresh application state. self.applicationState.changeApplicationState(accountModel: accountModel, instance: instance, - lastSeenStatusId: accountData.lastSeenStatusId) + lastSeenStatusId: accountModel.lastSeenStatusId) // Change view displayed by application. self.applicationViewMode = .mainView diff --git a/Vernissage/Views/MainView.swift b/Vernissage/Views/MainView.swift index 5ac3440..2ee0d38 100644 --- a/Vernissage/Views/MainView.swift +++ b/Vernissage/Views/MainView.swift @@ -323,26 +323,27 @@ struct MainView: View { Task { // Verify access token correctness. let authorizationSession = AuthorizationSession() - await AuthorizationService.shared.verifyAccount(session: authorizationSession, currentAccount: account) { accountData in - guard let accountData = accountData else { + let accountModel = AccountModel(accountData: account) + + await AuthorizationService.shared.verifyAccount(session: authorizationSession, accountModel: accountModel) { signedInAccountModel in + guard let signedInAccountModel else { ToastrService.shared.showError(subtitle: "mainview.error.switchAccounts") return } Task { @MainActor in - let accountModel = AccountModel(accountData: accountData) - let instance = try? await self.client.instances.instance(url: accountModel.serverUrl) + let instance = try? await self.client.instances.instance(url: signedInAccountModel.serverUrl) // Refresh client state. - self.client.setAccount(account: accountModel) + self.client.setAccount(account: signedInAccountModel) // Refresh application state. - self.applicationState.changeApplicationState(accountModel: accountModel, + self.applicationState.changeApplicationState(accountModel: signedInAccountModel, instance: instance, - lastSeenStatusId: accountData.lastSeenStatusId) + lastSeenStatusId: signedInAccountModel.lastSeenStatusId) // Set account as default (application will open this account after restart). - ApplicationSettingsHandler.shared.set(accountData: accountData) + ApplicationSettingsHandler.shared.set(accountId: signedInAccountModel.id) } } } diff --git a/Vernissage/Views/SettingsView/Subviews/AccountsSectionView.swift b/Vernissage/Views/SettingsView/Subviews/AccountsSectionView.swift index f8be4a2..bd57a1d 100644 --- a/Vernissage/Views/SettingsView/Subviews/AccountsSectionView.swift +++ b/Vernissage/Views/SettingsView/Subviews/AccountsSectionView.swift @@ -72,7 +72,7 @@ struct AccountsSectionView: View { if shouldClearApplicationState { // We have to do this after animation of deleting row is ended. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - ApplicationSettingsHandler.shared.set(accountData: nil) + ApplicationSettingsHandler.shared.set(accountId: nil) self.applicationState.clearApplicationState() self.client.clearAccount() } diff --git a/Vernissage/Views/SignInView/SignInView.swift b/Vernissage/Views/SignInView/SignInView.swift index 6810785..a1bb3ea 100644 --- a/Vernissage/Views/SignInView/SignInView.swift +++ b/Vernissage/Views/SignInView/SignInView.swift @@ -19,7 +19,7 @@ struct SignInView: View { @State private var instructionsUrlString:String? @State private var instances: [Instance] = [] - var onSignedIn: ((_ accountData: AccountData) -> Void)? + var onSignedIn: ((_ accountModel: AccountModel) -> Void)? var body: some View { List { @@ -91,8 +91,8 @@ struct SignInView: View { Task { do { let authorizationSession = AuthorizationSession() - try await AuthorizationService.shared.sign(in: baseAddress, session: authorizationSession) { accountData in - onSignedIn?(accountData) + try await AuthorizationService.shared.sign(in: baseAddress, session: authorizationSession) { accountModel in + onSignedIn?(accountModel) DispatchQueue.main.sync { dismiss()