LoginViewController.swift 31 KB

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