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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ final class GoogleSignInAuthenticator: ObservableObject {

/// Signs in the user based upon the selected account.'
/// - note: Successful calls to this will set the `authViewModel`'s `state` property.
func signIn() {
@MainActor func signIn() {
#if os(iOS)
guard let rootViewController = UIApplication.shared.windows.first?.rootViewController else {
print("There is no root view controller!")
Expand Down Expand Up @@ -98,7 +98,7 @@ final class GoogleSignInAuthenticator: ObservableObject {
/// `addScopes(_:presenting:)` request.
/// - note: Successful requests will update the `authViewModel.state` with a new current user that
/// has the granted scope.
func addBirthdayReadScope(completion: @escaping () -> Void) {
@MainActor func addBirthdayReadScope(completion: @escaping () -> Void) {
guard let currentUser = GIDSignIn.sharedInstance.currentUser else {
fatalError("No user signed in!")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,38 +22,50 @@ typealias GIDImage = NSImage

import Combine
import SwiftUI
import GoogleSignIn
@preconcurrency import GoogleSignIn

/// An observable class for loading the current user's profile image.
final class UserProfileImageLoader: ObservableObject {
@MainActor final class UserProfileImageLoader: ObservableObject {
private let userProfile: GIDProfileData
private let imageLoaderQueue = DispatchQueue(label: "com.google.days-until-birthday")
private var imageLoadingTask: Task<Void, Never>?
/// A `UIImage` property containing the current user's profile image.
/// - note: This will default to a placeholder, and updates will be published to subscribers.
@Published var image = GIDImage(named: "PlaceholderAvatar")!

/// Creates an instance of this loader with provided user profile.
/// - note: The instance will asynchronously fetch the image data upon creation.
init(userProfile: GIDProfileData) {
self.userProfile = userProfile
guard userProfile.hasImage else {
return
init(userProfile: GIDProfileData) {
self.userProfile = userProfile
guard userProfile.hasImage else {
return
}
imageLoadingTask = Task {
await loadProfileImage()
}
}

imageLoaderQueue.async {
#if os(iOS)
let dimension = 45 * UIScreen.main.scale
#elseif os(macOS)
let dimension = 120
#endif
guard let url = userProfile.imageURL(withDimension: UInt(dimension)),
let data = try? Data(contentsOf: url),
let image = GIDImage(data: data) else {
return
}
DispatchQueue.main.async {
self.image = image
}
private func loadProfileImage() async {
#if os(iOS)
let dimension = 45 * UIScreen.main.scale
#elseif os(macOS)
let dimension = 120
#endif

guard let url = userProfile.imageURL(withDimension: UInt(dimension)) else {
return
}

do {
let (imageData, _) = try await URLSession.shared.data(from: url)
if let image = GIDImage(data: imageData) {
self.image = image
}
} catch {
print("Image download failed:", error)
}
}

deinit {
imageLoadingTask?.cancel()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ final class AuthenticationViewModel: ObservableObject {
}

/// Signs the user in.
func signIn() {
@MainActor func signIn() {
authenticator.signIn()
}

Expand All @@ -66,8 +66,8 @@ final class AuthenticationViewModel: ObservableObject {

/// Adds the requested birthday read scope.
/// - parameter completion: An escaping closure that is called upon successful completion.
func addBirthdayReadScope(completion: @escaping () -> Void) {
authenticator.addBirthdayReadScope(completion: completion)
@MainActor func addBirthdayReadScope(completion: @escaping () -> Void) {
authenticator.addBirthdayReadScope(completion: completion)
}

}
Expand Down