GoogleSignInButtonStyling.swift 7.2 KB

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