Extensions.swift 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. // Copyright 2020 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import FirebaseAuth
  15. import SwiftUI
  16. import UIKit
  17. // MARK: - Extending a `Firebase User` to conform to `DataSourceProvidable`
  18. extension User: DataSourceProvidable {
  19. private var infoSection: Section {
  20. let items = [Item(title: providerID, detailTitle: "Provider ID"),
  21. Item(title: uid, detailTitle: "UUID"),
  22. Item(title: displayName ?? "––", detailTitle: "Display Name", isEditable: true),
  23. Item(
  24. title: photoURL?.absoluteString ?? "––",
  25. detailTitle: "Photo URL",
  26. isEditable: true
  27. ),
  28. Item(title: email ?? "––", detailTitle: "Email", isEditable: true),
  29. Item(title: phoneNumber ?? "––", detailTitle: "Phone Number", isEditable: true)]
  30. return Section(headerDescription: "Info", items: items)
  31. }
  32. private var passkeysSection: Section {
  33. let passkeys = enrolledPasskeys ?? []
  34. guard !passkeys.isEmpty else {
  35. return Section(
  36. headerDescription: "Passkeys",
  37. items: [Item(title: "None", detailTitle: "No passkeys enrolled")]
  38. )
  39. }
  40. let items: [Item] = passkeys.map { info in
  41. Item(title: info.name, detailTitle: info.credentialID)
  42. }
  43. return Section(headerDescription: "Passkeys", items: items)
  44. }
  45. private var metaDataSection: Section {
  46. let metadataRows = [
  47. Item(title: metadata.lastSignInDate?.description, detailTitle: "Last Sign-in Date"),
  48. Item(title: metadata.creationDate?.description, detailTitle: "Creation Date"),
  49. ]
  50. return Section(headerDescription: "Firebase Metadata", items: metadataRows)
  51. }
  52. private var otherSection: Section {
  53. let otherRows = [Item(title: isAnonymous ? "Yes" : "No", detailTitle: "Is User Anonymous?"),
  54. Item(title: isEmailVerified ? "Yes" : "No", detailTitle: "Is Email Verified?")]
  55. return Section(headerDescription: "Other", items: otherRows)
  56. }
  57. private var actionSection: Section {
  58. let actionsRows = [
  59. Item(title: UserAction.refreshUserInfo.rawValue, textColor: .systemBlue),
  60. Item(title: UserAction.signOut.rawValue, textColor: .systemBlue),
  61. Item(title: UserAction.link.rawValue, textColor: .systemBlue, hasNestedContent: true),
  62. Item(title: UserAction.requestVerifyEmail.rawValue, textColor: .systemBlue),
  63. Item(title: UserAction.updatePassword.rawValue, textColor: .systemBlue),
  64. Item(title: UserAction.tokenRefresh.rawValue, textColor: .systemBlue),
  65. Item(title: UserAction.tokenRefreshAsync.rawValue, textColor: .systemBlue),
  66. Item(title: UserAction.delete.rawValue, textColor: .systemRed),
  67. ]
  68. return Section(headerDescription: "Actions", items: actionsRows)
  69. }
  70. var sections: [Section] {
  71. [infoSection, passkeysSection, metaDataSection, otherSection, actionSection]
  72. }
  73. }
  74. // MARK: - UIKit Extensions
  75. public extension UIViewController {
  76. func displayInfo(title: String, message: String, style: UIAlertController.Style) {
  77. let alert = UIAlertController(title: title, message: message, preferredStyle: style)
  78. alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
  79. DispatchQueue.main.async { // Ensure UI updates on the main thread
  80. self.present(alert, animated: true, completion: nil)
  81. }
  82. }
  83. @MainActor func displayError(_ error: (any Error)?, from function: StaticString = #function) {
  84. guard let error = error else { return }
  85. print("ⓧ Error in \(function): \(error.localizedDescription)")
  86. let message = "\(error.localizedDescription)\n\n Occurred in \(function)"
  87. let errorAlertController = UIAlertController(
  88. title: "Error",
  89. message: message,
  90. preferredStyle: .alert
  91. )
  92. errorAlertController.addAction(UIAlertAction(title: "OK", style: .default))
  93. present(errorAlertController, animated: true, completion: nil)
  94. }
  95. }
  96. extension UINavigationController {
  97. func configureTabBar(title: String, systemImageName: String) {
  98. let tabBarItemImage = UIImage(systemName: systemImageName)
  99. tabBarItem = UITabBarItem(title: title,
  100. image: tabBarItemImage?.withRenderingMode(.alwaysTemplate),
  101. selectedImage: tabBarItemImage)
  102. }
  103. enum titleType: CaseIterable {
  104. case regular, large
  105. }
  106. func setTitleColor(_ color: UIColor, _ types: [titleType] = titleType.allCases) {
  107. if types.contains(.regular) {
  108. navigationBar.titleTextAttributes = [.foregroundColor: color]
  109. }
  110. if types.contains(.large) {
  111. navigationBar.largeTitleTextAttributes = [.foregroundColor: color]
  112. }
  113. }
  114. }
  115. extension UITextField {
  116. func setImage(_ image: UIImage?) {
  117. guard let image = image else { return }
  118. let imageView = UIImageView(image: image)
  119. imageView.frame = CGRect(x: 10, y: 10, width: 20, height: 20)
  120. imageView.contentMode = .scaleAspectFit
  121. let containerView = UIView()
  122. containerView.frame = CGRect(x: 20, y: 0, width: 40, height: 40)
  123. containerView.addSubview(imageView)
  124. leftView = containerView
  125. leftViewMode = .always
  126. }
  127. }
  128. extension UIImageView {
  129. convenience init(systemImageName: String, tintColor: UIColor? = nil) {
  130. var systemImage = UIImage(systemName: systemImageName)
  131. if let tintColor = tintColor {
  132. systemImage = systemImage?.withTintColor(tintColor, renderingMode: .alwaysOriginal)
  133. }
  134. self.init(image: systemImage)
  135. }
  136. func setImage(from url: URL?) {
  137. guard let url = url else { return }
  138. DispatchQueue.global(qos: .background).async {
  139. guard let data = try? Data(contentsOf: url) else { return }
  140. let image = UIImage(data: data)
  141. DispatchQueue.main.async {
  142. self.image = image
  143. self.contentMode = .scaleAspectFit
  144. }
  145. }
  146. }
  147. }
  148. extension UIImage {
  149. static func systemImage(_ systemName: String, tintColor: UIColor) -> UIImage? {
  150. let systemImage = UIImage(systemName: systemName)
  151. return systemImage?.withTintColor(tintColor, renderingMode: .alwaysOriginal)
  152. }
  153. }
  154. extension UIColor {
  155. static let highlightedLabel = UIColor.label.withAlphaComponent(0.8)
  156. var highlighted: UIColor { withAlphaComponent(0.8) }
  157. var image: UIImage {
  158. let pixel = CGSize(width: 1, height: 1)
  159. return UIGraphicsImageRenderer(size: pixel).image { context in
  160. self.setFill()
  161. context.fill(CGRect(origin: .zero, size: pixel))
  162. }
  163. }
  164. }
  165. // MARK: UINavigationBar + UserDisplayable Protocol
  166. protocol UserDisplayable {
  167. func addProfilePic(_ imageView: UIImageView)
  168. }
  169. extension UINavigationBar: UserDisplayable {
  170. func addProfilePic(_ imageView: UIImageView) {
  171. let length = frame.height * 0.46
  172. imageView.clipsToBounds = true
  173. imageView.layer.cornerRadius = length / 2
  174. imageView.translatesAutoresizingMaskIntoConstraints = false
  175. addSubview(imageView)
  176. NSLayoutConstraint.activate([
  177. imageView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -15),
  178. imageView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -5),
  179. imageView.heightAnchor.constraint(equalToConstant: length),
  180. imageView.widthAnchor.constraint(equalToConstant: length),
  181. ])
  182. }
  183. }
  184. // MARK: Extending UITabBarController to work with custom transition animator
  185. extension UITabBarController: UITabBarControllerDelegate {
  186. public func tabBarController(_ tabBarController: UITabBarController,
  187. animationControllerForTransitionFrom fromVC: UIViewController,
  188. to toVC: UIViewController)
  189. -> (any UIViewControllerAnimatedTransitioning)? {
  190. let fromIndex = tabBarController.viewControllers!.firstIndex(of: fromVC)!
  191. let toIndex = tabBarController.viewControllers!.firstIndex(of: toVC)!
  192. let direction: Animator.TransitionDirection = fromIndex < toIndex ? .right : .left
  193. return Animator(direction)
  194. }
  195. func transitionToViewController(atIndex index: Int) {
  196. selectedIndex = index
  197. }
  198. }
  199. // MARK: - Foundation Extensions
  200. extension Date {
  201. var description: String {
  202. let dateFormatter = DateFormatter()
  203. dateFormatter.dateStyle = .medium
  204. dateFormatter.timeStyle = .short
  205. return dateFormatter.string(from: self)
  206. }
  207. }