import CoreImage.CIFilterBuiltins import SwiftUI import UIKit // 扫码登录界面:显示二维码 + 引导用已登录的 cdrop 扫码批准。轮询在 AuthManager 里。 struct LoginView: View { @Environment(AuthManager.self) private var auth var body: some View { VStack(spacing: 24) { Spacer() Text(t("app.brand")) .font(.largeTitle) .bold() qrArea Text(auth.statusText) .font(.callout) .foregroundStyle(auth.qrExpired ? Color.red : Color.secondary) refreshButton Text(t("ios.login.guide")) .font(.footnote) .foregroundStyle(.secondary) .multilineTextAlignment(.center) .padding(.horizontal, 40) Spacer() } .padding() .tint(.cdropAccent) .task { await auth.startQRLogin() } } // 刷新二维码:失效后高亮(glassProminent),未失效时也常驻可手动刷新。两种玻璃样式类型 // 不同,故经 @ViewBuilder 条件分支。 @ViewBuilder private var refreshButton: some View { let button = Button { Task { await auth.startQRLogin() } } label: { Label(t("ios.login.refresh"), systemImage: "arrow.clockwise") } if auth.qrExpired { button.buttonStyle(.glassProminent) } else { button.buttonStyle(.glass) } } @ViewBuilder private var qrArea: some View { if let payload = auth.qrPayload, let img = qrImage(payload) { Image(uiImage: img) .interpolation(.none) .resizable() .frame(width: 220, height: 220) .padding(16) .background(.white, in: RoundedRectangle(cornerRadius: 16)) .opacity(auth.qrExpired ? 0.25 : 1) // 失效后置灰,视觉提示需刷新 .overlay { if auth.qrExpired { Image(systemName: "arrow.clockwise.circle.fill") .font(.system(size: 56)) .foregroundStyle(.secondary) } } } else { ProgressView() .frame(width: 220, height: 220) } } // CoreImage 内建二维码生成(无第三方依赖)。payload 是后端给的完整 /link?r=..&c=.. URL。 private func qrImage(_ string: String) -> UIImage? { let filter = CIFilter.qrCodeGenerator() filter.message = Data(string.utf8) guard let output = filter.outputImage else { return nil } let scaled = output.transformed(by: CGAffineTransform(scaleX: 10, y: 10)) let ctx = CIContext() guard let cg = ctx.createCGImage(scaled, from: scaled.extent) else { return nil } return UIImage(cgImage: cg) } }