GoogleSignInButtonStyling.swift 6.9 KB

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