feat: 会员弹窗

This commit is contained in:
jinyaqiu 2025-08-29 15:27:02 +08:00
parent 8044927b51
commit 10ecd93b54
8 changed files with 253 additions and 43 deletions

9
wake/Assets/Svg/IP1.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 183 KiB

View File

@ -0,0 +1,11 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6596_2873)">
<rect x="0.0969238" y="0.567871" width="30.9623" height="30.9623" rx="15.4812" transform="rotate(0.179418 0.0969238 0.567871)" fill="black"/>
<path d="M15.578 0.597365L28.9286 23.8893L2.08191 23.8052L15.578 0.597365Z" fill="#D9D9D9"/>
</g>
<defs>
<clipPath id="clip0_6596_2873">
<rect x="0.0969238" y="0.567871" width="30.9623" height="30.9623" rx="15.4812" transform="rotate(0.179418 0.0969238 0.567871)" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 582 B

View File

@ -26,6 +26,7 @@ enum TypographyStyle {
case largeTitle // case largeTitle //
case smallLargeTitle // case smallLargeTitle //
case headline // case headline //
case headline1 // 1
case title // case title //
case title2 // case title2 //
case title3 // case title3 //
@ -54,6 +55,7 @@ struct Typography {
private static let styleConfig: [TypographyStyle: TypographyConfig] = [ private static let styleConfig: [TypographyStyle: TypographyConfig] = [
.largeTitle: TypographyConfig(size: 32, weight: .heavy, textStyle: .largeTitle), .largeTitle: TypographyConfig(size: 32, weight: .heavy, textStyle: .largeTitle),
.smallLargeTitle: TypographyConfig(size: 30, weight: .heavy, textStyle: .largeTitle), .smallLargeTitle: TypographyConfig(size: 30, weight: .heavy, textStyle: .largeTitle),
.headline1: TypographyConfig(size: 26, weight: .bold, textStyle: .headline),
.headline: TypographyConfig(size: 24, weight: .bold, textStyle: .headline), .headline: TypographyConfig(size: 24, weight: .bold, textStyle: .headline),
.title3: TypographyConfig(size: 22, weight: .semibold, textStyle: .title2), .title3: TypographyConfig(size: 22, weight: .semibold, textStyle: .title2),
.title: TypographyConfig(size: 20, weight: .semibold, textStyle: .title2), .title: TypographyConfig(size: 20, weight: .semibold, textStyle: .title2),

View File

@ -1,38 +0,0 @@
import SwiftUI
import UIKit
// UIImageViewAPNG
struct APNGView: UIViewRepresentable {
let imageName: String
@Binding var isAnimating: Bool
func makeUIView(context: Context) -> UIImageView {
let imageView = UIImageView()
// APNG
if let image = UIImage(named: imageName) {
imageView.image = image
//
imageView.animationImages = image.images
//
imageView.animationDuration = image.duration
// isAnimating
if isAnimating {
imageView.startAnimating()
}
}
return imageView
}
func updateUIView(_ uiView: UIImageView, context: Context) {
// isAnimating
if isAnimating {
if !uiView.isAnimating {
uiView.startAnimating()
}
} else {
uiView.stopAnimating()
}
}
}

View File

@ -8,6 +8,7 @@ struct BlindOutcomeView: View {
@Environment(\.presentationMode) var presentationMode @Environment(\.presentationMode) var presentationMode
@State private var isFullscreen = false @State private var isFullscreen = false
@State private var isPlaying = false @State private var isPlaying = false
@State private var showIPListModal = false
var body: some View { var body: some View {
NavigationView { NavigationView {
@ -99,9 +100,11 @@ struct BlindOutcomeView: View {
// Button below media // Button below media
VStack(spacing: 16) { VStack(spacing: 16) {
Button(action: { Button(action: {
// videocontentview // video
if case .video = media { if case .video = media {
// Router.shared.navigate(to: .mediaUpload) withAnimation {
showIPListModal = true
}
} else { } else {
Router.shared.navigate(to: .mediaUpload) Router.shared.navigate(to: .mediaUpload)
} }
@ -130,6 +133,9 @@ struct BlindOutcomeView: View {
FullscreenMediaView(media: media, isPresented: $isFullscreen, isPlaying: $isPlaying, player: nil) FullscreenMediaView(media: media, isPresented: $isFullscreen, isPlaying: $isPlaying, player: nil)
} }
} }
.overlay(
JoinModal(isPresented: $showIPListModal)
)
} }
.navigationViewStyle(StackNavigationViewStyle()) // iPad .navigationViewStyle(StackNavigationViewStyle()) // iPad
.navigationBarHidden(true) // .navigationBarHidden(true) //

View File

@ -0,0 +1,220 @@
import SwiftUI
struct JoinModal: View {
@Binding var isPresented: Bool
var body: some View {
ZStack(alignment: .bottom) {
// Semi-transparent background
if isPresented {
Color.black.opacity(0.4)
.edgesIgnoringSafeArea(.all)
.onTapGesture {
withAnimation {
isPresented = false
}
}
}
// Modal content
if isPresented {
VStack(spacing: 0) {
// IP Image peeking from top
HStack {
// Make sure you have an image named "IP" in your assets
SVGImage(svgName: "IP1")
.frame(width: 116, height: 65)
.offset(x: 30)
Spacer()
}
.frame(height: 65)
VStack(spacing: 0) {
// Close button on the right
HStack {
Spacer()
Button(action: {
withAnimation {
isPresented = false
}
}) {
Image(systemName: "xmark")
.font(.system(size: 20, weight: .medium))
.foregroundColor(.themeTextMessageMain)
.padding(12)
}
.padding(.trailing, 16)
}
//
VStack(spacing: 8) {
Text("Join us!")
.font(Typography.font(for: .headline1, family: .quicksandBold))
.foregroundColor(.themeTextMessageMain)
Text("Join us to get more exclusive benefits.")
.font(.system(size: 14, weight: .regular))
.foregroundColor(.themeTextMessageMain)
}
.padding(.vertical, 12)
// List content
VStack (alignment: .leading) {
HStack {
SVGImage(svgName: "JoinList")
.frame(width: 32, height: 32)
HStack (alignment: .top){
Text("Unlimited")
.font(.system(size: 16, weight: .bold))
.foregroundColor(.themeTextMessageMain)
Text(" blind box purchases.")
.font(.system(size: 16, weight: .regular))
.foregroundColor(.themeTextMessageMain)
}
}
.padding(.vertical, 12)
.padding(.leading,12)
HStack (alignment: .center) {
SVGImage(svgName: "JoinList")
.frame(width: 32, height: 32)
VStack (alignment: .leading,spacing: 4) {
HStack {
Text("Freely")
.font(.system(size: 16, weight: .bold))
.foregroundColor(.themeTextMessageMain)
Text(" upload image and video")
.font(.system(size: 16, weight: .regular))
.foregroundColor(.themeTextMessageMain)
}
Text(" materials.")
.font(.system(size: 16, weight: .regular))
.foregroundColor(.themeTextMessageMain)
}
}
.padding(.vertical, 12)
.padding(.leading,12)
HStack(alignment: .top) {
SVGImage(svgName: "JoinList")
.frame(width: 32, height: 32)
VStack (alignment: .leading,spacing: 4) {
HStack {
Text("500")
.font(.system(size: 16, weight: .bold))
.foregroundColor(.themeTextMessageMain)
Text(" credits daily,")
.font(.system(size: 16, weight: .regular))
.foregroundColor(.themeTextMessageMain)
}
HStack(alignment: .top) {
VStack (alignment: .leading, spacing: 4) {
HStack {
Text("5000")
.font(.system(size: 16, weight: .bold))
.foregroundColor(.themeTextMessageMain)
Text(" permanent credits on your first")
.font(.system(size: 16, weight: .regular))
.foregroundColor(.themeTextMessageMain)
}
Text(" purchase!")
.font(.system(size: 16, weight: .regular))
.foregroundColor(.themeTextMessageMain)
}
}
}
}
.padding(.top, 12)
.padding(.leading,12)
HStack {
Spacer() // This will push the button to the right
Button(action: {
//
Router.shared.navigate(to: .subscribe)
}) {
HStack {
Text("See More")
.font(.system(size: 16))
Image(systemName: "chevron.right")
.font(.system(size: 14))
}
.foregroundColor(.themeTextMessageMain)
.padding(.vertical, 12)
.padding(.horizontal, 24)
.cornerRadius(20)
}
}
.padding(.trailing, 16) // Add some right padding to match the design
Button(action: {
//
Router.shared.navigate(to: .subscribe)
}) {
HStack {
Text("Subscribe")
.font(Typography.font(for: .body, family: .quicksandBold))
Spacer()
Text("$1.00/Mon")
.font(Typography.font(for: .body, family: .quicksandBold))
}
.foregroundColor(.themeTextMessageMain)
.padding(.vertical, 12)
.padding(.horizontal, 30)
.background(Color.themePrimary)
.cornerRadius(20)
}
.padding(.top, 16)
//
HStack(alignment: .center) {
Button(action: {
// Action for Terms of Service
}) {
Text("Terms of Service")
.font(.system(size: 12, weight: .regular))
.foregroundColor(.themeTextMessage)
.underline() // Add underline
}
Rectangle()
.fill(Color.gray.opacity(0.5))
.frame(width: 1, height: 16)
.padding(.vertical, 4)
Button(action: {
// Action for Privacy Policy
}) {
Text("Privacy Policy")
.font(.system(size: 12, weight: .regular))
.foregroundColor(.themeTextMessage)
.underline() // Add underline
}
Rectangle()
.fill(Color.gray.opacity(0.5))
.frame(width: 1, height: 16)
.padding(.vertical, 4)
Button(action: {
// Action for Restore Purchase
}) {
Text("Restore Purchase")
.font(.system(size: 12, weight: .regular))
.foregroundColor(.themeTextMessage)
.underline() // Add underline
}
}
.padding(.bottom, 24)
.frame(maxWidth: .infinity, alignment: .center)
}
.padding(.horizontal, 16)
}
.background(Color.white)
.cornerRadius(20, corners: [.topLeft, .topRight])
}
.frame(height: nil)
.transition(.move(edge: .bottom))
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)
.edgesIgnoringSafeArea(.all)
.animation(.easeInOut, value: isPresented)
}
}
struct JoinModal_Previews: PreviewProvider {
static var previews: some View {
JoinModal(isPresented: .constant(true))
}
}

View File

@ -167,7 +167,7 @@ struct MediaUploadView: View {
} }
// //
uploadManager.startUpload() // uploadManager.startUpload()
} else { } else {
print(" 没有新文件需要添加,所有选择的文件都已存在") print(" 没有新文件需要添加,所有选择的文件都已存在")
} }
@ -249,7 +249,7 @@ struct MediaUploadView: View {
} }
// //
uploadManager.startUpload() // uploadManager.startUpload()
print("媒体添加完成,总数量: \(uploadManager.selectedMedia.count)") print("媒体添加完成,总数量: \(uploadManager.selectedMedia.count)")
} }
} }

View File

@ -46,7 +46,7 @@ struct WakeApp: App {
// //
if authState.isAuthenticated { if authState.isAuthenticated {
// userInfo // userInfo
LoginView() MediaUploadView()
.environmentObject(authState) .environmentObject(authState)
// ContentView() // ContentView()
// .environmentObject(authState) // .environmentObject(authState)