| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721 |
- //
- // VKNimbusPanelController.swift (obfuscated from UserSettingsViewController)
- // VenusKitto
- //
- // Obfuscated on 2025/08/27
- //
- import Foundation
- import UIKit
- // 外部兼容:仍然可以用旧名引用
- typealias UserSettingsViewController = VKNimbusPanelController
- final class VKNimbusPanelController: UIViewController {
- // MARK: - UI (obfuscated names)
- private var zxAvatar: UIImage?
- private var zxNick: String = "未设置"
- private let grid: UITableView = {
- let t = UITableView(frame: .zero, style: .grouped)
- t.backgroundColor = .white
- t.separatorStyle = .none
- t.showsVerticalScrollIndicator = false
- t.translatesAutoresizingMaskIntoConstraints = false
- return t
- }()
- private let exitBtn: UIButton = {
- let b = UIButton(type: .system)
- b.setTitle("退出登录", for: .normal)
- b.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
- b.setTitleColor(.black, for: .normal)
- b.backgroundColor = UIColor(hex: "#FFE059")
- b.layer.cornerRadius = 25
- b.translatesAutoresizingMaskIntoConstraints = false
- return b
- }()
- private let nukeBtn: UIButton = {
- let b = UIButton(type: .system)
- b.setTitle("注销用户", for: .normal)
- b.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
- b.setTitleColor(UIColor(hex: "#34260C"), for: .normal)
- b.backgroundColor = .clear
- b.translatesAutoresizingMaskIntoConstraints = false
- return b
- }()
- // 数据源(标题 + 默认值/占位资源名)
- private let entries: [(String, String)] = [
- ("头像修改", "Home372"),
- ("昵称修改", "未设置")
- ]
- // MARK: - Lifecycle
- override func viewDidLoad() {
- super.viewDidLoad()
- forgeUI()
- pinAutoLayout()
- primeTableKit()
- bindActions()
- navigationItem.title = "用户设置"
- navigationController?.navigationBar.tintColor = .black
- navigationItem.leftBarButtonItem = UIBarButtonItem(
- image: UIImage(systemName: "chevron.left"),
- style: .plain,
- target: self,
- action: #selector(ax_back)
- )
- }
- override func viewWillAppear(_ animated: Bool) {
- super.viewWillAppear(animated)
- // 头像
- if let icon = UserDefaults.standard.string(forKey: "memberIcon"),
- !icon.isEmpty, let url = URL(string: icon) {
- print("[Settings] pull avatar: \(url.absoluteString)")
- DispatchQueue.global().async { [weak self] in
- if let data = try? Data(contentsOf: url), let img = UIImage(data: data) {
- DispatchQueue.main.async { self?.zxAvatar = img; self?.grid.reloadData() }
- }
- }
- } else {
- zxAvatar = nil
- grid.reloadData()
- }
- // 昵称
- zxNick = UserDefaults.standard.string(forKey: "memberName") ?? "未设置"
- grid.reloadData()
- navigationController?.setNavigationBarHidden(false, animated: animated)
- }
- // MARK: - Build UI
- private func forgeUI() {
- view.backgroundColor = .white
- view.addSubview(grid)
- view.addSubview(exitBtn)
- view.addSubview(nukeBtn)
- }
- private func pinAutoLayout() {
- NSLayoutConstraint.activate([
- grid.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10),
- grid.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
- grid.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
- grid.heightAnchor.constraint(equalToConstant: 150),
- nukeBtn.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10),
- nukeBtn.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
- nukeBtn.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
- nukeBtn.heightAnchor.constraint(equalToConstant: 50),
- exitBtn.bottomAnchor.constraint(equalTo: nukeBtn.topAnchor, constant: -20),
- exitBtn.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
- exitBtn.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
- exitBtn.heightAnchor.constraint(equalToConstant: 50)
- ])
- }
- private func primeTableKit() {
- grid.register(VXSettingCell.self, forCellReuseIdentifier: VXSettingCell.reuseId)
- grid.dataSource = self
- grid.delegate = self
- grid.isScrollEnabled = false
- grid.layer.cornerRadius = 8
- }
- private func bindActions() {
- exitBtn.addTarget(self, action: #selector(ax_logout), for: .touchUpInside)
- nukeBtn.addTarget(self, action: #selector(ax_deleteAccount), for: .touchUpInside)
- }
- // MARK: - Navigation
- @objc private func ax_back() {
- if presentingViewController != nil { dismiss(animated: true) }
- else { navigationController?.popViewController(animated: true) }
- }
- // MARK: - Actions
- @objc private func ax_logout() { askLogout() }
- @objc private func ax_deleteAccount() { askDestruct() }
- private func askLogout() {
- let ac = UIAlertController(title: "退出登录", message: "确定要退出当前账号吗?", preferredStyle: .alert)
- ac.addAction(UIAlertAction(title: "取消", style: .cancel))
- ac.addAction(UIAlertAction(title: "确定", style: .destructive) { _ in self.doLogout() })
- present(ac, animated: true)
- }
- private func askDestruct() {
- let ac = UIAlertController(title: "注销账户", message: "此操作将永久删除您的账户和所有数据,确定要继续吗?", preferredStyle: .alert)
- ac.addAction(UIAlertAction(title: "取消", style: .cancel))
- ac.addAction(UIAlertAction(title: "注销", style: .destructive) { _ in self.doErase() })
- present(ac, animated: true)
- }
- private func doLogout() {
- UserDefaults.standard.set(false, forKey: "isLogggedIn")
- UserDefaults.standard.removeObject(forKey: "userToken")
- vx_toast("已退出登录")
- DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { self.routeToLogin() }
- }
- private func doErase() {
- vx_toast("账户已注销")
- DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { self.routeToLogin() }
- }
- private func routeToLogin() {
- let loginVC = LoginViewController()
- let nav = UINavigationController(rootViewController: loginVC)
- guard let window = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) else { return }
- window.rootViewController = nav
- UIView.transition(with: window, duration: 0.5, options: .transitionCrossDissolve, animations: nil)
- }
- }
- // MARK: - Table
- extension VKNimbusPanelController: UITableViewDataSource, UITableViewDelegate {
- func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { entries.count }
- func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
- let cell = tableView.dequeueReusableCell(withIdentifier: VXSettingCell.reuseId, for: indexPath) as! VXSettingCell
- let (title, val) = entries[indexPath.row]
- if indexPath.row == 0 {
- cell.bind(title: title,
- value: "",
- isFirst: indexPath.row == 0,
- isLast: indexPath.row == entries.count - 1,
- avatar: zxAvatar,
- placeholder: val)
- } else {
- cell.bind(title: title,
- value: zxNick,
- isFirst: indexPath.row == 0,
- isLast: indexPath.row == entries.count - 1,
- avatar: nil,
- placeholder: val)
- }
- return cell
- }
- func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { indexPath.row == 0 ? 80 : 60 }
- func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { UIView() }
- func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 0.1 }
- func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { UIView() }
- func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { 0.1 }
- func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
- tableView.deselectRow(at: indexPath, animated: true)
- switch indexPath.row {
- case 0: popAvatarPanel()
- case 1: presentNicknamePad()
- default: break
- }
- }
- }
- // MARK: - Avatar
- extension VKNimbusPanelController: UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIPopoverPresentationControllerDelegate {
- private func popAvatarPanel() {
- let ac = UIAlertController(title: "更换头像", message: nil, preferredStyle: .actionSheet)
- ac.modalPresentationStyle = .popover
- if let p = ac.popoverPresentationController {
- p.delegate = self
- let idx = IndexPath(row: 0, section: 0)
- if let cell = grid.cellForRow(at: idx) {
- p.sourceView = cell.contentView; p.sourceRect = cell.contentView.bounds
- } else {
- p.sourceView = view; p.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.maxY - 1, width: 1, height: 1)
- }
- p.permittedArrowDirections = []
- }
- if UIImagePickerController.isSourceTypeAvailable(.camera) {
- let a = UIAlertAction(title: "拍照", style: .default) { [weak self] _ in self?.summonCamera() }
- a.setValue(UIColor.black, forKey: "titleTextColor"); ac.addAction(a)
- }
- let b = UIAlertAction(title: "从相册选择", style: .default) { [weak self] _ in self?.summonLibrary() }
- b.setValue(UIColor.black, forKey: "titleTextColor"); ac.addAction(b)
- let c = UIAlertAction(title: "取消", style: .cancel)
- c.setValue(UIColor.black, forKey: "titleTextColor"); ac.addAction(c)
- present(ac, animated: true)
- }
- private func summonCamera() {
- let p = UIImagePickerController()
- p.delegate = self
- p.sourceType = .camera
- p.allowsEditing = true
- present(p, animated: true)
- }
- private func summonLibrary() {
- let p = UIImagePickerController()
- p.delegate = self
- p.sourceType = .photoLibrary
- p.allowsEditing = true
- present(p, animated: true)
- }
- func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
- picker.dismiss(animated: true)
- guard let img = info[.editedImage] as? UIImage else { return }
- showHud()
- guard let data = squash(img) else { hideHud(); vx_toast("图片处理失败"); return }
- pushBlob(data) { [weak self] result in
- DispatchQueue.main.async {
- self?.hideHud()
- switch result {
- case .success(let url):
- self?.zxAvatar = img
- self?.grid.reloadData()
- self?.syncAvatarUrl(url)
- case .failure(let e):
- self?.raiseOops("上传失败: \(e.localizedDescription)")
- }
- }
- }
- }
- func prepareForPopoverPresentation(_ popoverPresentationController: UIPopoverPresentationController) {
- if popoverPresentationController.sourceView == nil && popoverPresentationController.barButtonItem == nil {
- popoverPresentationController.sourceView = view
- popoverPresentationController.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.maxY - 1, width: 1, height: 1)
- popoverPresentationController.permittedArrowDirections = []
- }
- }
- }
- // MARK: - Nickname
- extension VKNimbusPanelController {
- private func presentNicknamePad() {
- let mask = UIView(frame: view.bounds)
- mask.backgroundColor = UIColor.black.withAlphaComponent(0.5)
- mask.alpha = 0; mask.tag = 1001
- view.addSubview(mask)
- let panel = UIView(); panel.backgroundColor = .white; panel.layer.cornerRadius = 12; panel.translatesAutoresizingMaskIntoConstraints = false
- mask.addSubview(panel)
- let title = UILabel(); title.text = "修改昵称"; title.font = .systemFont(ofSize: 16, weight: .medium); title.textAlignment = .center; title.translatesAutoresizingMaskIntoConstraints = false
- let tf = UITextField(); tf.placeholder = "请输入昵称"; tf.font = .systemFont(ofSize: 16); tf.borderStyle = .roundedRect; tf.clearButtonMode = .whileEditing; tf.text = (zxNick == "未设置" ? "" : zxNick); tf.translatesAutoresizingMaskIntoConstraints = false
- let sep = UIView(); sep.backgroundColor = UIColor(hex: "#EEEEEE"); sep.translatesAutoresizingMaskIntoConstraints = false
- let cancel = UIButton(type: .system); cancel.setTitle("取消", for: .normal); cancel.setTitleColor(UIColor(hex: "#999999"), for: .normal); cancel.translatesAutoresizingMaskIntoConstraints = false
- let ok = UIButton(type: .system); ok.setTitle("完成", for: .normal); ok.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium); ok.setTitleColor(.black, for: .normal); ok.translatesAutoresizingMaskIntoConstraints = false
- panel.addSubview(title); panel.addSubview(tf); panel.addSubview(sep); panel.addSubview(cancel); panel.addSubview(ok)
- NSLayoutConstraint.activate([
- panel.centerXAnchor.constraint(equalTo: mask.centerXAnchor),
- panel.centerYAnchor.constraint(equalTo: mask.centerYAnchor),
- panel.widthAnchor.constraint(equalToConstant: 280),
- panel.heightAnchor.constraint(equalToConstant: 180),
- title.topAnchor.constraint(equalTo: panel.topAnchor, constant: 20),
- title.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 20),
- title.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -20),
- tf.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 20),
- tf.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 20),
- tf.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -20),
- tf.heightAnchor.constraint(equalToConstant: 40),
- sep.topAnchor.constraint(equalTo: tf.bottomAnchor, constant: 20),
- sep.leadingAnchor.constraint(equalTo: panel.leadingAnchor),
- sep.trailingAnchor.constraint(equalTo: panel.trailingAnchor),
- sep.heightAnchor.constraint(equalToConstant: 0.5),
- cancel.topAnchor.constraint(equalTo: sep.bottomAnchor),
- cancel.leadingAnchor.constraint(equalTo: panel.leadingAnchor),
- cancel.trailingAnchor.constraint(equalTo: panel.centerXAnchor),
- cancel.bottomAnchor.constraint(equalTo: panel.bottomAnchor),
- cancel.heightAnchor.constraint(equalToConstant: 50),
- ok.topAnchor.constraint(equalTo: sep.bottomAnchor),
- ok.leadingAnchor.constraint(equalTo: panel.centerXAnchor),
- ok.trailingAnchor.constraint(equalTo: panel.trailingAnchor),
- ok.bottomAnchor.constraint(equalTo: panel.bottomAnchor),
- ok.heightAnchor.constraint(equalToConstant: 50)
- ])
- cancel.addTarget(self, action: #selector(x_dismissOverlay), for: .touchUpInside)
- ok.addTarget(self, action: #selector(x_commitNickname), for: .touchUpInside)
- mask.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(x_dismissOverlay)))
- UIView.animate(withDuration: 0.3) { mask.alpha = 1 }
- tf.becomeFirstResponder()
- }
- @objc private func x_dismissOverlay() {
- if let mask = view.viewWithTag(1001) {
- UIView.animate(withDuration: 0.2, animations: { mask.alpha = 0 }) { _ in mask.removeFromSuperview() }
- }
- }
- @objc private func x_commitNickname() {
- guard let mask = view.viewWithTag(1001),
- let panel = mask.subviews.first,
- let tf = panel.subviews.first(where: { $0 is UITextField }) as? UITextField,
- let text = tf.text, !text.isEmpty else {
- vx_toast("昵称不能为空")
- return
- }
- rx_updateNick(text)
- x_dismissOverlay()
- }
- }
- // MARK: - Networking helpers
- extension VKNimbusPanelController {
- private func squash(_ image: UIImage) -> Data? {
- let target = CGSize(width: 800, height: 800)
- return image.x_resize(to: target)?.jpegData(compressionQuality: 0.7)
- }
- private func showHud() {
- let indicator = UIActivityIndicatorView(style: .large)
- indicator.color = .white
- indicator.startAnimating()
- let bg = UIView(frame: view.bounds)
- bg.backgroundColor = UIColor.black.withAlphaComponent(0.5)
- bg.tag = 1002
- bg.addSubview(indicator)
- indicator.center = bg.center
- view.addSubview(bg)
- }
- private func hideHud() { view.viewWithTag(1002)?.removeFromSuperview() }
- private func pushBlob(_ imageData: Data, completion: @escaping (Result<String, Error>) -> Void) {
- guard let token = UserDefaults.standard.string(forKey: "userToken") else {
- completion(.failure(NSError(domain: "AuthError", code: 401, userInfo: [NSLocalizedDescriptionKey: "未找到用户凭证"])))
- return
- }
- guard let url = URL(string: "\(baseURL)/common/upload") else {
- completion(.failure(NSError(domain: "URLError", code: 400, userInfo: [NSLocalizedDescriptionKey: "无效的服务器地址"])))
- return
- }
- var req = URLRequest(url: url)
- req.httpMethod = "POST"
- req.setValue(token, forHTTPHeaderField: "Authorization")
- let boundary = "Boundary-\(UUID().uuidString)"
- req.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
- var body = Data()
- body.append("--\(boundary)\r\n".data(using: .utf8)!)
- body.append("Content-Disposition: form-data; name=\"file\"; filename=\"avatar.jpg\"\r\n".data(using: .utf8)!)
- body.append("Content-Type: image/jpeg\r\n\r\n".data(using: .utf8)!)
- body.append(imageData)
- body.append("\r\n".data(using: .utf8)!)
- body.append("--\(boundary)--\r\n".data(using: .utf8)!)
- req.httpBody = body
- dbg_emitRequest(name: "上传头像(文件)", request: req, body: body)
- URLSession.shared.dataTask(with: req) { [weak self] data, response, error in
- self?.dbg_emitResponse(name: "上传头像(文件)", data: data, response: response, error: error)
- if let error = error { completion(.failure(error)); return }
- guard let http = response as? HTTPURLResponse, (200...299).contains(http.statusCode), let data = data else {
- completion(.failure(NSError(domain: "ServerError", code: 500, userInfo: [NSLocalizedDescriptionKey: "服务器返回错误"])))
- return
- }
- do {
- if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
- let codeStr: String = (json["code"] as? String) ?? ((json["code"] as? Int).map { String($0) }),
- codeStr == "200",
- let dataDict = json["data"] as? [String: Any],
- let imageUrl = dataDict["url"] as? String {
- completion(.success(imageUrl))
- } else {
- completion(.failure(NSError(domain: "ParseError", code: 500, userInfo: [NSLocalizedDescriptionKey: "响应解析失败"])))
- }
- } catch {
- completion(.failure(error))
- }
- }.resume()
- }
- private func syncAvatarUrl(_ imageUrl: String) {
- guard let token = UserDefaults.standard.string(forKey: "userToken") else { raiseOops("未找到用户凭证"); return }
- guard let url = URL(string: "\(baseURL)/petRecordApUser/modifyMemberIcon") else { raiseOops("无效的服务器地址"); return }
- var req = URLRequest(url: url)
- req.httpMethod = "POST"
- req.setValue("application/json", forHTTPHeaderField: "Content-Type")
- req.setValue(token, forHTTPHeaderField: "Authorization")
- let payload: [String: Any] = ["memberIcon": imageUrl]
- do {
- req.httpBody = try JSONSerialization.data(withJSONObject: payload)
- dbg_emitRequest(name: "更新头像URL", request: req, body: req.httpBody)
- } catch { raiseOops("请求创建失败"); return }
- showHud()
- URLSession.shared.dataTask(with: req) { [weak self] data, response, error in
- DispatchQueue.main.async {
- self?.dbg_emitResponse(name: "更新头像URL", data: data, response: response, error: error)
- self?.hideHud()
- if let error = error { self?.raiseOops("更新失败: \(error.localizedDescription)"); return }
- guard let http = response as? HTTPURLResponse, (200...299).contains(http.statusCode), let data = data else { self?.raiseOops("服务器返回错误"); return }
- do {
- if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
- let codeStr: String = (json["code"] as? String) ?? ((json["code"] as? Int).map { String($0) }),
- codeStr == "200" {
- self?.vx_toast("头像更新成功")
- UserDefaults.standard.set(imageUrl, forKey: "memberIcon")
- NotificationCenter.default.post(name: Notification.Name("UserAvatarUpdated"), object: imageUrl)
- } else {
- self?.raiseOops("更新失败")
- }
- } catch {
- self?.raiseOops("响应解析失败")
- }
- }
- }.resume()
- }
- private func rx_updateNick(_ newNickname: String) {
- guard let token = UserDefaults.standard.string(forKey: "userToken") else { raiseOops("未找到用户凭证"); return }
- guard let url = URL(string: "\(baseURL)/petRecordApUser/modifyMemberName") else { raiseOops("无效的服务器地址"); return }
- var req = URLRequest(url: url)
- req.httpMethod = "POST"
- req.setValue("application/json", forHTTPHeaderField: "Content-Type")
- req.setValue(token, forHTTPHeaderField: "Authorization")
- let payload: [String: Any] = ["memberName": newNickname]
- do {
- req.httpBody = try JSONSerialization.data(withJSONObject: payload)
- dbg_emitRequest(name: "修改昵称", request: req, body: req.httpBody)
- } catch { raiseOops("请求创建失败"); return }
- showHud()
- URLSession.shared.dataTask(with: req) { [weak self] data, response, error in
- DispatchQueue.main.async {
- self?.dbg_emitResponse(name: "修改昵称", data: data, response: response, error: error)
- self?.hideHud()
- if let error = error { self?.raiseOops("更新失败: \(error.localizedDescription)"); return }
- guard let http = response as? HTTPURLResponse, (200...299).contains(http.statusCode), let data = data else { self?.raiseOops("服务器返回错误"); return }
- do {
- if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
- let codeStr: String = (json["code"] as? String) ?? ((json["code"] as? Int).map { String($0) }),
- codeStr == "200" {
- self?.zxNick = newNickname
- UserDefaults.standard.set(newNickname, forKey: "memberName")
- NotificationCenter.default.post(name: Notification.Name("UserNicknameUpdated"), object: newNickname)
- self?.grid.reloadData()
- self?.vx_toast("昵称修改成功")
- } else {
- self?.raiseOops("更新失败")
- }
- } catch {
- self?.raiseOops("响应解析失败")
- }
- }
- }.resume()
- }
- }
- // MARK: - Cell (obfuscated)
- final class VXSettingCell: UITableViewCell {
- static let reuseId = "vx.setting"
- private let tLabel: UILabel = {
- let l = UILabel()
- l.font = .systemFont(ofSize: 16, weight: .medium)
- l.textColor = .gray
- l.translatesAutoresizingMaskIntoConstraints = false
- return l
- }()
- private let vLabel: UILabel = {
- let l = UILabel()
- l.font = .systemFont(ofSize: 16, weight: .medium)
- l.textColor = UIColor(hex: "#999999")
- l.translatesAutoresizingMaskIntoConstraints = false
- return l
- }()
- private let head: UIImageView = {
- let iv = UIImageView()
- iv.contentMode = .scaleAspectFill
- iv.layer.cornerRadius = 25
- iv.clipsToBounds = true
- iv.backgroundColor = UIColor(hex: "#F0F0F0")
- iv.translatesAutoresizingMaskIntoConstraints = false
- return iv
- }()
- private let arrow: UIImageView = {
- let iv = UIImageView()
- iv.image = UIImage(systemName: "chevron.right")
- iv.tintColor = UIColor(hex: "#000000")
- iv.translatesAutoresizingMaskIntoConstraints = false
- return iv
- }()
- private let line: UIView = {
- let v = UIView()
- v.backgroundColor = UIColor(hex: "#EEEEEE")
- v.translatesAutoresizingMaskIntoConstraints = false
- return v
- }()
- override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
- super.init(style: style, reuseIdentifier: reuseIdentifier)
- contentView.backgroundColor = .white
- selectionStyle = .none
- contentView.addSubview(tLabel)
- contentView.addSubview(vLabel)
- contentView.addSubview(arrow)
- contentView.addSubview(line)
- contentView.addSubview(head)
- NSLayoutConstraint.activate([
- tLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
- tLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
- arrow.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
- arrow.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
- arrow.widthAnchor.constraint(equalToConstant: 12),
- arrow.heightAnchor.constraint(equalToConstant: 18),
- vLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
- vLabel.trailingAnchor.constraint(equalTo: arrow.leadingAnchor, constant: -8),
- head.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
- head.trailingAnchor.constraint(equalTo: arrow.leadingAnchor, constant: -8),
- head.widthAnchor.constraint(equalToConstant: 50),
- head.heightAnchor.constraint(equalToConstant: 50),
- line.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
- line.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
- line.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
- line.heightAnchor.constraint(equalToConstant: 0.5)
- ])
- }
- required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
- func bind(title: String, value: String, isFirst: Bool, isLast: Bool, avatar: UIImage?, placeholder: String) {
- tLabel.text = title
- vLabel.isHidden = true
- head.isHidden = true
- if title == "头像修改" {
- head.isHidden = false
- head.image = avatar ?? UIImage(named: placeholder) ?? UIImage(named: "Home372")
- } else {
- vLabel.isHidden = false
- vLabel.text = value
- }
- line.isHidden = isLast
- }
- }
- // MARK: - Utils
- extension VKNimbusPanelController {
- fileprivate func vx_toast(_ message: String) {
- let lab = UILabel()
- lab.text = message
- lab.font = .systemFont(ofSize: 14)
- lab.textColor = .white
- lab.backgroundColor = UIColor.black.withAlphaComponent(0.7)
- lab.textAlignment = .center
- lab.alpha = 0
- lab.layer.cornerRadius = 8
- lab.clipsToBounds = true
- lab.translatesAutoresizingMaskIntoConstraints = false
- view.addSubview(lab)
- NSLayoutConstraint.activate([
- lab.centerXAnchor.constraint(equalTo: view.centerXAnchor),
- lab.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -100),
- lab.widthAnchor.constraint(equalToConstant: 160),
- lab.heightAnchor.constraint(equalToConstant: 40)
- ])
- UIView.animate(withDuration: 0.3, animations: { lab.alpha = 1 }) { _ in
- DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
- UIView.animate(withDuration: 0.3, animations: { lab.alpha = 0 }) { _ in lab.removeFromSuperview() }
- }
- }
- }
- fileprivate func raiseOops(_ msg: String) {
- let ac = UIAlertController(title: "错误", message: msg, preferredStyle: .alert)
- ac.addAction(UIAlertAction(title: "确定", style: .default))
- present(ac, animated: true)
- }
- }
- // MARK: - UIImage resize
- extension UIImage {
- fileprivate func x_resize(to size: CGSize) -> UIImage? {
- UIGraphicsBeginImageContextWithOptions(size, false, scale)
- defer { UIGraphicsEndImageContext() }
- draw(in: CGRect(origin: .zero, size: size))
- return UIGraphicsGetImageFromCurrentImageContext()
- }
- }
- // MARK: - Debug loggers (obfuscated names)
- extension VKNimbusPanelController {
- func dbg_emitRequest(name: String, request: URLRequest, body: Data?) {
- #if DEBUG
- var lines: [String] = []
- lines.append("\n====================[REQUEST: \(name)]====================")
- lines.append("URL : \(request.url?.absoluteString ?? "-")")
- lines.append("Method : \(request.httpMethod ?? "GET")")
- lines.append("Headers : \(request.allHTTPHeaderFields ?? [:])")
- if let ct = request.value(forHTTPHeaderField: "Content-Type"), ct.contains("multipart/form-data"), let body = body {
- lines.append("Body : <multipart> size=\(body.count) bytes")
- } else if let body = body, !body.isEmpty {
- if let pretty = dbg_pretty(body) { lines.append("Body(JSON): \n\(pretty)") }
- else if let s = String(data: body, encoding: .utf8) { lines.append("Body(Text): \n\(s)") }
- else { lines.append("Body(Binary) size=\(body.count) bytes") }
- } else {
- lines.append("Body : <empty>")
- }
- lines.append("cURL : \n\(dbg_curl(from: request, body: body))")
- lines.append("==========================================================\n")
- print(lines.joined(separator: "\n"))
- #endif
- }
- func dbg_emitResponse(name: String, data: Data?, response: URLResponse?, error: Error?) {
- #if DEBUG
- var lines: [String] = []
- lines.append("\n--------------------[RESPONSE: \(name)]--------------------")
- if let http = response as? HTTPURLResponse {
- lines.append("Status : \(http.statusCode)")
- lines.append("URL : \(http.url?.absoluteString ?? "-")")
- lines.append("Headers : \(http.allHeaderFields)")
- }
- if let error = error { lines.append("Error : \(error.localizedDescription)") }
- if let d = data, !d.isEmpty {
- if let pretty = dbg_pretty(d) { lines.append("Body(JSON): \n\(pretty)") }
- else if let s = String(data: d, encoding: .utf8) { lines.append("Body(Text): \n\(s)") }
- else { lines.append("Body(Binary) size=\(d.count) bytes") }
- } else { lines.append("Body : <empty>") }
- lines.append("----------------------------------------------------------\n")
- print(lines.joined(separator: "\n"))
- #endif
- }
- private func dbg_pretty(_ data: Data) -> String? {
- if let obj = try? JSONSerialization.jsonObject(with: data),
- let json = try? JSONSerialization.data(withJSONObject: obj, options: [.prettyPrinted]),
- let s = String(data: json, encoding: .utf8) { return s }
- return nil
- }
- private func dbg_curl(from request: URLRequest, body: Data?) -> String {
- var parts: [String] = ["curl -i"]
- if let m = request.httpMethod { parts.append("-X \(m)") }
- if let headers = request.allHTTPHeaderFields {
- for (k, v) in headers { parts.append("-H '\(k): \(v)'") }
- }
- if let body = body, !body.isEmpty, !(request.value(forHTTPHeaderField: "Content-Type")?.contains("multipart/form-data") ?? false) {
- if let s = String(data: body, encoding: .utf8) {
- let esc = s.replacingOccurrences(of: "'", with: "'\\''")
- parts.append("--data '\(esc)'")
- }
- }
- if let u = request.url?.absoluteString { parts.append("'\(u)'") }
- return parts.joined(separator: " ")
- }
- }
|