This commit is contained in:
Junhui Chen 2025-09-10 19:25:33 +08:00
parent 17dc5bbe5e
commit 5ca76c4e5b
9 changed files with 238 additions and 93 deletions

View File

@ -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
}
}
}
}
//

View File

@ -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() {

View File

@ -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
}
}

BIN
wake/Media.xcassets/IP.imageset/IP.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -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
}
}

BIN
wake/Media.xcassets/IP1.imageset/IP1.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -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<ifaddrs>?

View File

@ -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

View File

@ -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))