LNLoginPanel.swift 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. //
  2. // LNLoginPanel.swift
  3. // Lanu
  4. //
  5. // Created by OneeChan on 2025/12/2.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. import GoogleSignIn
  11. private weak var curLoginPanel: LNLoginPanel? = nil
  12. class LNLoginPanel: LNPopupView {
  13. override init(frame: CGRect) {
  14. super.init(frame: frame)
  15. containerHeight = .percent(1.0)
  16. setupViews()
  17. LNEventDeliver.addObserver(self)
  18. }
  19. static func show(container: UIView?) {
  20. guard curLoginPanel == nil else { return }
  21. let panel = LNLoginPanel()
  22. panel.showIn(container)
  23. curLoginPanel = panel
  24. }
  25. required init?(coder: NSCoder) {
  26. fatalError("init(coder:) has not been implemented")
  27. }
  28. }
  29. extension LNLoginPanel: LNAccountManagerNotify {
  30. func onUserLogin() {
  31. dismiss()
  32. }
  33. }
  34. extension LNLoginPanel: ASAuthorizationControllerDelegate {
  35. func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
  36. guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential,
  37. let token = credential.identityToken,
  38. let tokenStr = String(data: token, encoding: .utf8)
  39. else {
  40. return
  41. }
  42. LNAccountManager.shared.loginByApple(data: tokenStr)
  43. }
  44. func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: any Error) {
  45. }
  46. }
  47. extension LNLoginPanel {
  48. private func setupViews() {
  49. container.backgroundColor = .text_5.withAlphaComponent(0.7)
  50. let logo = UIImageView()
  51. logo.image = .init(named: "ic_login_logo")
  52. container.addSubview(logo)
  53. logo.snp.makeConstraints { make in
  54. make.centerX.equalToSuperview()
  55. make.centerY.equalToSuperview().multipliedBy(0.5)
  56. }
  57. let privacy = buildPrivacy()
  58. container.addSubview(privacy)
  59. privacy.snp.makeConstraints { make in
  60. make.centerX.equalToSuperview()
  61. make.width.equalToSuperview().offset(-50)
  62. make.bottom.equalToSuperview().offset(-safeBottomInset - 40)
  63. }
  64. let stackView = UIStackView()
  65. stackView.axis = .vertical
  66. stackView.spacing = 16
  67. container.addSubview(stackView)
  68. stackView.snp.makeConstraints { make in
  69. make.horizontalEdges.equalToSuperview().inset(38)
  70. make.bottom.equalTo(privacy.snp.top).offset(-50)
  71. }
  72. #if DEBUG
  73. let email = buildEmail()
  74. stackView.addArrangedSubview(email)
  75. #endif
  76. let apple = buildAppleLogin()
  77. stackView.addArrangedSubview(apple)
  78. let google = buildGoogleLogin()
  79. stackView.addArrangedSubview(google)
  80. let tipsLabel = UILabel()
  81. tipsLabel.text = .init(key: "A00114")
  82. tipsLabel.font = .body_m
  83. tipsLabel.textColor = .primary_3
  84. tipsLabel.numberOfLines = 0
  85. tipsLabel.textAlignment = .center
  86. container.addSubview(tipsLabel)
  87. tipsLabel.snp.makeConstraints { make in
  88. make.horizontalEdges.equalToSuperview().inset(38)
  89. make.bottom.equalTo(stackView.snp.top).offset(-16)
  90. }
  91. let close = buildClose()
  92. container.addSubview(close)
  93. close.snp.makeConstraints { make in
  94. make.top.equalToSuperview().offset(UIView.statusBarHeight)
  95. make.trailing.equalToSuperview().offset(-20)
  96. }
  97. }
  98. private func buildAppleLogin() -> UIView {
  99. let button = UIButton()
  100. button.backgroundColor = .init(hex: "#0B0B0A")
  101. button.layer.cornerRadius = 24
  102. button.snp.makeConstraints { make in
  103. make.height.equalTo(48)
  104. }
  105. let ic = UIImageView()
  106. ic.image = .init(named: "ic_apple")
  107. button.addSubview(ic)
  108. ic.snp.makeConstraints { make in
  109. make.centerY.equalToSuperview()
  110. make.leading.equalToSuperview().offset(12)
  111. }
  112. let title = UILabel()
  113. title.font = .heading_h3
  114. title.textColor = .text_1
  115. title.text = .init(key: "A00285")
  116. button.addSubview(title)
  117. title.snp.makeConstraints { make in
  118. make.center.equalToSuperview()
  119. }
  120. button.addAction(UIAction(handler: { [weak self] _ in
  121. guard let self else { return }
  122. let provider = ASAuthorizationAppleIDProvider()
  123. let request = provider.createRequest()
  124. request.requestedScopes = [.fullName, .email]
  125. let controller = ASAuthorizationController(authorizationRequests: [request])
  126. controller.delegate = self
  127. // controller.presentationContextProvider = self
  128. controller.performRequests()
  129. }), for: .touchUpInside)
  130. return button
  131. }
  132. private func buildGoogleLogin() -> UIView {
  133. let button = UIButton()
  134. button.backgroundColor = .fill
  135. button.layer.cornerRadius = 24
  136. button.snp.makeConstraints { make in
  137. make.height.equalTo(48)
  138. }
  139. let ic = UIImageView()
  140. ic.image = .init(named: "google")
  141. button.addSubview(ic)
  142. ic.snp.makeConstraints { make in
  143. make.centerY.equalToSuperview()
  144. make.leading.equalToSuperview().offset(22)
  145. make.width.height.equalTo(20)
  146. }
  147. let title = UILabel()
  148. title.font = .heading_h3
  149. title.textColor = .text_5
  150. title.text = .init(key: "A00115")
  151. button.addSubview(title)
  152. title.snp.makeConstraints { make in
  153. make.center.equalToSuperview()
  154. }
  155. button.addAction(UIAction(handler: { [weak self] _ in
  156. guard let self else { return }
  157. guard let topVC = navigationController?.topViewController else { return }
  158. GIDSignIn.sharedInstance.signIn(withPresenting: topVC) { [weak self] result, err in
  159. guard self != nil else { return }
  160. guard err == nil, let result else { return }
  161. guard let token = result.user.idToken?.tokenString else { return }
  162. LNAccountManager.shared.loginByGoogle(data: token)
  163. }
  164. }), for: .touchUpInside)
  165. return button
  166. }
  167. private func buildPrivacy() -> UIView {
  168. let text: String = .init(key: "A00116")
  169. let attrStr = NSMutableAttributedString(string: text)
  170. let agreementRange = (text as NSString).range(of: .init(key: "A00117"))
  171. attrStr.addAttribute(.link, value: String.serviceUrl, range: agreementRange)
  172. let privacyRange = (text as NSString).range(of: .init(key: "A00118"))
  173. attrStr.addAttribute(.link, value: String.privacyUrl, range: privacyRange)
  174. let privacyView = LNPrivacyTextView()
  175. privacyView.attributedText = attrStr
  176. privacyView.textAlignment = .center
  177. privacyView.backgroundColor = .clear
  178. privacyView.font = .systemFont(ofSize: 13.4)
  179. privacyView.textColor = .text_2
  180. privacyView.linkTextAttributes = [.foregroundColor: UIColor.text_1]
  181. return privacyView
  182. }
  183. #if DEBUG
  184. private func buildEmail() -> UIView {
  185. let container = UIView()
  186. container.backgroundColor = .fill
  187. container.layer.cornerRadius = 24
  188. container.snp.makeConstraints { make in
  189. make.height.equalTo(48)
  190. }
  191. let input = UITextField()
  192. let login = UIButton()
  193. login.setContentHuggingPriority(.defaultHigh, for: .horizontal)
  194. login.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
  195. login.setImage(.init(systemName: "arrow.forward"), for: .normal)
  196. login.addAction(UIAction(handler: { [weak self] _ in
  197. guard self != nil else { return }
  198. guard let text = input.text, !text.isEmpty else { return }
  199. LNAccountManager.shared.loginByEmail(email: text) { _ in }
  200. }), for: .touchUpInside)
  201. container.addSubview(login)
  202. login.snp.makeConstraints { make in
  203. make.centerY.equalToSuperview()
  204. make.trailing.equalToSuperview().offset(-16)
  205. }
  206. container.addSubview(input)
  207. input.snp.makeConstraints { make in
  208. make.verticalEdges.equalToSuperview()
  209. make.leading.equalToSuperview().offset(16)
  210. make.trailing.equalTo(login.snp.leading)
  211. }
  212. return container
  213. }
  214. #endif
  215. private func buildClose() -> UIView {
  216. let config = UIImage.SymbolConfiguration(pointSize: 24)
  217. let button = UIButton()
  218. button.setImage(.init(systemName: "xmark", withConfiguration: config), for: .normal)
  219. button.tintColor = .white
  220. button.addAction(UIAction(handler: { [weak self] _ in
  221. guard let self else { return }
  222. self.dismiss()
  223. }), for: .touchUpInside)
  224. return button
  225. }
  226. }
  227. #if DEBUG
  228. import SwiftUI
  229. import AuthenticationServices
  230. struct LNLoginPanelPreview: UIViewRepresentable {
  231. func makeUIView(context: Context) -> some UIView {
  232. let container = UIView()
  233. container.backgroundColor = .lightGray
  234. let view = LNLoginPanel()
  235. view.showIn(container)
  236. return container
  237. }
  238. func updateUIView(_ uiView: UIViewType, context: Context) { }
  239. }
  240. #Preview(body: {
  241. LNLoginPanelPreview()
  242. })
  243. #endif