GoogleSignInButtonStyling.swift 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. /*
  2. * Copyright 2022 Google LLC
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import SwiftUI
  17. // MARK: - Sizing Constants
  18. /// The corner radius of the button
  19. let googleCornerRadius: CGFloat = 2
  20. /// The standard height of the sign in button.
  21. let buttonHeight: CGFloat = 40
  22. /// The width of the icon part of the button in points.
  23. let iconWidth: CGFloat = 40
  24. /// The padding to be applied to the Google icon image.
  25. let iconPadding: CGFloat = 2
  26. /// Left and right text padding.
  27. let textPadding: CGFloat = 14
  28. // MARK: - Font
  29. /// The name of the font for button text.
  30. let fontNameRobotoBold = "Roboto-Bold"
  31. /// Sign-in button text font size.
  32. let fontSize: CGFloat = 14
  33. // MARK: - Icon Image
  34. let googleImageName = "google"
  35. // MARK: - Button Style
  36. /// The layout styles supported by the sign-in button.
  37. ///
  38. /// The minimum size of the button depends on the language used for text.
  39. @available(iOS 13.0, macOS 10.15, *)
  40. public enum GoogleSignInButtonStyle: String, Identifiable, CaseIterable {
  41. case standard
  42. case wide
  43. case icon
  44. public var id: String { rawValue }
  45. }
  46. // MARK: - Button Color Scheme
  47. /// The color schemes supported by the sign-in button.
  48. @available(iOS 13.0, macOS 10.15, *)
  49. public enum GoogleSignInButtonColorScheme: String, Identifiable, CaseIterable {
  50. case dark
  51. case light
  52. public var id: String { rawValue }
  53. }
  54. // MARK: - Button State
  55. /// The state of the sign-in button.
  56. @available(iOS 13.0, macOS 10.15, *)
  57. public enum GoogleSignInButtonState: String, Identifiable, CaseIterable {
  58. case normal
  59. case disabled
  60. case pressed
  61. public var id: String { rawValue }
  62. }
  63. // MARK: - Colors
  64. // All colors in hex RGBA format (0xRRGGBBAA)
  65. let googleBlue = 0x4285f4ff;
  66. let googleDarkBlue = 0x3367d6ff;
  67. let white = 0xffffffff;
  68. let lightestGrey = 0x00000014;
  69. let lightGrey = 0xeeeeeeff;
  70. let disabledGrey = 0x00000066;
  71. let darkestGrey = 0x00000089;
  72. /// Helper type to calculate foreground color for text.
  73. @available(iOS 13.0, macOS 10.15, *)
  74. private struct ForegroundColor {
  75. let scheme: GoogleSignInButtonColorScheme
  76. let state: GoogleSignInButtonState
  77. var color: Color {
  78. switch (scheme, state) {
  79. case (.dark, .normal):
  80. return Color(hex: white)
  81. case (.light, .normal):
  82. return Color(hex: darkestGrey)
  83. case (_, .disabled):
  84. return Color(hex: disabledGrey)
  85. case (.dark, .pressed):
  86. return Color(hex: white)
  87. case (.light, .pressed):
  88. return Color(hex: darkestGrey)
  89. }
  90. }
  91. }
  92. /// Helper type to calculate background color for view.
  93. @available(iOS 13.0, macOS 10.15, *)
  94. private struct BackgroundColor {
  95. let scheme: GoogleSignInButtonColorScheme
  96. let state: GoogleSignInButtonState
  97. var color: Color {
  98. switch (scheme, state) {
  99. case (.dark, .normal):
  100. return Color(hex: googleBlue)
  101. case (.light, .normal):
  102. return Color(hex: white)
  103. case (_, .disabled):
  104. return Color(hex: lightestGrey)
  105. case (.dark, .pressed):
  106. return Color(hex: googleDarkBlue)
  107. case (.light, .pressed):
  108. return Color(hex: lightGrey)
  109. }
  110. }
  111. }
  112. /// Helper type to calculate background color for the icon.
  113. @available(iOS 13.0, macOS 10.15, *)
  114. private struct IconBackgroundColor {
  115. let scheme: GoogleSignInButtonColorScheme
  116. let state: GoogleSignInButtonState
  117. var color: Color {
  118. switch (scheme, state) {
  119. case (.light, .pressed), (_, .disabled):
  120. return Color(hex: lightGrey)
  121. default:
  122. return Color(hex: white)
  123. }
  124. }
  125. }
  126. /// A type calculating the background and foreground (text) colors of the
  127. /// sign-in button.
  128. @available(iOS 13.0, macOS 10.15, *)
  129. struct SignInButtonColor {
  130. let scheme: GoogleSignInButtonColorScheme
  131. let state: GoogleSignInButtonState
  132. var iconColor: Color {
  133. let ibc = IconBackgroundColor(scheme: scheme, state: state)
  134. return ibc.color
  135. }
  136. // Icon's border should always match background regardless of state
  137. var iconBorderColor: Color {
  138. return backgroundColor
  139. }
  140. var foregroundColor: Color {
  141. let fc = ForegroundColor(scheme: scheme, state: state)
  142. return fc.color
  143. }
  144. var backgroundColor: Color {
  145. let bc = BackgroundColor(scheme: scheme, state: state)
  146. return bc.color
  147. }
  148. }
  149. @available(iOS 13.0, macOS 10.15, *)
  150. extension Color {
  151. init(hex: Int) {
  152. self.init(
  153. red: Double((hex & 0xff000000) >> 24) / 255,
  154. green: Double((hex & 0x00ff0000) >> 16) / 255,
  155. blue: Double((hex & 0x0000ff00) >> 8) / 255,
  156. opacity: Double((hex & 0x000000ff) >> 0) / 255
  157. )
  158. }
  159. }
  160. // MARK: - Custom Width
  161. @available(iOS 13.0, macOS 10.15, *)
  162. fileprivate struct Width {
  163. let min, max: CGFloat
  164. }
  165. // MARK: - Width and Text By Button Style
  166. @available(iOS 13.0, macOS 10.15, *)
  167. extension GoogleSignInButtonStyle {
  168. fileprivate var width: Width {
  169. switch self {
  170. case .icon:
  171. return Width(min: iconWidth, max: iconWidth)
  172. case .standard, .wide:
  173. return Width(
  174. min: iconWidth + widthForButtonText + iconPadding + textPadding,
  175. max: .infinity
  176. )
  177. }
  178. }
  179. private var buttonStrings: GoogleSignInButtonString {
  180. return GoogleSignInButtonString()
  181. }
  182. var buttonText: String {
  183. switch self {
  184. case .wide: return buttonStrings.localizedWideButtonText
  185. case .standard: return buttonStrings.localizedStandardButtonText
  186. case .icon: return ""
  187. }
  188. }
  189. var widthForButtonText: CGFloat {
  190. let bt = buttonText as NSString
  191. let size = CGSize(width: .max, height: .max)
  192. let anyFont: Any
  193. #if os(iOS) || targetEnvironment(macCatalyst)
  194. anyFont = UIFont(name: fontNameRobotoBold, size: fontSize) as Any
  195. #elseif os(macOS)
  196. anyFont = NSFont(name: fontNameRobotoBold, size: fontSize) as Any
  197. #else
  198. fatalError("Unrecognized platform to calculate minimum width")
  199. #endif
  200. let rect = bt.boundingRect(
  201. with: size,
  202. options: [],
  203. attributes: [.font: anyFont],
  204. context: nil
  205. )
  206. return rect.width
  207. }
  208. }
  209. // MARK: - Button Style
  210. @available(iOS 13.0, macOS 10.15, *)
  211. struct SwiftUIButtonStyle: ButtonStyle {
  212. let style: GoogleSignInButtonStyle
  213. let state: GoogleSignInButtonState
  214. let scheme: GoogleSignInButtonColorScheme
  215. /// A computed property vending the button's foreground and background colors.
  216. var colors: SignInButtonColor {
  217. return SignInButtonColor(scheme: scheme, state: state)
  218. }
  219. func makeBody(configuration: Configuration) -> some View {
  220. configuration.label
  221. .frame(minWidth: style.width.min,
  222. maxWidth: style.width.max,
  223. minHeight: buttonHeight,
  224. maxHeight: buttonHeight)
  225. .background(colors.backgroundColor)
  226. .foregroundColor(colors.foregroundColor)
  227. .cornerRadius(googleCornerRadius)
  228. .shadow(
  229. color: state == .disabled ? .clear : .gray,
  230. radius: googleCornerRadius, x: 0, y: 2
  231. )
  232. }
  233. }