diff --git a/wake/Features/BlindBox/Components/Card.swift b/wake/Features/BlindBox/Components/Card.swift index f5f8e41..f057b6f 100644 --- a/wake/Features/BlindBox/Components/Card.swift +++ b/wake/Features/BlindBox/Components/Card.swift @@ -19,6 +19,52 @@ struct CustomLightSequenceAnimation: View { self.squareSize = screenWidth * 1.8 // 正方形背景尺寸 self.imageSize = squareSize / 3 // 光束卡片尺寸(1/3背景大小) } + +// MARK: - SwiftUI 背景重绘(方形版本) +private struct CardBlindBackground: View { + var body: some View { + GeometryReader { geo in + let w = geo.size.width + let h = geo.size.height + ZStack { + // 主背景卡片(方形) + RoundedRectangle(cornerRadius: 28) + .fill( + LinearGradient( + colors: [Color.white, Color.white.opacity(0.96)], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) + .shadow(color: Color.black.opacity(0.06), radius: 16, x: 0, y: 8) + .frame(width: w * 0.88, height: h * 0.88) + .position(x: w / 2, y: h / 2) + + // 左上光斑 + Circle() + .fill(Color.themePrimary.opacity(0.18)) + .blur(radius: 40) + .frame(width: min(w, h) * 0.35, height: min(w, h) * 0.35) + .position(x: w * 0.25, y: h * 0.25) + + // 右下光斑 + Circle() + .fill(Color.orange.opacity(0.14)) + .blur(radius: 50) + .frame(width: min(w, h) * 0.40, height: min(w, h) * 0.40) + .position(x: w * 0.75, y: h * 0.75) + + // 中央高光描边 + RoundedRectangle(cornerRadius: 28) + .stroke(Color.white.opacity(0.35), lineWidth: 1) + .frame(width: w * 0.88, height: h * 0.88) + .position(x: w / 2, y: h / 2) + .blendMode(.overlay) + .opacity(0.7) + } + } + } +} // 卡片中心位置(固定,确保摆正居中) private var centerPosition: CGPoint { @@ -27,53 +73,13 @@ struct CustomLightSequenceAnimation: View { var body: some View { ZStack { - // 底部背景(正方形) - SVGImage(svgName: "BlindBg") + // 底部背景(正方形,SwiftUI 重绘) + CardBlindBackground() .frame(width: squareSize, height: squareSize) .position(centerPosition) - - // 当前显示的光束卡片(摆正状态) - SVGImage(svgName: "Light\(currentLight)") - .frame(width: imageSize, height: imageSize) - .position(centerPosition) - .opacity(currentOpacity) - - // 下一张待显示的光束卡片(提前加载,摆正状态) - SVGImage(svgName: "Light\(nextLight)") - .frame(width: imageSize, height: imageSize) - .position(centerPosition) - .opacity(nextOpacity) - } - .onAppear { - startLoopAnimation() } } - // 计算下一张卡片序号(基于当前索引循环) - private var nextLight: Int { - let nextIdx = (sequenceIndex + 1) % baseSequence.count - return baseSequence[nextIdx] - } - - // 启动循环切换动画 - private func startLoopAnimation() { - // 每1.2秒切换一次(可调整速度) - Timer.scheduledTimer(withTimeInterval: 1.2, repeats: true) { _ in - // 0.5秒淡入淡出过渡(丝滑无卡顿) - withAnimation(Animation.easeInOut(duration: 0.5)) { - currentOpacity = 0.0 - nextOpacity = 1.0 - } - - // 动画完成后更新状态(确保顺序无偏差) - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - currentLight = nextLight - sequenceIndex = (sequenceIndex + 1) % baseSequence.count - currentOpacity = 1.0 - nextOpacity = 0.0 - } - } - } } // 预览 diff --git a/wake/Features/BlindBox/View/BlindBoxView.swift b/wake/Features/BlindBox/View/BlindBoxView.swift index b79b3b3..019a545 100644 --- a/wake/Features/BlindBox/View/BlindBoxView.swift +++ b/wake/Features/BlindBox/View/BlindBoxView.swift @@ -384,11 +384,9 @@ struct BlindBoxView: View { // 盲盒 ZStack { - // 1. 背景SVG + // 1. 背景(SwiftUI 重绘) if !showScalingOverlay { - SVGImage(svgName: "BlindBg", contentMode: .fit) - // .position(x: UIScreen.main.bounds.width / 2, - // y: UIScreen.main.bounds.height * 0.325) + BlindBackground() .opacity(showScalingOverlay ? 0 : 1) .animation(.easeOut(duration: 1.5), value: showScalingOverlay) } @@ -665,6 +663,52 @@ struct BlindBoxView: View { ) } } + + // MARK: - 盲盒背景(SwiftUI 重绘) + private struct BlindBackground: View { + var body: some View { + GeometryReader { geo in + let w = geo.size.width + let h = geo.size.height + ZStack { + // 主背景卡片 + RoundedRectangle(cornerRadius: 28) + .fill( + LinearGradient( + colors: [Color.white, Color.white.opacity(0.96)], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) + .shadow(color: Color.black.opacity(0.06), radius: 16, x: 0, y: 8) + .frame(width: min(w * 0.9, 360), height: min(h * 0.6, 260)) + .position(x: w / 2, y: h * 0.35) + + // 左上光斑 + Circle() + .fill(Color.themePrimary.opacity(0.18)) + .blur(radius: 40) + .frame(width: 160, height: 160) + .position(x: w * 0.22, y: h * 0.18) + + // 右下光斑 + Circle() + .fill(Color.orange.opacity(0.14)) + .blur(radius: 50) + .frame(width: 180, height: 180) + .position(x: w * 0.78, y: h * 0.55) + + // 中央高光 + RoundedRectangle(cornerRadius: 28) + .stroke(Color.white.opacity(0.35), lineWidth: 1) + .frame(width: min(w * 0.9, 360), height: min(h * 0.6, 260)) + .position(x: w / 2, y: h * 0.35) + .blendMode(.overlay) + .opacity(0.7) + } + } + } + } /// 隐藏设置页面 private func hideSettings() { diff --git a/wake/Media.xcassets/IP.imageset/Contents.json b/wake/Media.xcassets/IP.imageset/Contents.json new file mode 100644 index 0000000..5f2f361 --- /dev/null +++ b/wake/Media.xcassets/IP.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "IP.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/wake/Media.xcassets/IP.imageset/IP.png b/wake/Media.xcassets/IP.imageset/IP.png new file mode 100644 index 0000000..0c6ffe6 Binary files /dev/null and b/wake/Media.xcassets/IP.imageset/IP.png differ diff --git a/wake/Media.xcassets/IP1.imageset/Contents.json b/wake/Media.xcassets/IP1.imageset/Contents.json new file mode 100644 index 0000000..c312e5a --- /dev/null +++ b/wake/Media.xcassets/IP1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "IP1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/wake/Media.xcassets/IP1.imageset/IP1.png b/wake/Media.xcassets/IP1.imageset/IP1.png new file mode 100644 index 0000000..0c6ffe6 Binary files /dev/null and b/wake/Media.xcassets/IP1.imageset/IP1.png differ diff --git a/wake/View/Owner/AboutUsView.swift b/wake/View/Owner/AboutUsView.swift index 1bf4b18..425f7c5 100644 --- a/wake/View/Owner/AboutUsView.swift +++ b/wake/View/Owner/AboutUsView.swift @@ -1,4 +1,5 @@ import SwiftUI +import UIKit struct AboutUsView: View { // MARK: - Properties @@ -25,8 +26,19 @@ struct AboutUsView: View { VStack(spacing: 0) { // IP Address Section VStack(spacing: 12) { - SVGImage(svgName: "AboutIP") - .frame(width: 102, height: 102) + if let icon = primaryAppIconUIImage() { + Image(uiImage: icon) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 102, height: 102) + .cornerRadius(18) + } else { + Image(systemName: "app.fill") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 82, height: 82) + .foregroundColor(.gray) + } } .frame(maxWidth: .infinity) .padding(.vertical, 32) @@ -113,6 +125,18 @@ struct AboutUsView: View { } // MARK: - Private Methods + private func primaryAppIconUIImage() -> UIImage? { + // 获取主 AppIcon 名称列表中最后一个(通常是最大尺寸) + if let iconsDictionary = Bundle.main.infoDictionary?["CFBundleIcons"] as? [String: Any], + let primaryIconsDictionary = iconsDictionary["CFBundlePrimaryIcon"] as? [String: Any], + let iconFiles = primaryIconsDictionary["CFBundleIconFiles"] as? [String], + let lastIcon = iconFiles.last, + let image = UIImage(named: lastIcon) { + return image + } + return nil + } + private func getIPAddress() -> String? { var address: String? var ifaddr: UnsafeMutablePointer? diff --git a/wake/View/Owner/UserInfo/AvatarPicker.swift b/wake/View/Owner/UserInfo/AvatarPicker.swift index 08d94de..ccfa25a 100644 --- a/wake/View/Owner/UserInfo/AvatarPicker.swift +++ b/wake/View/Owner/UserInfo/AvatarPicker.swift @@ -59,28 +59,35 @@ public struct AvatarPicker: View { ) .scaleEffect(scaleFactor) } else { - // Default SVG avatar with animated dashed border - SVGImageHtml(svgName: "IP") - .frame(width: 225, height: 225) - .scaleEffect(scaleFactor) - .contentShape(Rectangle()) - .clipShape(RoundedRectangle(cornerRadius: 20 * scaleFactor)) - .overlay( - RoundedRectangle(cornerRadius: 20) - .stroke(style: StrokeStyle( - lineWidth: borderWidth, - lineCap: .round, - dash: [12, 8], - dashPhase: isAnimating ? 40 : 0 - )) - .foregroundColor(Color.themePrimary) - .scaleEffect(scaleFactor) - ) - .onAppear { - withAnimation(Animation.linear(duration: 1.5).repeatForever(autoreverses: false)) { - isAnimating = true - } + // SwiftUI 占位:白底 + 虚线边框 + 居中加号(带虚线动画) + ZStack { + RoundedRectangle(cornerRadius: 20) + .fill(Color.white) + .frame(width: 225, height: 225) + + Image(systemName: "plus") + .font(.system(size: 32, weight: .bold)) + .foregroundColor(.black) + } + .scaleEffect(scaleFactor) + .contentShape(Rectangle()) + .clipShape(RoundedRectangle(cornerRadius: 20 * scaleFactor)) + .overlay( + RoundedRectangle(cornerRadius: 20) + .stroke(style: StrokeStyle( + lineWidth: borderWidth, + lineCap: .round, + dash: [12, 8], + dashPhase: isAnimating ? 40 : 0 + )) + .foregroundColor(Color.themePrimary) + .scaleEffect(scaleFactor) + ) + .onAppear { + withAnimation(Animation.linear(duration: 1.5).repeatForever(autoreverses: false)) { + isAnimating = true } + } } // Upload indicator diff --git a/wake/View/Subscribe/JoinModal.swift b/wake/View/Subscribe/JoinModal.swift index 2a21e5c..9201191 100644 --- a/wake/View/Subscribe/JoinModal.swift +++ b/wake/View/Subscribe/JoinModal.swift @@ -19,16 +19,7 @@ struct JoinModal: View { // Modal content if isPresented { VStack(spacing: 0) { - // IP Image peeking from top - HStack { - // Make sure you have an image named "IP" in your assets - SVGImageHtml(svgName: "IP1") - .frame(width: 116, height: 65) - .offset(x: 30) - Spacer() - } - .frame(height: 65) - + // 顶部装饰图已移除(按需求) VStack(spacing: 0) { // Close button on the right HStack { @@ -57,9 +48,9 @@ struct JoinModal: View { } .padding(.vertical, 12) // List content - VStack (alignment: .leading) { + VStack (alignment: .leading) { HStack { - SVGImage(svgName: "JoinList") + JoinListMark() .frame(width: 32, height: 32) HStack (alignment: .top){ Text("Unlimited") @@ -73,7 +64,7 @@ struct JoinModal: View { .padding(.vertical, 12) .padding(.leading,12) HStack (alignment: .center) { - SVGImage(svgName: "JoinList") + JoinListMark() .frame(width: 32, height: 32) VStack (alignment: .leading,spacing: 4) { HStack { @@ -91,9 +82,9 @@ struct JoinModal: View { } .padding(.vertical, 12) .padding(.leading,12) - + HStack(alignment: .top) { - SVGImage(svgName: "JoinList") + JoinListMark() .frame(width: 32, height: 32) VStack (alignment: .leading,spacing: 4) { HStack { @@ -123,7 +114,7 @@ struct JoinModal: View { } .padding(.top, 12) .padding(.leading,12) - HStack { + HStack { Spacer() // This will push the button to the right Button(action: { // 点击跳转到会员页面 @@ -164,7 +155,7 @@ struct JoinModal: View { HStack(alignment: .center) { Button(action: { // Action for Terms of Service - if let url = URL(string: "https://memorywake.com/privacy-policy") { + if let url = URL(string: "https://memorywake.com/privacy-policy") { UIApplication.shared.open(url) } }) { @@ -173,11 +164,11 @@ struct JoinModal: View { .foregroundColor(.themeTextMessage) .underline() // Add underline } - Rectangle() + Rectangle() .fill(Color.gray.opacity(0.5)) .frame(width: 1, height: 16) .padding(.vertical, 4) - Button(action: { + Button(action: { // 打开网页 if let url = URL(string: "https://memorywake.com/privacy-policy") { UIApplication.shared.open(url) @@ -188,13 +179,13 @@ struct JoinModal: View { .foregroundColor(.themeTextMessage) .underline() // Add underline } - Rectangle() + Rectangle() .fill(Color.gray.opacity(0.5)) .frame(width: 1, height: 16) .padding(.vertical, 4) - Button(action: { + Button(action: { // Action for Restore Purchase - if let url = URL(string: "https://memorywake.com/privacy-policy") { + if let url = URL(string: "https://memorywake.com/privacy-policy") { UIApplication.shared.open(url) } }) { @@ -206,8 +197,8 @@ struct JoinModal: View { } .padding(.bottom, 24) .frame(maxWidth: .infinity, alignment: .center) - } - .padding(.horizontal, 16) + } + .padding(.horizontal, 16) } .background(Color.white) .cornerRadius(20, corners: [.topLeft, .topRight]) @@ -222,6 +213,37 @@ struct JoinModal: View { } } +// MARK: - SwiftUI JoinList 图标重绘 +private struct JoinListMark: View { + var body: some View { + ZStack { + // 背景圆 + Circle() + .fill( + LinearGradient(colors: [Color.themePrimary.opacity(0.9), Color.orange.opacity(0.8)], + startPoint: .topLeading, + endPoint: .bottomTrailing) + ) + .shadow(color: Color.black.opacity(0.12), radius: 4, x: 0, y: 2) + + // 右指三角(白色) + GeometryReader { geo in + Path { path in + let w = geo.size.width + let h = geo.size.height + path.move(to: CGPoint(x: w*0.42, y: h*0.30)) + path.addLine(to: CGPoint(x: w*0.70, y: h*0.50)) + path.addLine(to: CGPoint(x: w*0.42, y: h*0.70)) + path.closeSubpath() + } + .fill(Color.white) + .opacity(0.95) + } + .padding(8) + } + } +} + struct JoinModal_Previews: PreviewProvider { static var previews: some View { JoinModal(isPresented: .constant(true))