/* * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #if !arch(arm) && !arch(i386) import SwiftUI import CoreGraphics // MARK: - Sign-In Button /// A Google Sign In button to be used in SwiftUI. @available(iOS 13.0, macOS 10.15, *) public struct GoogleSignInButton: View { /// An object containing the styling information needed to create the button. @ObservedObject public var viewModel: GoogleSignInButtonViewModel private let action: () -> Void private let fontLoaded: Bool /// Creates an instance of the Google Sign-In button for use in SwiftUI /// - parameter viewModel: An instance of `GoogleSignInButtonViewModel` /// containing information on the button's scheme, style, and state. /// Defaults to `GoogleSignInButtonViewModel` with its standard defaults. /// - parameter action: A closure to use as the button's action upon press. /// - seealso: Refer to `GoogleSignInButtonViewModel.swift` for its defaults. public init( viewModel: GoogleSignInButtonViewModel = GoogleSignInButtonViewModel(), action: @escaping () -> Void ) { self.viewModel = viewModel self.action = action self.fontLoaded = Font.loadCGFont() } /// A convenience initializer to create a Google Sign-In button in SwiftUI /// with scheme, style, and state. /// - parameter scheme: The `GoogleSignInButtonColorScheme` to use. Defaults /// to `.light`. /// - parameter style: The `GoogleSignInButtonStyle` to use. Defaults to /// `.standard`. /// - parameter state: The `GoogleSignInButtonState` to use. Defaults to /// `.normal`. /// - parameter action: A closure to use as the button's action upon press. public init( scheme: GoogleSignInButtonColorScheme = .light, style: GoogleSignInButtonStyle = .standard, state: GoogleSignInButtonState = .normal, action: @escaping () -> Void ) { let vm = GoogleSignInButtonViewModel( scheme: scheme, style: style, state: state ) self.init(viewModel: vm, action: action) } public var body: some View { Button(action: action) { switch viewModel.style { case .icon: ZStack { RoundedRectangle(cornerRadius: googleCornerRadius) .fill(viewModel.buttonStyle.colors.iconColor) .border(viewModel.buttonStyle.colors.iconBorderColor) Image.signInButtonImage } case .standard, .wide: HStack(alignment: .center) { ZStack { RoundedRectangle(cornerRadius: googleCornerRadius) .fill( viewModel.state == .disabled ? .clear : viewModel.buttonStyle.colors.iconColor ) .frame( width: iconWidth - iconPadding, height: iconWidth - iconPadding ) Image.signInButtonImage } .padding(.leading, 1) Text(viewModel.style.buttonText) .fixedSize() .padding(.trailing, textPadding) .frame( width: viewModel.style.widthForButtonText, height: buttonHeight, alignment: .leading ) Spacer() } } } .font(signInButtonFont) .buttonStyle(viewModel.buttonStyle) .disabled(viewModel.state == .disabled) .simultaneousGesture( DragGesture(minimumDistance: 0) .onChanged { _ in guard viewModel.state != .disabled, viewModel.state != .pressed else { return } viewModel.state = .pressed } .onEnded { _ in guard viewModel.state != .disabled else { return } viewModel.state = .normal } ) } } // MARK: - Google Icon Image @available(iOS 13.0, macOS 10.15, *) private extension Image { static var signInButtonImage: Image { guard let iconURL = Bundle.urlForGoogleResource( name: googleImageName, withExtension: "png" ) else { fatalError("Unable to load Google icon image url: \(Image.Error.unableToLoadGoogleIcon(name: googleImageName))") } #if os(iOS) || targetEnvironment(macCatalyst) guard let uiImage = UIImage(contentsOfFile: iconURL.path) else { fatalError("Unable to load Google icon image url: \(Image.Error.unableToLoadGoogleIcon(name: googleImageName))") } return Image(uiImage: uiImage) #elseif os(macOS) guard let nsImage = NSImage(contentsOfFile: iconURL.path) else { fatalError("Unable to load Google icon image url: \(Image.Error.unableToLoadGoogleIcon(name: googleImageName))") } return Image(nsImage: nsImage) #else fatalError("Unrecognized platform for SwiftUI sign in button image") #endif } enum Error: Swift.Error { case unableToLoadGoogleIcon(name: String) } } // MARK: - Google Font @available(iOS 13.0, macOS 10.15, *) private extension GoogleSignInButton { var signInButtonFont: Font { guard fontLoaded else { return .system(size: fontSize, weight: .bold) } return .custom(fontNameRobotoBold, size: fontSize) } } @available(iOS 13.0, macOS 10.15, *) private extension Font { /// Load the font for the button. /// - returns A `Bool` indicating whether or not the font was loaded. static func loadCGFont() -> Bool { // Check to see if the font has already been loaded #if os(iOS) || targetEnvironment(macCatalyst) if let _ = UIFont(name: fontNameRobotoBold, size: fontSize) { return true } #elseif os(macOS) if let _ = NSFont(name: fontNameRobotoBold, size: fontSize) { return true } #else fatalError("Unrecognized platform for SwiftUI sign in button font") #endif guard let fontURL = Bundle.urlForGoogleResource( name: fontNameRobotoBold, withExtension: "ttf" ), let dataProvider = CGDataProvider(filename: fontURL.path), let newFont = CGFont(dataProvider) else { return false } return CTFontManagerRegisterGraphicsFont(newFont, nil) } } #endif // !arch(arm) && !arch(i386)