LoginViewController.swift 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. //
  2. // LoginViewController.swift
  3. // VenusKitto
  4. //
  5. // Created by Neoa on 2025/8/21.
  6. //
  7. import Foundation
  8. import UIKit
  9. import WebKit
  10. class LoginViewController: UIViewController {
  11. // UI Elements
  12. var logoImageView: UIImageView!
  13. var phoneNumberTextField: UITextField!
  14. var verificationCodeTextField: UITextField!
  15. var getVerificationCodeButton: UIButton!
  16. var termsCheckboxButton: UIButton!
  17. var loginButton: UIButton!
  18. var termsLabel: UILabel!
  19. // State
  20. var isTermsAccepted: Bool = false
  21. var customAlertView: UIView!
  22. var alertBackground: UIView!
  23. // State
  24. var isCountdownActive: Bool = false
  25. var countdownTimer: Timer?
  26. var remainingTime: Int = 60
  27. var uuid: String?
  28. override func viewDidLoad() {
  29. super.viewDidLoad()
  30. // Set up the view background color
  31. view.backgroundColor = .white
  32. let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
  33. view.addGestureRecognizer(tapGesture)
  34. // Set up the logo
  35. logoImageView = UIImageView()
  36. logoImageView.image = UIImage(named: "cats378")
  37. logoImageView.contentMode = .scaleAspectFit
  38. logoImageView.translatesAutoresizingMaskIntoConstraints = false
  39. view.addSubview(logoImageView)
  40. // Set up phone number text field
  41. phoneNumberTextField = UITextField()
  42. phoneNumberTextField.placeholder = "请输入手机号码"
  43. phoneNumberTextField.keyboardType = .numberPad
  44. phoneNumberTextField.borderStyle = .roundedRect
  45. phoneNumberTextField.translatesAutoresizingMaskIntoConstraints = false
  46. view.addSubview(phoneNumberTextField)
  47. // Set up verification code text field
  48. verificationCodeTextField = UITextField()
  49. verificationCodeTextField.placeholder = "请输入验证码"
  50. verificationCodeTextField.keyboardType = .numberPad
  51. verificationCodeTextField.borderStyle = .roundedRect
  52. verificationCodeTextField.translatesAutoresizingMaskIntoConstraints = false
  53. view.addSubview(verificationCodeTextField)
  54. // Set up get verification code button
  55. getVerificationCodeButton = UIButton(type: .system)
  56. getVerificationCodeButton.setTitle("获取验证码", for: .normal)
  57. getVerificationCodeButton.titleLabel?.font = UIFont.systemFont(ofSize: 13)
  58. getVerificationCodeButton.setTitleColor(UIColor(hex: "#000000"), for: .normal)
  59. getVerificationCodeButton.translatesAutoresizingMaskIntoConstraints = false
  60. getVerificationCodeButton.addTarget(self, action: #selector(getVerificationCode), for: .touchUpInside)
  61. view.addSubview(getVerificationCodeButton)
  62. // Set up terms checkbox button
  63. termsCheckboxButton = UIButton(type: .custom)
  64. termsCheckboxButton.setImage(UIImage(named: "cats370"), for: .normal)
  65. termsCheckboxButton.setImage(UIImage(named: "cats371"), for: .selected)
  66. termsCheckboxButton.addTarget(self, action: #selector(toggleTermsAcceptance), for: .touchUpInside)
  67. termsCheckboxButton.translatesAutoresizingMaskIntoConstraints = false
  68. view.addSubview(termsCheckboxButton)
  69. // Set up terms label with clickable links
  70. termsLabel = UILabel()
  71. termsLabel.font = UIFont.systemFont(ofSize: 12)
  72. termsLabel.textColor = .gray
  73. termsLabel.numberOfLines = 0
  74. termsLabel.translatesAutoresizingMaskIntoConstraints = false
  75. view.addSubview(termsLabel)
  76. // Set up the clickable parts of the text with specific color and links
  77. let fullText = "我已阅读并同意《中国认证服务条款》以及《用户协议》和《隐私政策》"
  78. let attributedText = NSMutableAttributedString(string: fullText)
  79. // Define ranges
  80. let range1 = (fullText as NSString).range(of: "中国认证服务条款")
  81. let range2 = (fullText as NSString).range(of: "用户协议")
  82. let range3 = (fullText as NSString).range(of: "隐私政策")
  83. let linkColor = UIColor(hex: "#FFE059")
  84. // Set color and underline for clickable text
  85. attributedText.addAttribute(.foregroundColor, value: linkColor, range: range1)
  86. attributedText.addAttribute(.foregroundColor, value: linkColor, range: range2)
  87. attributedText.addAttribute(.foregroundColor, value: linkColor, range: range3)
  88. // Add underline style to indicate clickable
  89. attributedText.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range1)
  90. attributedText.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range2)
  91. attributedText.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range3)
  92. // Add link attribute with custom scheme for tap detection
  93. attributedText.addAttribute(.link, value: "terms://service", range: range1)
  94. attributedText.addAttribute(.link, value: "terms://user", range: range2)
  95. attributedText.addAttribute(.link, value: "terms://privacy", range: range3)
  96. // Use UITextView instead of UILabel to support clickable links
  97. let termsTextView = UITextView()
  98. termsTextView.attributedText = attributedText
  99. termsTextView.font = UIFont.systemFont(ofSize: 12)
  100. termsTextView.textColor = .gray
  101. termsTextView.isEditable = false
  102. termsTextView.isScrollEnabled = false
  103. termsTextView.backgroundColor = .clear
  104. termsTextView.translatesAutoresizingMaskIntoConstraints = false
  105. termsTextView.dataDetectorTypes = []
  106. termsTextView.delegate = self
  107. termsTextView.textContainerInset = .zero
  108. termsTextView.textContainer.lineFragmentPadding = 0
  109. termsTextView.linkTextAttributes = [
  110. .foregroundColor: UIColor(hex: "#000000"),
  111. .underlineStyle: NSUnderlineStyle.single.rawValue
  112. ]
  113. view.addSubview(termsTextView)
  114. self.termsLabel = nil // Remove the UILabel reference since using UITextView
  115. // Set up login button
  116. loginButton = UIButton(type: .system)
  117. loginButton.setTitle("登录", for: .normal)
  118. loginButton.backgroundColor = UIColor(hex: "#FFE059")
  119. loginButton.setTitleColor(.black, for: .normal)
  120. loginButton.layer.cornerRadius = 25
  121. loginButton.addTarget(self, action: #selector(login), for: .touchUpInside)
  122. loginButton.translatesAutoresizingMaskIntoConstraints = false
  123. view.addSubview(loginButton)
  124. // Set up constraints
  125. NSLayoutConstraint.activate([
  126. logoImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40),
  127. logoImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
  128. logoImageView.widthAnchor.constraint(equalToConstant: 100),
  129. logoImageView.heightAnchor.constraint(equalToConstant: 100),
  130. phoneNumberTextField.topAnchor.constraint(equalTo: logoImageView.bottomAnchor, constant: 40),
  131. phoneNumberTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
  132. phoneNumberTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
  133. phoneNumberTextField.heightAnchor.constraint(equalToConstant: 40),
  134. verificationCodeTextField.topAnchor.constraint(equalTo: phoneNumberTextField.bottomAnchor, constant: 20),
  135. verificationCodeTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
  136. verificationCodeTextField.trailingAnchor.constraint(equalTo: getVerificationCodeButton.leadingAnchor, constant: -10),
  137. verificationCodeTextField.heightAnchor.constraint(equalToConstant: 40),
  138. getVerificationCodeButton.centerYAnchor.constraint(equalTo: verificationCodeTextField.centerYAnchor),
  139. getVerificationCodeButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
  140. getVerificationCodeButton.widthAnchor.constraint(equalToConstant: 100),
  141. getVerificationCodeButton.heightAnchor.constraint(equalToConstant: 40),
  142. termsCheckboxButton.topAnchor.constraint(equalTo: verificationCodeTextField.bottomAnchor, constant: 20),
  143. termsCheckboxButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
  144. termsCheckboxButton.widthAnchor.constraint(equalToConstant: 20),
  145. termsCheckboxButton.heightAnchor.constraint(equalToConstant: 20),
  146. termsTextView.topAnchor.constraint(equalTo: verificationCodeTextField.bottomAnchor, constant: 20),
  147. termsTextView.leadingAnchor.constraint(equalTo: termsCheckboxButton.trailingAnchor, constant: 10),
  148. termsTextView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
  149. termsTextView.heightAnchor.constraint(greaterThanOrEqualToConstant: 40),
  150. loginButton.topAnchor.constraint(equalTo: termsTextView.bottomAnchor, constant: 30),
  151. loginButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
  152. loginButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
  153. loginButton.heightAnchor.constraint(equalToConstant: 50)
  154. ])
  155. }
  156. @objc func dismissKeyboard() {
  157. // Dismiss the keyboard when tapping outside the text fields
  158. view.endEditing(true)
  159. }
  160. // MARK: - Actions
  161. @objc func getVerificationCode() {
  162. // Logic to handle verification code request
  163. print("获取验证码")
  164. if isCountdownActive {
  165. return // If countdown is already active, do nothing
  166. }
  167. // Validate phone number
  168. guard let phoneNumber = phoneNumberTextField.text, !phoneNumber.isEmpty else {
  169. // Show error if phone number is empty
  170. showError("请输入手机号码")
  171. return
  172. }
  173. guard isValidPhoneNumber(phoneNumber) else {
  174. showAlert(title: "提示", message: "手机号格式不正确")
  175. return
  176. }
  177. // Send POST request to the server with phone number
  178. let url = URL(string: "\(baseURL)/petRecordApUser/sendSmsCode")!
  179. var request = URLRequest(url: url)
  180. request.httpMethod = "POST"
  181. request.setValue("application/json", forHTTPHeaderField: "Content-Type")
  182. let parameters = ["phonenumber": phoneNumber]
  183. request.httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: .fragmentsAllowed)
  184. print("Request URL: \(url)")
  185. print("Request Parameters: \(parameters)")
  186. // Make the network request
  187. URLSession.shared.dataTask(with: request) { data, response, error in
  188. if let error = error {
  189. DispatchQueue.main.async {
  190. self.showError("请求失败: \(error.localizedDescription)")
  191. }
  192. return
  193. }
  194. // Log the response status code
  195. if let response = response as? HTTPURLResponse {
  196. print("Response Status Code: \(response.statusCode)")
  197. }
  198. // Handle the response here (e.g., check the status code or response)
  199. DispatchQueue.main.async {
  200. if let data = data {
  201. print("ssss \(data)")
  202. // Example response handling (customize this part as per your API response)
  203. if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
  204. // 验证码发送成功
  205. if let dataDict = json["data"] as? [String: Any],
  206. let uuid = dataDict["uuid"] as? String {
  207. print("验证码发送成功,UUID: \(uuid)")
  208. // 保存uuid到属性中
  209. self.uuid = uuid
  210. // Start the countdown
  211. self.startCountdown()
  212. } else {
  213. self.uuid = "uuidstring"
  214. let errorMsg = json["msg"] as? String ?? "登录失败,未知错误"
  215. self.showAlert(title: "发送失败", message: errorMsg)
  216. }
  217. // Print the entire JSON object
  218. print("Response JSON: \(json)")
  219. // If you want to print specific values from the JSON, you can access them like this:
  220. // if let uuid = json["uuid"] as? String {
  221. // print("UUID received: \(uuid)")
  222. // self.uuid = uuid
  223. // }
  224. // You can also print other parts of the response
  225. if let message = json["msg"] as? String {
  226. print("Server Message: \(message)")
  227. }
  228. } }
  229. }
  230. }.resume()
  231. }
  232. @objc private func loginTapped() {
  233. guard let phone = phoneNumberTextField.text, !phone.isEmpty else {
  234. showAlert(title: "提示", message: "请输入手机号")
  235. return
  236. }
  237. guard isValidPhoneNumber(phone) else {
  238. showAlert(title: "提示", message: "手机号格式不正确")
  239. return
  240. }
  241. guard let code = verificationCodeTextField.text, !code.isEmpty else {
  242. showAlert(title: "提示", message: "请输入验证码")
  243. return
  244. }
  245. // Ensure UUID is available (it should be set when the SMS code was requested)
  246. guard let uuid = self.uuid else {
  247. showAlert(title: "错误", message: "请先获取验证码")
  248. return
  249. }
  250. // Prepare the request parameters
  251. let parameters: [String: Any] = [
  252. "phonenumber": phone,
  253. "smsCode": code,
  254. "uuid": uuid // Pass the uuid received during the SMS request
  255. ]
  256. // Create the URL for the login API
  257. guard let url = URL(string: "\(baseURL)/petRecordApUser/phoneLogin") else {
  258. showAlert(title: "错误", message: "无效的URL")
  259. return
  260. }
  261. // Create the request
  262. var request = URLRequest(url: url)
  263. request.httpMethod = "POST"
  264. request.setValue("application/json", forHTTPHeaderField: "Content-Type")
  265. // Log the request URL and parameters
  266. print("Login Request URL: \(url)")
  267. print("Login Request Parameters: \(parameters)")
  268. // Add the JSON body to the request
  269. do {
  270. request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: [])
  271. } catch {
  272. showAlert(title: "错误", message: "参数编码失败: \(error.localizedDescription)")
  273. return
  274. }
  275. // Show activity indicator while making the network request
  276. let activityIndicator = UIActivityIndicatorView(style: .medium)
  277. activityIndicator.center = view.center
  278. view.addSubview(activityIndicator)
  279. activityIndicator.startAnimating()
  280. // Send the request using URLSession
  281. let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
  282. // Ensure UI updates happen on the main thread
  283. DispatchQueue.main.async {
  284. activityIndicator.stopAnimating()
  285. activityIndicator.removeFromSuperview()
  286. guard let self = self else { return }
  287. if let error = error {
  288. self.showAlert(title: "网络错误", message: error.localizedDescription)
  289. return
  290. }
  291. guard let data = data else {
  292. self.showAlert(title: "错误", message: "未收到响应数据")
  293. return
  294. }
  295. // 解析JSON响应
  296. // In the response handler for the login request:
  297. do {
  298. if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
  299. // Check the response code
  300. if let code = json["code"] as? String, code == "200" {
  301. // Login successful
  302. print("登录成功: \(json)")
  303. UserDefaults.standard.set(true, forKey: "isLogggedIn")
  304. // Check if the token is present in the data
  305. if let dataDict = json["data"] as? [String: Any], let token = dataDict["token"] as? String {
  306. // Save the token and proceed with login
  307. print("Token received: \(token)")
  308. UserDefaults.standard.set(token, forKey: "userToken") // Store the token
  309. // Navigate to the main interface
  310. let mainVC = HomeViewController() // Replace with your actual main screen controller
  311. self.navigationController?.setViewControllers([mainVC], animated: true)
  312. } else {
  313. // Handle missing token error
  314. self.showAlert(title: "错误", message: "Token 不存在")
  315. }
  316. } else {
  317. // Log the error message from the server if code isn't 200
  318. print("登录失败: \(json)")
  319. if let msg = json["msg"] as? String {
  320. print("Login failed with message: \(msg)")
  321. self.showAlert(title: "登录失败", message: msg)
  322. } else {
  323. // Handle case where msg is not returned
  324. self.showAlert(title: "登录失败", message: "未知错误")
  325. }
  326. }
  327. } else {
  328. // Handle invalid JSON response format
  329. self.showAlert(title: "错误", message: "无效的响应格式")
  330. }
  331. } catch {
  332. // Handle JSON parsing errors
  333. self.showAlert(title: "解析错误", message: "无法解析响应: \(error.localizedDescription)")
  334. }
  335. }
  336. }
  337. task.resume()
  338. }
  339. func isValidPhoneNumber(_ phoneNumber: String) -> Bool {
  340. let phoneRegex = "^1[3-9]\\d{9}$" // This is a basic regex for validating Chinese phone numbers
  341. let phoneTest = NSPredicate(format: "SELF MATCHES %@", phoneRegex)
  342. return phoneTest.evaluate(with: phoneNumber)
  343. }
  344. func showAlert(title: String, message: String) {
  345. let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
  346. alert.addAction(UIAlertAction(title: "确定", style: .default, handler: nil))
  347. present(alert, animated: true, completion: nil)
  348. }
  349. func startCountdown() {
  350. // Disable the button and start the countdown
  351. isCountdownActive = true
  352. getVerificationCodeButton.isEnabled = false
  353. countdownTimer?.invalidate() // Invalidate previous timer if any
  354. remainingTime = 60
  355. updateButtonTitle()
  356. // Create a new timer to update the button every second
  357. countdownTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateCountdown), userInfo: nil, repeats: true)
  358. }
  359. @objc func updateCountdown() {
  360. remainingTime -= 1
  361. updateButtonTitle()
  362. if remainingTime <= 0 {
  363. // Countdown is complete, reset everything
  364. countdownTimer?.invalidate()
  365. isCountdownActive = false
  366. getVerificationCodeButton.isEnabled = true
  367. getVerificationCodeButton.setTitle("获取验证码", for: .normal)
  368. }
  369. }
  370. func updateButtonTitle() {
  371. let title = "重新获取 (\(remainingTime)s)"
  372. getVerificationCodeButton.setTitle(title, for: .normal)
  373. }
  374. func showError(_ message: String) {
  375. let alert = UIAlertController(title: "提示", message: message, preferredStyle: .alert)
  376. alert.addAction(UIAlertAction(title: "确定", style: .default, handler: nil))
  377. present(alert, animated: true, completion: nil)
  378. }
  379. @objc func toggleTermsAcceptance() {
  380. // Toggle the acceptance of terms
  381. isTermsAccepted.toggle()
  382. termsCheckboxButton.isSelected = isTermsAccepted
  383. }
  384. @objc func login() {
  385. // Logic to handle login
  386. if !isTermsAccepted {
  387. showCustomAlert()
  388. } else {
  389. // Proceed with login if terms are accepted
  390. print("登录")
  391. loginTapped()
  392. }
  393. }
  394. // MARK: - Custom Alert
  395. func showCustomAlert() {
  396. // Create a custom alert background (semi-transparent)
  397. alertBackground = UIView(frame: view.bounds)
  398. alertBackground.backgroundColor = UIColor.black.withAlphaComponent(0.5)
  399. alertBackground.translatesAutoresizingMaskIntoConstraints = false
  400. view.addSubview(alertBackground)
  401. // Create the alert view (popup window)
  402. customAlertView = UIView()
  403. customAlertView.backgroundColor = .white
  404. customAlertView.layer.cornerRadius = 10
  405. customAlertView.translatesAutoresizingMaskIntoConstraints = false
  406. view.addSubview(customAlertView)
  407. // Add title label
  408. let titleLabel = UILabel()
  409. titleLabel.text = "温馨提示"
  410. titleLabel.font = UIFont.boldSystemFont(ofSize: 18)
  411. titleLabel.textAlignment = .center
  412. titleLabel.translatesAutoresizingMaskIntoConstraints = false
  413. customAlertView.addSubview(titleLabel)
  414. // Add message label with colored text
  415. let alertFullText = "已阅读并同意《用户协议》和《隐私政策》"
  416. let alertAttributedText = NSMutableAttributedString(string: alertFullText)
  417. // Set all text to gray first
  418. alertAttributedText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: 0, length: alertFullText.count))
  419. // Find and set black color for "用户协议" and "隐私政策"
  420. let alertRange1 = (alertFullText as NSString).range(of: "用户协议")
  421. let alertRange2 = (alertFullText as NSString).range(of: "隐私政策")
  422. alertAttributedText.addAttribute(.foregroundColor, value: UIColor.black, range: alertRange1)
  423. alertAttributedText.addAttribute(.foregroundColor, value: UIColor.black, range: alertRange2)
  424. let messageLabel = UILabel()
  425. messageLabel.attributedText = alertAttributedText
  426. messageLabel.font = UIFont.systemFont(ofSize: 14)
  427. messageLabel.textAlignment = .center
  428. messageLabel.numberOfLines = 0
  429. messageLabel.translatesAutoresizingMaskIntoConstraints = false
  430. customAlertView.addSubview(messageLabel)
  431. // Add Agree Button
  432. let agreeButton = UIButton(type: .system)
  433. agreeButton.setTitle("同意并登录", for: .normal)
  434. agreeButton.backgroundColor = UIColor(hex: "#FFE059")
  435. agreeButton.setTitleColor(.black, for: .normal)
  436. agreeButton.layer.cornerRadius = 20
  437. agreeButton.addTarget(self, action: #selector(agreeAction), for: .touchUpInside)
  438. agreeButton.translatesAutoresizingMaskIntoConstraints = false
  439. customAlertView.addSubview(agreeButton)
  440. // Add Disagree Button
  441. let disagreeButton = UIButton(type: .system)
  442. disagreeButton.setTitle("不同意", for: .normal)
  443. disagreeButton.setTitleColor(.gray, for: .normal)
  444. disagreeButton.addTarget(self, action: #selector(disagreeAction), for: .touchUpInside)
  445. disagreeButton.translatesAutoresizingMaskIntoConstraints = false
  446. customAlertView.addSubview(disagreeButton)
  447. // Set up constraints for alert view
  448. NSLayoutConstraint.activate([
  449. // Alert background (full screen, semi-transparent)
  450. alertBackground.topAnchor.constraint(equalTo: view.topAnchor),
  451. alertBackground.leadingAnchor.constraint(equalTo: view.leadingAnchor),
  452. alertBackground.trailingAnchor.constraint(equalTo: view.trailingAnchor),
  453. alertBackground.bottomAnchor.constraint(equalTo: view.bottomAnchor),
  454. // Custom alert view (popup)
  455. customAlertView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
  456. customAlertView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
  457. customAlertView.widthAnchor.constraint(equalToConstant: 300),
  458. customAlertView.heightAnchor.constraint(equalToConstant: 180),
  459. // Title label
  460. titleLabel.topAnchor.constraint(equalTo: customAlertView.topAnchor, constant: 20),
  461. titleLabel.leadingAnchor.constraint(equalTo: customAlertView.leadingAnchor, constant: 20),
  462. titleLabel.trailingAnchor.constraint(equalTo: customAlertView.trailingAnchor, constant: -20),
  463. // Message label
  464. messageLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 10),
  465. messageLabel.leadingAnchor.constraint(equalTo: customAlertView.leadingAnchor, constant: 20),
  466. messageLabel.trailingAnchor.constraint(equalTo: customAlertView.trailingAnchor, constant: -20),
  467. // Agree button
  468. agreeButton.topAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 20),
  469. agreeButton.leadingAnchor.constraint(equalTo: customAlertView.leadingAnchor, constant: 20),
  470. agreeButton.trailingAnchor.constraint(equalTo: customAlertView.trailingAnchor, constant: -20),
  471. agreeButton.heightAnchor.constraint(equalToConstant: 40),
  472. // Disagree button
  473. disagreeButton.topAnchor.constraint(equalTo: agreeButton.bottomAnchor, constant: 10),
  474. disagreeButton.leadingAnchor.constraint(equalTo: customAlertView.leadingAnchor, constant: 20),
  475. disagreeButton.trailingAnchor.constraint(equalTo: customAlertView.trailingAnchor, constant: -20),
  476. disagreeButton.heightAnchor.constraint(equalToConstant: 40)
  477. ])
  478. }
  479. @objc func agreeAction() {
  480. // Handle the "Agree" action (e.g., proceed with login)
  481. print("用户同意协议,继续登录")
  482. customAlertView.removeFromSuperview()
  483. alertBackground.removeFromSuperview()
  484. loginTapped()
  485. }
  486. @objc func disagreeAction() {
  487. // Handle the "Disagree" action (e.g., close the alert)
  488. print("用户不同意协议,取消登录")
  489. customAlertView.removeFromSuperview()
  490. alertBackground.removeFromSuperview()
  491. }
  492. }
  493. extension LoginViewController: UITextViewDelegate {
  494. func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
  495. var urlString = ""
  496. switch URL.absoluteString {
  497. case "terms://service":
  498. urlString = "https://ytwljs.github.io/gl-us.html"
  499. case "terms://user":
  500. urlString = "https://ytwljs.github.io/gl-user.html"
  501. case "terms://privacy":
  502. urlString = "https://ytwljs.github.io/gl-policy.html"
  503. default:
  504. return false
  505. }
  506. let webVC = WebViewController()
  507. webVC.urlString = urlString
  508. navigationController?.pushViewController(webVC, animated: true)
  509. return false
  510. }
  511. }
  512. class WebViewController: UIViewController, WKNavigationDelegate {
  513. var urlString: String?
  514. var webView: WKWebView!
  515. override func viewDidLoad() {
  516. super.viewDidLoad()
  517. view.backgroundColor = .white
  518. webView = WKWebView(frame: view.bounds)
  519. webView.navigationDelegate = self
  520. webView.translatesAutoresizingMaskIntoConstraints = false
  521. view.addSubview(webView)
  522. NSLayoutConstraint.activate([
  523. webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
  524. webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
  525. webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
  526. webView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
  527. ])
  528. if let urlString = urlString, let url = URL(string: urlString) {
  529. let request = URLRequest(url: url)
  530. webView.load(request)
  531. }
  532. }
  533. }
  534. extension UIColor {
  535. convenience init(hex: String) {
  536. var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
  537. hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
  538. var rgb: UInt64 = 0
  539. Scanner(string: hexSanitized).scanHexInt64(&rgb)
  540. self.init(
  541. red: CGFloat((rgb & 0xFF0000) >> 16) / 255.0,
  542. green: CGFloat((rgb & 0x00FF00) >> 8) / 255.0,
  543. blue: CGFloat(rgb & 0x0000FF) / 255.0,
  544. alpha: 1.0
  545. )
  546. }
  547. }