feat: 会员弹窗
This commit is contained in:
parent
8044927b51
commit
10ecd93b54
9
wake/Assets/Svg/IP1.svg
Normal file
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 |
11
wake/Assets/Svg/JoinList.svg
Normal file
11
wake/Assets/Svg/JoinList.svg
Normal 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 |
@ -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),
|
||||||
|
|||||||
@ -1,38 +0,0 @@
|
|||||||
import SwiftUI
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
// 包装UIImageView以支持APNG动画
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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: {
|
||||||
// 如果携带的类型是video跳转到contentview
|
// 如果携带的类型是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) // 额外确保隐藏导航栏
|
||||||
|
|||||||
220
wake/View/Blind/JoinModal.swift
Normal file
220
wake/View/Blind/JoinModal.swift
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user