This commit is contained in:
zeus
2025-01-22 16:22:33 +08:00
parent 09b9cec8ec
commit 738c373a77
2534 changed files with 0 additions and 486292 deletions
@@ -1,286 +0,0 @@
//
// ActiveDashboardViewNewUI.swift
// SFI
//
// Created by Zeus on 2024/10/11.
//
import Foundation
import Foundation
import Libbox
import Library
import SwiftUI
import ApplicationLibrary
@MainActor
public struct ActiveDashboardViewNewUI: View {
@Environment(\.scenePhase) var scenePhase
@Environment(\.selection) private var parentSelection
@EnvironmentObject private var environments: ExtensionEnvironments
@EnvironmentObject private var profile: ExtensionProfile
@State private var isLoading = true
@State private var profileList: [ProfilePreview] = []
@State private var selectedProfileID: Int64 = 0
@State private var alert: Alert?
@State private var selection = DashboardPage.overview
@State private var systemProxyAvailable = false
@State private var systemProxyEnabled = false
@State private var alerthandleXufei = false
@State private var tixingdingyueEnabled = false
public init() {}
public var body: some View {
if isLoading {
ProgressView().onAppear {
Task {
await doReload()
}
}
} else {
if ApplicationLibrary.inPreview {
body1
} else {
body1
.onAppear {
Task {
await doReloadSystemProxy()
}
}
.onChangeCompat(of: profile.status) { newStatus in
if newStatus == .connected {
Task {
await doReloadSystemProxy()
}
}
}
}
}
}
private var body1: some View {
VStack {
#if os(iOS) || os(tvOS)
if ApplicationLibrary.inPreview || profile.status.isConnectedStrict {
/* Picker("Page", selection: $selection) {
ForEach(DashboardPage.allCases) { page in
page.label
}
}
.pickerStyle(.segmented)
#if os(iOS)
.padding([.leading, .trailing])
.navigationBarTitleDisplayMode(.inline)
#endif
TabView(selection: $selection) {
ForEach(DashboardPage.allCases) { page in
page.contentView($profileList, $selectedProfileID, $systemProxyAvailable, $systemProxyEnabled)
.tag(page)
}
}
.tabViewStyle(.page(indexDisplayMode: .always))
*/
OverviewView($profileList, $selectedProfileID, $systemProxyAvailable, $systemProxyEnabled)
} else {
if tixingdingyueEnabled {
XufeiTixingButton {
alerthandleXufei = true
}.alert(Text("续费提醒"), isPresented: $alerthandleXufei) {
Button("续费"){
environments.opentixingSubnodes.send()
}
} message: {
Text("您的账户已经过期,请续费后继续体验畅快感受;如果您刚购买完请耐心等待,会员时长会在1分钟内到账。")
}
}else{
OverviewView($profileList, $selectedProfileID, $systemProxyAvailable, $systemProxyEnabled)
}
}
#elseif os(macOS)
OverviewView($profileList, $selectedProfileID, $systemProxyAvailable, $systemProxyEnabled)
#endif
}.onReceive(environments.updateTixingdingyueEnabled, perform: { _ in
tixingdingyueEnabled = false
})
.onReceive(environments.profileUpdate) { _ in
Task {
await doReload()
}
}
.onReceive(environments.selectedProfileUpdate) { _ in
Task {
selectedProfileID = await SharedPreferences.selectedProfileID.get()
if profile.status.isConnected {
await doReloadSystemProxy()
}
}
}
.alertBinding($alert)
}
private func doReload() async {
defer {
isLoading = false
}
if ApplicationLibrary.inPreview {
profileList = [
ProfilePreview(Profile(id: 0, name: "profile local", type: .local, path: "")),
ProfilePreview(Profile(id: 1, name: "profile remote", type: .remote, path: "", lastUpdated: Date(timeIntervalSince1970: 0))),
]
systemProxyAvailable = true
systemProxyEnabled = true
selectedProfileID = 0
} else {
do {
profileList = try await ProfileManager.list().map { ProfilePreview($0) }
if profileList.isEmpty {
print("profileList.isEmpty 是否提醒续费\(tixingdingyueEnabled), \(UserManager.shared.getSuburlData().count)")
//add profile remote
if tixingdingyueEnabled {
return
}
Task {
if UserManager.shared.getSuburlData().count > 5 {
await createProfile()
}
}
await environments.reload()
return
}
selectedProfileID = await SharedPreferences.selectedProfileID.get()
if profileList.filter({ profile in
profile.id == selectedProfileID
})
.isEmpty {
selectedProfileID = profileList[0].id
await SharedPreferences.selectedProfileID.set(selectedProfileID)
}
// try await ProfileManager.list().forEach { p in
// await ProfileManager.update(p)
// }
} catch {
alert = Alert(error)
return
}
}
environments.emptyProfiles = profileList.isEmpty
}
private nonisolated func doReloadSystemProxy() async {
do {
let status = try LibboxNewStandaloneCommandClient()!.getSystemProxyStatus()
await MainActor.run {
systemProxyAvailable = status.available
systemProxyEnabled = status.enabled
}
} catch {
await MainActor.run {
alert = Alert(error)
}
}
}
private func createProfile() async {
do {
try await createProfileBackground()
} catch {
print("createProfile : \(error)")
}
environments.profileUpdate.send()
}
private nonisolated func createProfileBackground() async throws {
print("正在创建 profile")
let nextProfileID = try await ProfileManager.nextID()
var savePath = ""
let remoteURL: String = UserManager.shared.getSuburlData()
var lastUpdated: Date? = nil
var remoteContent = try HTTPClient().getString(remoteURL)
print("第一次订阅URL的内容 remoteContent: \(remoteContent.count)")
if(remoteContent.count < 3 && UserManager.shared.paymentURL().count < 3){
//TODO (便,)
//
remoteContent = try HTTPClient().getString(UserManager.shared.baseDYURL())
print("请求服务器的议定 remoteContent: \(remoteContent.count)")
}else{
if(remoteContent.count < 3){
//
print("提醒续费,正常用户")
await MainActor.run {
tixingdingyueEnabled = true
}
}else{
print("正常有订阅的付费客户")
//
await MainActor.run {
tixingdingyueEnabled = false
}
}
}
if remoteContent.isEmpty {
return
}
var error: NSError?
LibboxCheckConfig(remoteContent, &error)
if let error {
throw error
}
let profileConfigDirectory = FilePath.sharedDirectory.appendingPathComponent("configs", isDirectory: true)
try FileManager.default.createDirectory(at: profileConfigDirectory, withIntermediateDirectories: true)
let profileConfig = profileConfigDirectory.appendingPathComponent("config_\(nextProfileID).json")
try remoteContent.write(to: profileConfig, atomically: true, encoding: .utf8)
savePath = profileConfig.relativePath
lastUpdated = .now
try await ProfileManager.create(Profile(
name: "VPN",
type: ProfileType.remote,
path: savePath,
remoteURL: remoteURL,
autoUpdate: true,
autoUpdateInterval: 60,
lastUpdated: lastUpdated
))
#if os(iOS) || os(tvOS)
try UIProfileUpdateTask.configure()
let list = try await ProfileManager.list()
list.forEach { profileOne in
print("createProfileBackground 查询到所有订阅节点:{ \n \(profileOne.id ?? 0 ) \(profileOne.path) \n \(profileOne.remoteURL ?? "") }")
}
await MainActor.run {
environments.openProfileGetSuccess.send()
}
#else
try await ProfileUpdateTask.configure()
#endif
}
}
@@ -1,10 +0,0 @@
//
// BgView.swift
// SFI
//
// Created by Mac on 2024/10/12.
//
import Foundation
//
@@ -1,45 +0,0 @@
//
// DashboardViewNewUI.swift
// SFI
//
// Created by Zeus on 2024/10/11.
//
import Foundation
import Libbox
import Library
import SwiftUI
import ApplicationLibrary
struct DashboardViewNewUI: View {
@EnvironmentObject private var environments: ExtensionEnvironments
@EnvironmentObject private var profile: ExtensionProfile
@State private var alert: Alert?
var body: some View {
VStack {
ActiveDashboardViewNewUI()
}
.alertBinding($alert)
.onChangeCompat(of: profile.status) { newValue in
if newValue == .disconnecting || newValue == .connected {
Task {
await checkServiceError()
}
}
}
}
private nonisolated func checkServiceError() async {
var error: NSError?
let message = LibboxReadServiceError(&error)
if error != nil {
return
}
await MainActor.run {
alert = Alert(title: Text("VPN 服务异常"), message: Text(message))
}
}
}
@@ -1,9 +0,0 @@
//
// GroupListViewModel.swift
// SFI
//
// Created by Mac on 2024/10/19.
//
import Foundation
File diff suppressed because it is too large Load Diff
@@ -1,126 +0,0 @@
//
// QuestionView.swift
// SFI
//
// Created by Mac on 2024/10/24.
//
import ApplicationLibrary
import Foundation
import SwiftUI
struct QuestionView: View {
@Environment(\.presentationMode) var presentationMode //
var body: some View {
NavigationView(content: {
VStack{
ZStack{
HStack{
Button(action: {
presentationMode.wrappedValue.dismiss() //
}, label: {
Image(systemName: "chevron.left")
.foregroundColor(.white)
Text("返回")
.foregroundColor(.white)
}).padding(10) //
Spacer(minLength: 0)
}
Text("问题解答")
.fontWeight(.bold).lineLimit(1)
}
.padding()
// .padding(.top,edges.top)
.foregroundColor(.white)
ScrollView(.vertical, showsIndicators: false) {
VStack {
HStack{
Text("App软件无法使用问题?") // Text
Spacer()
}
Section("") {
Text("1:确认网络没问题,包括 WIFI 和 移动网络可用 \n2:确认手机可访问浏览器正常数据,可以尝试访问 https://www.apple.com 试试网络是否连接正常。")
.buttonStyle(.plain).font(.subheadline)
.foregroundColor(Color.white.opacity(0.8))
}
Divider() // 线
HStack{
Text("初次连接提示创建“VPN连接”失败?") // Text
Spacer()
}
Section("") {
Text("到这种情况可能是VPN功能被系统被禁止了。可以尝试到设置中开启“VPN连接”权限,若还有问题请随时联系客服。")
.buttonStyle(.plain).font(.subheadline)
.foregroundColor(Color.white.opacity(0.8))
}
Divider() // 线
HStack{
Text("指定的国家连接不上?") // Text
Spacer()
}
Section("") {
Text("快连每个国家的背后都有成干上百的网络节点在进行智能调控,若遇到指定国家连接不上的情况请随时联系客服为您定位原因,请放心一定能解决!")
.buttonStyle(.plain).font(.subheadline)
.foregroundColor(Color.white.opacity(0.8))
}
Divider() // 线
HStack{
Text("连接成功了,但无法访问外网?") // Text
Spacer()
}
Section("") {
Text("恭喜您遇到了严重的软件BUG,请尽快联系客服复现问题,领取BUG奖励~!!!")
.buttonStyle(.plain).font(.subheadline).foregroundColor(Color.white.opacity(0.8))
}
Divider() // 线
HStack{
Text("遇到VPN无法连接怎么办?") // Text
Spacer()
}
Section("") {
Text("1:确定您所安装的小熊加速器是最新版本 2:尝试重启客户端 3:联系我们的客服 ").multilineTextAlignment(.leading)
.buttonStyle(.plain).font(.subheadline).foregroundColor(Color.white.opacity(0.8))
}
}.padding(.leading,10).padding(.trailing,10)
}.foregroundColor(.white)
}.navigationBarHidden(true).edgesIgnoringSafeArea(.bottom).background(
BackgroundBg()
)
})
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true) //
}
}
@@ -1,391 +0,0 @@
//
// InviteListView.swift
// SFI
//
// Created by Mac on 2024/10/21.
//
import Foundation
import SwiftUI
import ApplicationLibrary
//InviteListView
struct InviteListView : View{
@Binding var isPresented: Bool
@State private var codeslist: [InviteCode] = []
@State private var isLoading = false
@State private var errorMessage: String?
@State private var alert: Alert?
@State private var stat: [Int]?
@Environment(\.openURL) private var openURL
@Environment(\.presentationMode) var presentationMode //
var body: some View {
NavigationView(content: {
VStack {
// Navigation Bar
ZStack{
HStack{
Button(action: {
isPresented = false
presentationMode.wrappedValue.dismiss() //
}, label: {
Image(systemName: "chevron.left")
.foregroundColor(.white)
Text("返回")
.foregroundColor(.white)
}).padding(10) //
Spacer()
Button(action: {
Task{
await genoneInvite()
}
}, label: {
Text("生成邀请码").foregroundColor(.white)
})
}
Spacer()
VStack(spacing: 5){
Text("我的邀请")
.fontWeight(.bold).lineLimit(1).frame(width: UIScreen.main.bounds.width*0.5)
}
.foregroundColor(.white)
Spacer()
}
.padding(.all)
if isLoading {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
.padding()
}
if let errorMessage = errorMessage {
Text(errorMessage)
.bold()
.foregroundColor(.red)
.multilineTextAlignment(.center) //
.padding()
}
//
// // Subscription Plans
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 16) {
VStack {
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 2), alignment: .leading) {
if let st = stat {
StatusItem("邀请注册人数") {
StatusLine("人数", "\(st[0])")
}
StatusItem("邀请佣金比例") {
StatusLine("比例", "\(st[3])%")
}
StatusItem("确认中的佣金") {
StatusLine("金额(¥)", "\(st[1])")
}
StatusItem("累计获得佣金") {
StatusLine("金额(¥)", "\(st[2])")
}
}
}.background {
GeometryReader { geometry in
Rectangle()
.fill(.clear)
.frame(height: 1)
.onChangeCompat(of: geometry.size.width) { newValue in
//updateColumnCount(newValue)
}
.onAppear {
// updateColumnCount(geometry.size.width)
}
}.padding()
}
}
.frame(alignment: .topLeading)
.padding([.top, .leading, .trailing])
if codeslist.isEmpty {
// Text("Loading plans...") // Loading indicator
if !isLoading
{
Text("暂无记录").font(.caption)
.foregroundColor(.gray)
.multilineTextAlignment(.center) //
.padding()
}
} else {
ForEach(codeslist) { plan in
Button(action: {
UIPasteboard.general.string = UserManager.shared.mainregisterURL()+plan.code
alert = Alert(okMessage: "复制成功: " + UserManager.shared.mainregisterURL()+plan.code )
}, label: {
InviteItemView(invicode: plan)
})
}
}
}
}.padding(.top, 20)
Spacer()
// Subscription Notice
Text(" ")
.font(.footnote)
.foregroundColor(.gray)
.padding(.bottom, 20)
}
.background(
LinearGradient(colors: [
Color("BG1"),
Color("BG1"),
Color("BG2"),
Color("BG2"),
], startPoint: .top, endPoint: .bottom)
)
.navigationBarHidden(true)
// .background(.white)
.onAppear(){
Task{
await getCodesList()
}
}.edgesIgnoringSafeArea(.bottom)
})
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true) //
.alertBinding($alert)
}
public func genoneInvite() async {
let userInfoUrl = URL(string: "\(UserManager.shared.baseURL())user/invite/save")!
var request = URLRequest(url: userInfoUrl)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(UserManager.shared.getAutoData(), forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
if let error = error {
alert = Alert(error)
return
}
guard let data = data else {
return
}
struct InviteReponse2: Codable {
let status, message: String?
}
// Parse the user info response
if let response = try? JSONDecoder().decode(InviteReponse2.self, from: data),let message = response.message {
if let status = response.status, status == "success" {
alert = Alert(okMessage: message){
Task{
await getCodesList()
}
}
}else{
alert = Alert(errorMessage: message)
}
}
}
}
task.resume()
}
private struct StatusLine: View {
private let name: String
private let value: String
init(_ name: String, _ value: String) {
self.name = name
self.value = value
}
var body: some View {
HStack {
Text(name)
.font(.subheadline)
.foregroundColor(.secondary)
Spacer()
Text(value)
.font(.subheadline)
}
}
}
private struct StatusItem<T>: View where T: View {
private let title: String
@ViewBuilder private let content: () -> T
init(_ title: String, @ViewBuilder content: @escaping () -> T) {
self.title = title
self.content = content
}
var body: some View {
VStack(alignment: .leading) {
HStack {
Text(title)
.font(.subheadline)
Spacer()
}.padding(.bottom, 8)
content()
}
.frame(minWidth: 125, alignment: .topLeading)
#if os(tvOS)
.padding(EdgeInsets(top: 20, leading: 26, bottom: 20, trailing: 26))
#else
.padding(EdgeInsets(top: 10, leading: 13, bottom: 10, trailing: 13))
#endif
.background(backgroundColor.opacity(0.3))
.cornerRadius(10)
}
private var backgroundColor: Color {
#if os(iOS)
return Color(.gray)
#elseif os(macOS)
return Color(nsColor: .textBackgroundColor)
#elseif os(tvOS)
return Color(uiColor: .black)
#endif
}
}
public func getCodesList() async {
isLoading = true
errorMessage = nil
let userInfoUrl = URL(string: "\(UserManager.shared.baseURL())user/invite/fetch")!
var request = URLRequest(url: userInfoUrl)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(UserManager.shared.getAutoData(), forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
self.isLoading = false
if let error = error {
self.errorMessage = "数据请求失败: \(error.localizedDescription)"
return
}
guard let data = data else {
self.errorMessage = "数据请求失败"
return
}
// if let jsonString = String(data: data, encoding: .utf8) {
// print("Response data: \(jsonString)")
// } else {
// print("Failed to convert data to string.")
// }
//
// Parse the user info response
if let Subscribe = try? JSONDecoder().decode(InviteReponse.self, from: data) {
if let codes = Subscribe.data?.codes {
codeslist = codes
stat = Subscribe.data?.stat
}else{
self.errorMessage = Subscribe.message ?? ""
}
}else{
self.errorMessage = "数据请求失败: JSON 解析错误"
}
}
}
task.resume()
}
}
struct InviteItemView: View {
var invicode : InviteCode
var body: some View {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text("邀请码:" + invicode.code )
.font(.headline)
.foregroundColor(.black).multilineTextAlignment(.leading)
.frame(maxWidth: .infinity)
Spacer()
Text("创建时间:"+TimestampConverter.convertTimestampToDateString(invicode.createdAt))
.font(.caption)
.fontWeight(.bold)
.padding(2)
.frame(maxWidth: .infinity)
.foregroundColor(.gray)
// .offset(x: -16, y:16)
.offset(y:10)
}
}
.padding()
.background(Color.white)
.cornerRadius(10)
.shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 3)
.padding(.horizontal, 20)
}
}
@@ -1,204 +0,0 @@
//
// InviteView.swift
// SFI
//
// Created by Mac on 2024/10/12.
//
import Foundation
import SwiftUI
struct InviteView: View {
@Binding var isPresented: Bool
@State private var inviteCode: String = "" // This can be dynamically updated.
@State private var alert: Alert?
var body: some View {
ZStack(){
LinearGradient(colors: [
Color("BG1"),
Color("BG1"),
Color("BG2"),
Color("BG2"),
], startPoint: .top, endPoint: .bottom)
VStack(spacing: 20) {
Spacer().frame(height: 30)
// Invitation code section
HStack {
Text("我的邀请码:")
.font(.headline)
.foregroundColor(.black)
Text(inviteCode)
.font(.headline)
.foregroundColor(.green)
}
// Illustration image
// Image("panda_invite") // Add your asset with this name
LottieView(animationFileName: "7a4ce4b4" , loopMode: .loop)
.aspectRatio(contentMode: .fill)
//// .frame(width: getRect().width,height: getRect().width)
.scaleEffect(0.3)
.scaledToFit()
.frame(width: 200, height: 150)
// Invite description
VStack(spacing: 5) {
Text("邀请您的朋友成为 VIP,邀请好友享首次付费30%返佣,赶紧邀请好友一同使用开心上网!")
.multilineTextAlignment(.center)
.font(.headline)
.padding(10)
.foregroundColor(.black)
}
// Copy invite code button
Button(action: {
// Copy invite code action
if inviteCode.count <= 1 {
Task {
await reloadInvite(copy: true)
}
}else{
UIPasteboard.general.string = UserManager.shared.mainregisterURL()+inviteCode
alert = Alert(okMessage: "复制成功")
}
}) {
Text("复制邀请链接")
.font(.system(size: 16))
.frame(maxWidth: .infinity)
.padding()
.background(LinearGradient(gradient: Gradient(colors: [Color.green.opacity(0.7), Color.green]), startPoint: .leading, endPoint: .trailing))
.cornerRadius(25)
.foregroundColor(.white)
}
.padding(.horizontal, 40)
Spacer().frame(height: 30)
}
.background(
RoundedRectangle(cornerRadius: 20)
.fill(Color.white)
.shadow(radius: 10)
)
.padding()
}
.onAppear(perform: {
Task {
await reloadInvite(copy: false)
}
})
.alertBinding($alert)
.edgesIgnoringSafeArea(.all)
}
public func reloadInvite(copy: Bool) async {
let userInfoUrl = URL(string: "\(UserManager.shared.baseURL())user/invite/fetch")!
var request = URLRequest(url: userInfoUrl)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(UserManager.shared.getAutoData(), forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
if let error = error {
alert = Alert(error)
return
}
guard let data = data else {
return
}
// Parse the user info response
if let invites = try? JSONDecoder().decode(InviteReponse.self, from: data) {
if let invite = invites.data?.codes?.first?.code {
self.inviteCode = invite
if copy {
UIPasteboard.general.string = UserManager.shared.mainregisterURL()+inviteCode
alert = Alert(okMessage: "复制成功")
}
}else{
//
Task{
await genoneInvite()
}
}
}else{
alert = Alert(errorMessage: "访问失败")
}
}
}
task.resume()
}
public func genoneInvite() async {
let userInfoUrl = URL(string: "\(UserManager.shared.baseURL())user/invite/save")!
var request = URLRequest(url: userInfoUrl)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(UserManager.shared.getAutoData(), forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
if let error = error {
alert = Alert(error)
return
}
guard let data = data else {
return
}
// Parse the user info response
if let _ = try? JSONDecoder().decode(InviteReponse.self, from: data) {
Task{
await reloadInvite(copy: false)
}
// if invites.status == "success" {}
/**
{
"status": "success",
"message": "\u64cd\u4f5c\u6210\u529f",
"data": true,
"error": null
}
*/
}
}
}
task.resume()
}
}
@@ -1,182 +0,0 @@
//
// ForgotPassword.swift
// LoginKit
//
// Created by Balaji on 04/08/23.
//
import SwiftUI
struct ForgotPassword: View {
@Binding var showResetView: Bool
/// View Properties
//@State private var emailID: String = ""
@Binding var emailID: String
/// Environment Properties
@Environment(\.dismiss) private var dismiss
@State private var askOTP: Bool = false
//@State private var otpText: String = ""
@Binding var otpText: String
@State private var isLoading = false
@State private var errorMessage: String?
var body: some View {
VStack(alignment: .leading, spacing: 15, content: {
/// Back Button
Button(action: {
dismiss()
}, label: {
Image(systemName: "arrow.left")
.font(.title2)
.foregroundStyle(.gray)
})
.padding(.top, 10)
Text("忘记密码?")
.font(.largeTitle)
//.fontWeight(.heavy)
.padding(.top, 5)
Text("请输入您的电子邮件,以便我们可以发送验证码。")
.font(.caption)
//.fontWeight(.semibold)
.foregroundStyle(.gray)
.padding(.top, -5)
VStack(spacing: 25) {
/// Custom Text Fields
CustomTF(sfIcon: "at", hint: "输入邮箱", value: $emailID)
if isLoading {
ProgressView()
}
if let errorMessage = errorMessage {
Text(errorMessage)
.bold()
.foregroundColor(.red)
.multilineTextAlignment(.center) //
.padding()
}
/// SignUp Button
GradientButton(title: "发送验证码", icon: "arrow.right") {
/// YOUR CODE
/// After the Link sent
Task {
await sendemail(email:emailID)
}
}
.hSpacing(.trailing)
/// Disabling Until the Data is Entered
.disableWithOpacity(emailID.isEmpty || isLoading)
}
.padding(.top, 20)
Spacer(minLength: 0)
})/// OTP Prompt
.sheet(isPresented: $askOTP, onDismiss: {
/// YOUR CODE
/// Reset OTP if You Want
// otpText = ""
}, content: {
if #available(iOS 16.4, *) {
/// Since I wanted a Custom Sheet Corner Radius
OTPView(otpText: $otpText,onButtonClick: {
print(otpText)
Task {
dismiss()
// try? await Task.sleep(for: .seconds(0))
/// Showing the Reset View
showResetView = true
}
})
.presentationDetents([.height(350)])
.presentationCornerRadius(30)
} else {
if #available(iOS 16.0, *) {
OTPView(otpText: $otpText,onButtonClick: {
print(otpText)
Task {
dismiss()
// try? await Task.sleep(for: .seconds(0))
/// Showing the Reset View
showResetView = true
}
}).presentationDetents([.height(350)])
} else {
OTPView(otpText: $otpText,onButtonClick: {
print(otpText)
Task {
dismiss()
// try? await Task.sleep(for: .seconds(0))
/// Showing the Reset View
showResetView = true
}
})
// Fallback on earlier versions
}
}
})
.padding(.vertical, 15)
.padding(.horizontal, 25)
/// Since this is going to be a Sheet.
.interactiveDismissDisabled()
}
@MainActor
public func sendemail(email: String) async {
isLoading = true
errorMessage = nil
let userInfoUrl = URL(string: "\(UserManager.shared.baseURL())passport/comm/sendEmailVerify?email=\(email)&recaptcha_data=")!
var request = URLRequest(url: userInfoUrl)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(UserManager.shared.getAutoData(), forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
self.isLoading = false
if let error = error {
self.errorMessage = "验证码发送失败: \(error.localizedDescription)"
return
}
guard let data = data else {
self.errorMessage = "验证码发送失败:数据返回空"
return
}
// Parse the user info response
if let message = try? JSONDecoder().decode(MessageResponse.self, from: data) {
dump(message)
if let _ = message.errors{
self.errorMessage = message.message
}else{
askOTP.toggle()
}
}
}
}
task.resume()
}
}
@@ -1,693 +0,0 @@
//
// Login.swift
// LoginKit
//
// Created by Balaji on 04/08/23.
//
import SwiftUI
import Crisp
// MARK: - Errors
struct Errors: Codable {
let email: [String]?
}
struct LoginResponseSuccess: Codable {
let data: LoginResponseSuccessClass?
let message: String?
let status: String?
let errors: Errors?
}
// MARK: - DataClass
struct LoginResponseSuccessClass: Codable {
let token: String
let isAdmin: Int?
let authData: String
enum CodingKeys: String, CodingKey {
case token
case isAdmin = "is_admin"
case authData = "auth_data"
}
}
struct Login: View {
@Binding var isLoggedIn: Bool
@Binding var showSignup: Bool
/// View Properties
@State private var emailID: String = ""
@State private var password: String = ""
@State private var showForgotPasswordView: Bool = false
/// Reset Password View (with New Password and Confimration Password View)
@State private var showResetView: Bool = false
/// Optional, Present If you want to ask OTP for login
@State private var askOTP: Bool = false
@State private var otpText: String = ""
@State private var showKefuView: Bool = false
@State private var userInfo: UserInfo? = nil
@State private var isLoading = false
@State private var errorMessage: String?
@State private var alert: Alert?
@State private var okMessage: String?
@FocusState private var focusedField: Bool
@State private var isresetpwdLoading = false
@State private var resetpwdErrorMessage = ""
@AppStorage("isFirstOpen") private var isFirstOpen: Bool?
@State var onboardingItems: [OnboardingItem] = [
.init(title: "最稳定的加速器",
subTitle: "順暢瀏覽社交媒體,同時支持Netflix及Youtube等流媒體內容平台,4K清晰度不卡頓。",
lottieView: .init(name: "12c853b3",bundle: .main)),
.init(title: "永不跑路的VPN加速器",
subTitle: "一鍵訪問匿名互聯網,告別繁瑣的配置,跨平臺設備支持,隨時隨地連接網絡。",
lottieView: .init(name: "2c630b55",bundle: .main)),
.init(title: "加密访问,全球畅连",
subTitle: "隱藏您的IP地址。互聯網流量通過TLS加密保護免受任何黑客探測、監控和攻擊。",
lottieView: .init(name: "5a519db7",bundle: .main))
]
// MARK: Current Slide Index
@State var currentIndex: Int = 0
var onBoardView: some View {
GeometryReader{
let size = $0.size
VStack(spacing: 40, content: {
Spacer().frame(height: 10)
HStack(spacing: 0){
ForEach($onboardingItems) { $item in
let isLastSlide = (currentIndex == onboardingItems.count - 1)
VStack{
// MARK: Top Nav Bar
HStack{
Button(action: {
if currentIndex > 0{
currentIndex -= 1
playAnimation()
}
}, label: {
Image(systemName: "arrow.backward")
}).tint(Color("Main"))
.opacity(currentIndex > 0 ? 1 : 0)
Spacer(minLength: 0)
// Button(""){
// currentIndex = onboardingItems.count - 1
// playAnimation()
// }
// .opacity(isLastSlide ? 0 : 1)
}
.animation(.easeInOut, value: currentIndex)
.tint(Color("Main"))
//.fontWeight(.bold)
// MARK: Movable Slides
VStack(spacing: 15){
let offset = -CGFloat(currentIndex) * size.width
// MARK: Resizable Lottie View
ResizableLottieView(onboardingItem: $item)
.frame(height: size.width*0.8)
.onAppear {
// MARK: Intially Playing First Slide Animation
if currentIndex == indexOf(item){
item.lottieView.play(toProgress: 0.7)
}
}
.offset(x: offset)
.animation(.easeInOut(duration: 0.5), value: currentIndex)
Text(item.title)
.font(.title.bold())
.offset(x: offset)
.animation(.easeInOut(duration: 0.5).delay(0.1), value: currentIndex)
Text(item.subTitle)
.font(.system(size: 14))
.multilineTextAlignment(.center)
.padding(.horizontal,15)
.foregroundColor(.gray)
.offset(x: offset)
.animation(.easeInOut(duration: 0.5).delay(0.2), value: currentIndex)
}
Spacer(minLength: 0)
// MARK: Next / Login Button
VStack(spacing: 15){
Button(isLastSlide ? "登录" : "下一步"){
if currentIndex < onboardingItems.count - 1{
// MARK: Pausing Previous Animation
let currentProgress = onboardingItems[currentIndex].lottieView.currentProgress
onboardingItems[currentIndex].lottieView.currentProgress = (currentProgress == 0 ? 0.7 : currentProgress)
currentIndex += 1
// MARK: Playing Next Animation from Start
playAnimation()
}
if isLastSlide {
withAnimation(){
isFirstOpen = false
}
}
}
// .fontWeight(.bold)
.foregroundColor(.white)
.padding(.vertical,isLastSlide ? 13 : 12)
.frame(maxWidth: .infinity)
.background {
Capsule()
.fill(Color("Main2"))
}
.padding(.horizontal,isLastSlide ? 50 : 100)
if currentIndex == onboardingItems.count - 1{
HStack{
// Button("Terms of Service"){}
// Button("Privacy Policy"){}
}
.font(.caption2)
//.underline(true, color: .primary)
.offset(y: 5)
}
}.onTapGesture {
if currentIndex < onboardingItems.count - 1{
// MARK: Pausing Previous Animation
let currentProgress = onboardingItems[currentIndex].lottieView.currentProgress
onboardingItems[currentIndex].lottieView.currentProgress = (currentProgress == 0 ? 0.7 : currentProgress)
currentIndex += 1
// MARK: Playing Next Animation from Start
playAnimation()
}
if isLastSlide {
withAnimation(){
isFirstOpen = false
}
}
}
}
.animation(.easeInOut, value: isLastSlide)
.padding(15)
.frame(width: size.width, height: size.height)
}
}
.frame(width: size.width * CGFloat(onboardingItems.count),alignment: .leading)
}).ignoresSafeArea(.all)
// if !#available(iOS 15.0, *) {
//
// }
}
}
func playAnimation(){
onboardingItems[currentIndex].lottieView.currentProgress = 0
onboardingItems[currentIndex].lottieView.play(toProgress: 0.7)
}
// MARK: Retreving Index of the Item in the Array
func indexOf(_ item: OnboardingItem)->Int{
if let index = onboardingItems.firstIndex(of: item){
return index
}
return 0
}
var body: some View {
if isFirstOpen ?? true {
onBoardView
}else{
loginView
}
}
var loginView: some View {
VStack(alignment: .leading, spacing: 15, content: {
Spacer(minLength: 0)
// HStack{
// Spacer()
// Image("applogo").resizable().frame(width: 100, height: 100).cornerRadius(20)
// Spacer()
// }
Text("登录")
.font(.largeTitle)
//.fontWeight(.heavy)
Text("请按登录按钮继续")
.font(.callout)
//.fontWeight(.semibold)
.foregroundStyle(.gray)
.padding(.top, -5)
VStack(spacing: 25) {
/// Custom Text Fields
CustomTF(sfIcon: "at", hint: "Email", value: $emailID)
CustomTF(sfIcon: "lock", hint: "密码", isPassword: true, value: $password)
.padding(.top, 5).focused($focusedField)
HStack(content: {
Button("忘记密码?") {
showForgotPasswordView.toggle()
}
.font(.callout)
//.fontWeight(.heavy)
.tint(.white)
Button {
showKefuView.toggle()
} label: {
Text("联系客服") .font(.callout)
Image(systemName: "person.crop.circle.badge.questionmark")
}.tint(.white)
})
.hSpacing(.trailing)
/// Login Button
///
if isLoading {
ProgressView()
}
if let errorMessage = errorMessage {
Text(errorMessage)
.bold()
.foregroundColor(.red)
.multilineTextAlignment(.center) //
.padding()
}
if let okmsg = okMessage {
Text(okmsg)
.bold()
.foregroundColor(Color("Main"))
.multilineTextAlignment(.center) //
.padding()
}
if let userInfo = userInfo {
Text("Logged in as: \(userInfo.data.email)")
// AsyncImage(url: URL(string: userInfo.data.avatar_url))
// .frame(width: 64, height: 64)
// .clipShape(Circle())
}
GradientButton(title: "登录", icon: "arrow.right") {
/// YOUR CODE
//askOTP.toggle()
Task{
await loginUser()
}
}
.hSpacing(.trailing)
/// Disabling Until the Data is Entered
.disableWithOpacity(emailID.isEmpty || password.isEmpty || isLoading)
}
.padding(.top, 20)
Spacer(minLength: 0)
HStack(spacing: 6) {
Text("没有账号?")
.foregroundStyle(.gray)
Button("注册") {
showSignup.toggle()
}
//.fontWeight(.bold)
.tint(.white)
}
.font(.callout)
.hSpacing()
})
.onAppear(){
// config
Task{
await getConfig()
}
}
.padding(.vertical, 15)
.padding(.horizontal, 25)
//.toolbar(.hidden, for: .navigationBar)
/// Asking Email ID For Sending Reset Link
.sheet(isPresented: $showForgotPasswordView, content: {
if #available(iOS 16.4, *) {
/// Since I wanted a Custom Sheet Corner Radius
ForgotPassword(showResetView: $showResetView, emailID: $emailID, otpText: $otpText)
.presentationDetents([.height(300)])
.presentationCornerRadius(30)
} else {
ForgotPassword(showResetView: $showResetView, emailID: $emailID, otpText: $otpText)
// .presentationDetents([.height(300)])
}
})
.fullScreenCover(isPresented: $showKefuView, content: {
SupportView()
})
/// Resetting New Password
.sheet(isPresented: $showResetView, content: {
if #available(iOS 16.4, *) {
/// Since I wanted a Custom Sheet Corner Radius
PasswordResetView(isresetpwdLoading: $isresetpwdLoading, resetpwdErrorMessage: $resetpwdErrorMessage) { password in
print(otpText + " " + password)
Task{
await resetpwd(password:password)
}
}
.presentationDetents([.height(350)])
.presentationCornerRadius(30)
} else {
PasswordResetView(isresetpwdLoading: $isresetpwdLoading, resetpwdErrorMessage: $resetpwdErrorMessage){password in
print(otpText + " " + password)
Task{
await resetpwd(password:password)
}
}
// .presentationDetents([.height(350)])
}
})
.alertBinding($alert)
/// OTP Prompt
.sheet(isPresented: $askOTP, onDismiss: {
/// YOUR CODE
/// Reset OTP if You Want
// otpText = ""
}, content: {
if #available(iOS 16.4, *) {
/// Since I wanted a Custom Sheet Corner Radius
OTPView(otpText: $otpText)
.presentationDetents([.height(350)])
.presentationCornerRadius(30)
} else {
OTPView(otpText: $otpText)
//.presentationDetents([.height(350)])
}
})
}
func resetpwd(password:String) async {
isresetpwdLoading = true
let loginUrl = URL(string: "\(UserManager.shared.baseURL())passport/auth/forget?email=\(emailID)&password=\(password)&email_code=\(otpText)")!
var request = URLRequest(url: loginUrl)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
print(loginUrl)
let loginData = ["email": emailID, "password": password, "email_code": otpText]
guard let httpBody = try? JSONSerialization.data(withJSONObject: loginData) else {
self.resetpwdErrorMessage = "请求格式错误"
self.isresetpwdLoading = false
return
}
request.httpBody = httpBody
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
isresetpwdLoading = false
if let error = error {
resetpwdErrorMessage = ( "密码重置失败: \(error.localizedDescription)")
return
}
guard let data = data else {
resetpwdErrorMessage = ("密码重置失败: 数据为空")
return
}
struct resetpwdResponseSuccess: Codable {
let message: String?
let status: String?
}
// Parse the login response and get the authorization token
if let jsonResponse = try? JSONDecoder().decode(resetpwdResponseSuccess.self, from: data){
dump(jsonResponse)
if let status = jsonResponse.status , status == "success" {
withAnimation {
showResetView = false
}
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {
alert = Alert(okMessage: "密码重置成功!")
}
}else{
if let message = jsonResponse.message {
resetpwdErrorMessage = ( "\(message)")
}else{
resetpwdErrorMessage = ( "密码重置失败: 未知错误")
}
}
}else{
resetpwdErrorMessage = ("密码重置失败: 数据 json 格式错误")
}
}
}
task.resume()
}
// MARK: - Networking Logic
func getConfig() async {
let loginUrl = URL(string: UserManager.shared.configURL)!
var request = URLRequest(url: loginUrl)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(Bundle.main.bundleIdentifier ?? "", forHTTPHeaderField: "bid")
request.addValue(UserManager.shared.appversion, forHTTPHeaderField: "appver")
await request.addValue(UIDevice.current.model, forHTTPHeaderField: "model")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
if let _ = error {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) {
Task{
await getConfig()
}
}
return
}
guard let data = data else {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) {
Task{
await getConfig()
}
}
return
}
// Parse the login response and get the authorization token
struct configReponse: Codable {
let baseURL, mainregisterURL,paymentURL,crisptoken: String
let telegramurl, kefuurl, websiteURL,baseDYURL: String
let message: String
let code: Int
}
if let jsonString = String(data: data, encoding: .utf8) {
print("Response data: \(jsonString)")
}
if let jsonResponse = try? JSONDecoder().decode(configReponse.self, from: data){
// dump(jsonResponse)
if jsonResponse.code == 1 {
//save data
UserManager.shared.storebaseURLData(data: jsonResponse.baseURL)
UserManager.shared.storemainregisterURLData(data: jsonResponse.mainregisterURL)
UserManager.shared.storepaymentURLData(data: jsonResponse.paymentURL)
UserManager.shared.storetelegramUrlData(data: jsonResponse.telegramurl)
UserManager.shared.storekefuUrlData(data: jsonResponse.kefuurl)
UserManager.shared.storewebsiteURLData(data: jsonResponse.websiteURL)
UserManager.shared.storebaseDYURL(data: jsonResponse.baseDYURL)
//crisptoken
CrispSDK.configure(websiteID: jsonResponse.crisptoken)
}
}else{
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) {
Task{
await getConfig()
}
}
}
}
}
task.resume()
}
func loginUser() async {
isLoading = true
errorMessage = nil
focusedField = false
let loginUrl = URL(string: "\(UserManager.shared.baseURL())passport/auth/login")!
var request = URLRequest(url: loginUrl)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let loginData = ["email": emailID, "password": password, "captchaData": ""]
guard let httpBody = try? JSONSerialization.data(withJSONObject: loginData) else {
self.errorMessage = "Invalid login data"
self.isLoading = false
return
}
request.httpBody = httpBody
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
self.isLoading = false
if let error = error {
self.errorMessage = "登录失败: \(error.localizedDescription)"
return
}
guard let data = data else {
self.errorMessage = "登录失败: 数据为空"
return
}
// Parse the login response and get the authorization token
if let jsonResponse = try? JSONDecoder().decode(LoginResponseSuccess.self, from: data){
dump(jsonResponse)
if let _ = jsonResponse.errors, let message = jsonResponse.message{
// Proceed to fetch user info
// fetchUserInfo(token: token)
self.errorMessage = "\(message)"
}else{
if let authData = jsonResponse.data?.authData {
UserManager.shared.updateLoginStatus(true)
UserManager.shared.storeAutoData(data: authData)
self.okMessage = "登录成功,正在跳转..."
self.isLoading = true
UserManager.shared.storeUserInfo(email: emailID, avator: "")
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {
self.isLoggedIn = true
}
//
// Task{
// await fetchUserInfo(token: authData)
// }
}else{
if let message = jsonResponse.message {
self.errorMessage = "\(message)"
}
}
}
}else{
self.errorMessage = "登录失败: 数据 json 格式错误"
}
//{"data":{"token":"880e8785746c0bf72b2e01a882a678e7","is_admin":1,"auth_data":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6Miwic2Vzc2lvbiI6Ijc4MzE1Yjc5YmNlNTliYWIzOTg5MTIzMTFhNDkwN2NiIn0.00fdwJ85bOSycfnkOPmhF7pSR1VV9WczBEeE9aXncQQ"}}
}
}
task.resume()
}
func fetchUserInfo(token: String) async {
let userInfoUrl = URL(string: "\(UserManager.shared.baseURL())user/info")!
var request = URLRequest(url: userInfoUrl)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("\(token)", forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
if let error = error {
self.errorMessage = "Failed to get user info: \(error.localizedDescription)"
return
}
guard let data = data else {
self.errorMessage = "No user data received"
return
}
// Parse the user info response
if let userInfo = try? JSONDecoder().decode(UserInfo.self, from: data) {
self.userInfo = userInfo
// Save user info to local storage
UserManager.shared.saveUserInfoToLocal(userInfo: userInfo)
UserManager.shared.storeUserInfo(email: userInfo.data.email, avator: userInfo.data.avatar_url)
//
let isLoggedIn = UserManager.shared.isUserLoggedIn()
let userInfo = UserManager.shared.getUserInfo()
let autoData = UserManager.shared.getAutoData()
print("Logged in: \(isLoggedIn)")
print("Email: \(userInfo.email), avator: \(userInfo.avator)")
print("Auto Data: \(autoData)")
self.isLoggedIn = true
}
}
}
task.resume()
}
// Save user info to local storage (UserDefaults)
/**
{"data":{"email":"ceshi1@qq.com","transfer_enable":119185342464,"device_limit":null,"last_login_at":1725893596,"created_at":1725893596,"banned":0,"remind_expire":1,"remind_traffic":1,"expired_at":null,"balance":0,"commission_balance":0,"plan_id":1,"discount":null,"commission_rate":null,"telegram_id":null,"uuid":"0b0d55e3-9f6f-4ee0-9a73-2901655cfaf2","avatar_url":"https:\/\/cravatar.cn\/avatar\/845735bc7a6186ae5bad56e7cb87d88b?s=64&d=identicon"}}
*/
}
@@ -1,75 +0,0 @@
//
// ContentView.swift
// LoginKit
//
// Created by Balaji on 04/08/23.
//
import SwiftUI
struct LoginContentView: View {
@Binding var isLoggedIn: Bool
/// View Properties
@State private var showSignup: Bool = false
/// Keyboard Status
@State private var isKeyboardShowing: Bool = false
var body: some View {
NavigationView{
if showSignup {
// showSignup true SignUp
SignUp(isLoggedIn: $isLoggedIn,showSignup: $showSignup).onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification), perform: { _ in
/// Disabling it for signup view
if !showSignup {
isKeyboardShowing = true
}
})
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification), perform: { _ in
isKeyboardShowing = false
}).background(BackgroundBg())
} else {
/// Checking if any Keyboard is Visible
// showSignup false Login
Login(isLoggedIn: $isLoggedIn, showSignup: $showSignup).onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification), perform: { _ in
/// Disabling it for signup view
if !showSignup {
isKeyboardShowing = true
}
})
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification), perform: { _ in
isKeyboardShowing = false
}).background(BackgroundBg())
}
}
.preferredColorScheme(.dark)
//
// /// iOS 17 Bounce Animations
// if #available(iOS 17, *) {
// /// Since this Project Supports iOS 16 too.
// CircleView()
// .animation(.smooth(duration: 0.45, extraBounce: 0), value: showSignup)
// .animation(.smooth(duration: 0.45, extraBounce: 0), value: isKeyboardShowing)
// } else {
// CircleView()
// .animation(.easeInOut(duration: 0.3), value: showSignup)
// .animation(.easeInOut(duration: 0.3), value: isKeyboardShowing)
// }
// }
}
/// Moving Blurred background
@ViewBuilder
func CircleView() -> some View {
Circle()
.fill(.linearGradient(colors: [.main, .main2], startPoint: .top, endPoint: .bottom))
.frame(width: 200, height: 200)
/// Moving When the Signup Pages Loads/Dismisses
.offset(x: showSignup ? 90 : -90, y: -90 - (isKeyboardShowing ? 200 : 0))
.blur(radius: 15)
.hSpacing(showSignup ? .trailing : .leading)
.vSpacing(.top)
}
}
@@ -1,62 +0,0 @@
//
// OTPView.swift
// LoginKit
//
// Created by Balaji on 04/08/23.
//
import SwiftUI
struct OTPView: View {
@Binding var otpText: String
/// Environment Properties
@Environment(\.dismiss) private var dismiss
var onButtonClick: (() -> Void)?
var body: some View {
VStack(alignment: .leading, spacing: 15, content: {
/// Back Button
Button(action: {
dismiss()
}, label: {
Image(systemName: "arrow.left")
.font(.title2)
.foregroundStyle(.gray)
})
.padding(.top, 15)
Text("输入验证码")
.font(.largeTitle)
//.fontWeight(.heavy)
.padding(.top, 5)
Text("一个 6 位数的验证码已发送至您的电子邮件。")
.font(.caption)
//.fontWeight(.semibold)
.foregroundStyle(.gray)
.padding(.top, -5)
VStack(spacing: 25) {
/// Custom OTP TextField
OTPVerificationView(otpText: $otpText)
/// SignUp Button
GradientButton(title: "下一步", icon: "arrow.right") {
/// YOUR CODE
onButtonClick?()
}
.hSpacing(.trailing)
/// Disabling Until the Data is Entered
.disableWithOpacity(otpText.isEmpty)
}
.padding(.top, 20)
Spacer(minLength: 0)
})
.padding(.vertical, 15)
.padding(.horizontal, 25)
/// Since this is going to be a Sheet.
.interactiveDismissDisabled()
}
}
@@ -1,74 +0,0 @@
//
// PasswordResetView.swift
// LoginKit
//
// Created by Balaji on 04/08/23.
//
import SwiftUI
struct PasswordResetView: View {
/// View Properties
@State private var password: String = ""
@State private var confirmPassword: String = ""
@Binding var isresetpwdLoading: Bool
@Binding var resetpwdErrorMessage : String
var onButtonClick: ((String) -> Void)?
/// Environment Properties
@Environment(\.dismiss) private var dismiss
var body: some View {
VStack(alignment: .leading, spacing: 15, content: {
/// Back Button
Button(action: {
dismiss()
}, label: {
Image(systemName: "xmark")
.font(.title2)
.foregroundStyle(.gray)
})
.padding(.top, 10)
Text("重置密码")
.font(.largeTitle)
//.fontWeight(.heavy)
.padding(.top, 5)
VStack(spacing: 25) {
/// Custom Text Fields
CustomTF(sfIcon: "lock", hint: "密码", value: $password)
CustomTF(sfIcon: "lock", hint: "再次确认密码", value: $confirmPassword)
.padding(.top, 5)
if isresetpwdLoading {
ProgressView()
}
if !resetpwdErrorMessage.isEmpty {
Text(resetpwdErrorMessage)
.bold()
.foregroundColor(.red)
.multilineTextAlignment(.center) //
.padding()
}
/// SignUp Button
GradientButton(title: "重置密码", icon: "arrow.right") {
/// YOUR CODE
/// Reset Password
onButtonClick?(password)
}
.hSpacing(.trailing)
/// Disabling Until the Data is Entered
.disableWithOpacity(password.isEmpty || confirmPassword.isEmpty || confirmPassword != password || isresetpwdLoading)
}
.padding(.top, 20)
Spacer(minLength: 0)
})
.padding(.vertical, 15)
.padding(.horizontal, 25)
/// Since this is going to be a Sheet.
.interactiveDismissDisabled()
}
}
@@ -1,214 +0,0 @@
//
// Signup.swift
// LoginKit
//
// Created by Balaji on 04/08/23.
//
import SwiftUI
struct SignUp: View {
@Binding var isLoggedIn: Bool
@Binding var showSignup: Bool
@State private var isLoading = false
@State private var errorMessage: String?
/// View Properties
@State private var emailID: String = ""
@State private var fullName: String = ""
@State private var password: String = ""
/// Optional, Present If you want to ask OTP for Signup
@State private var askOTP: Bool = false
@State private var otpText: String = ""
var body: some View {
VStack(alignment: .leading, spacing: 15, content: {
// HStack{
// Spacer()
// Image("applogo").resizable().frame(width: 100, height: 100).cornerRadius(20)
// Spacer()
// }
Text("注册")
.font(.largeTitle)
//.fontWeight(.heavy)
.padding(.top, 25)
Text("请按注册按钮继续")
.font(.callout)
//.fontWeight(.semibold)
.foregroundStyle(.gray)
.padding(.top, -5)
VStack(spacing: 25) {
/// Custom Text Fields
CustomTF(sfIcon: "at", hint: "Email", value: $emailID)
CustomTF(sfIcon: "lock", hint: "密码", isPassword: true, value: $password)
.padding(.top, 5)
CustomTF(sfIcon: "person", hint: "邀请人(可不填)", value: $fullName)
.padding(.top, 5)
Text("通过注册,你同意我们的[条款和条件](https://minipanda.soccertt.com/teams.html)和[隐私政策](https://minipanda.soccertt.com/privacy.html)")
.font(.caption)
.tint(.white)
.foregroundStyle(.gray)
.frame(height: 50)
if isLoading {
ProgressView()
}
if let errorMessage = errorMessage {
Text(errorMessage)
.bold()
.foregroundColor(.red)
.multilineTextAlignment(.center) //
.padding()
}
/// SignUp Button
GradientButton(title: "注册", icon: "arrow.right") {
/// YOUR CODE
//askOTP.toggle()
Task{
await logupUser()
}
}
.hSpacing(.trailing)
/// Disabling Until the Data is Entered || fullName.isEmpty
.disableWithOpacity(emailID.isEmpty || password.isEmpty || isLoading)
}
.padding(.top, 20)
Spacer(minLength: 0)
HStack(spacing: 6) {
Text("已经有账号?")
.foregroundStyle(.gray)
Button("登录") {
showSignup = false
}
//.fontWeight(.bold)
.tint(.white)
}
.font(.callout)
.hSpacing()
})
.padding(.vertical, 15)
.padding(.horizontal, 25)
//.toolbar(.hidden, for: .navigationBar)
/// OTP Prompt
.sheet(isPresented: $askOTP, onDismiss: {
/// YOUR CODE
/// Reset OTP if You Want
// otpText = ""
}, content: {
if #available(iOS 16.4, *) {
/// Since I wanted a Custom Sheet Corner Radius
OTPView(otpText: $otpText)
.presentationDetents([.height(350)])
.presentationCornerRadius(30)
} else {
OTPView(otpText: $otpText)
// .presentationDetents([.height(350)])
}
})
}
// MARK: - Networking Logic
func logupUser() async {
isLoading = true
errorMessage = nil
let loginUrl = URL(string: "\(UserManager.shared.baseURL())passport/auth/register")!
var request = URLRequest(url: loginUrl)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let loginData = ["email": emailID, "password": password, "captchaData": "","email_code":"","invite_code":fullName]
guard let httpBody = try? JSONSerialization.data(withJSONObject: loginData) else {
self.errorMessage = "Invalid login data"
self.isLoading = false
return
}
request.httpBody = httpBody
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
self.isLoading = false
if let error = error {
self.errorMessage = "注册失败: \(error.localizedDescription)"
return
}
guard let data = data else {
self.errorMessage = "注册失败:数据返回空"
return
}
// Parse the login response and get the authorization token
if let jsonResponse = try? JSONDecoder().decode(LoginResponseSuccess.self, from: data){
if let authData = jsonResponse.data?.authData {
UserManager.shared.updateLoginStatus(true)
UserManager.shared.storeAutoData(data: authData)
self.isLoggedIn = true
}else{
if let message = jsonResponse.message {
self.errorMessage = "\(message)"
}
}
}else{
self.errorMessage = "注册失败: 返回JSON格式错误"
}
}
}
task.resume()
}
}
/***
{
"status": "success",
"message": "\u64cd\u4f5c\u6210\u529f",
"data": {
"token": "56ffc6d0465212eca2856aa25dc1644e",
"is_admin": null,
"auth_data": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MTU0MDMsInNlc3Npb24iOiJiYzk3N2RhOTczN2RjZWVlM2FiNDc2NzAzMjBjOTI2OCJ9.WUPCzTlgsOMGM-DrsSMRrfBqgAB8GEysDyAofq6TgVo"
},
"error": null
}
*/
@@ -1,75 +0,0 @@
//
// MessageCenterView.swift
// SFI
//
// Created by Mac on 2024/10/24.
//
import Foundation
//
//
// QuestionView.swift
// SFI
//
// Created by Mac on 2024/10/24.
//
import ApplicationLibrary
import Foundation
import SwiftUI
struct MessageCenterView: View {
@Environment(\.presentationMode) var presentationMode //
var body: some View {
NavigationView(content: {
VStack{
ZStack{
HStack{
Button(action: {
presentationMode.wrappedValue.dismiss() //
}, label: {
Image(systemName: "chevron.left")
.foregroundColor(.white)
Text("返回")
.foregroundColor(.white)
}).padding(10) //
Spacer(minLength: 0)
}
Text("消息通知")
.fontWeight(.bold).lineLimit(1)
}
.padding()
// .padding(.top,edges.top)
.foregroundColor(.white)
Text("暂无消息").font(.subheadline).padding()
Spacer()
}.navigationBarHidden(true).edgesIgnoringSafeArea(.bottom).foregroundColor(.white).background(
LinearGradient(colors: [
Color("BG1"),
Color("BG1"),
Color("BG2"),
Color("BG2"),
], startPoint: .top, endPoint: .bottom))
})
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true) //
}
}
@@ -1,167 +0,0 @@
//
// OnBoardingScreen.swift
// AnimatedOnBoardingScreen
//
// Created by Balaji on 17/12/22.
//
import SwiftUI
import Lottie
struct OnBoardingScreen: View {
// MARK: OnBoarding Slides Model Data
@State var onboardingItems: [OnboardingItem] = [
]
// MARK: Current Slide Index
@State var currentIndex: Int = 0
var body: some View {
GeometryReader{
let size = $0.size
HStack(spacing: 0){
ForEach($onboardingItems) { $item in
let isLastSlide = (currentIndex == onboardingItems.count - 1)
VStack{
// MARK: Top Nav Bar
HStack{
Button(action: {
if currentIndex > 0{
currentIndex -= 1
playAnimation()
}
}, label: {
Image(systemName: "arrow.backward")
})
.opacity(currentIndex > 0 ? 1 : 0)
Spacer(minLength: 0)
Button("跳过"){
currentIndex = onboardingItems.count - 1
playAnimation()
}
.opacity(isLastSlide ? 0 : 1)
}
.animation(.easeInOut, value: currentIndex)
.tint(Color("Green"))
//.fontWeight(.bold)
// MARK: Movable Slides
VStack(spacing: 15){
let offset = -CGFloat(currentIndex) * size.width
// MARK: Resizable Lottie View
ResizableLottieView(onboardingItem: $item)
.frame(height: size.width)
.onAppear {
// MARK: Intially Playing First Slide Animation
if currentIndex == indexOf(item){
item.lottieView.play(toProgress: 0.7)
}
}
.offset(x: offset)
.animation(.easeInOut(duration: 0.5), value: currentIndex)
Text(item.title)
.font(.title.bold())
.offset(x: offset)
.animation(.easeInOut(duration: 0.5).delay(0.1), value: currentIndex)
Text(item.subTitle)
.font(.system(size: 14))
.multilineTextAlignment(.center)
.padding(.horizontal,15)
.foregroundColor(.gray)
.offset(x: offset)
.animation(.easeInOut(duration: 0.5).delay(0.2), value: currentIndex)
}
Spacer(minLength: 0)
// MARK: Next / Login Button
VStack(spacing: 15){
Button {
if currentIndex < onboardingItems.count - 1{
// MARK: Pausing Previous Animation
let currentProgress = onboardingItems[currentIndex].lottieView.currentProgress
onboardingItems[currentIndex].lottieView.currentProgress = (currentProgress == 0 ? 0.7 : currentProgress)
currentIndex += 1
// MARK: Playing Next Animation from Start
playAnimation()
}
if isLastSlide {
withAnimation(){
}
}
} label: {
Text(isLastSlide ? "登录" : "下一步")
.foregroundColor(.white)
.padding(.vertical, isLastSlide ? 13 : 12)
.background(
Capsule()
.fill(Color("Green"))
)
.frame(maxWidth: .infinity) //
.padding(.horizontal, isLastSlide ? 50 : 100)
.contentShape(Rectangle()) //
}
/* Button(isLastSlide ? "" : ""){
}
.foregroundColor(.white)
.padding(.vertical,isLastSlide ? 13 : 12)
.frame(maxWidth: .infinity)
.background {
Capsule()
.fill(Color("Green"))
}
.padding(.horizontal,isLastSlide ? 50 : 100)
*/
if currentIndex == onboardingItems.count - 1{
HStack{
// Button("Terms of Service"){}
// Button("Privacy Policy"){}
}
.font(.caption2)
//.underline(true, color: .primary)
.offset(y: 5)
}
}
}
.animation(.easeInOut, value: isLastSlide)
.padding(15)
.frame(width: size.width, height: size.height)
}
}
.frame(width: size.width * CGFloat(onboardingItems.count),alignment: .leading)
}
}
func playAnimation(){
onboardingItems[currentIndex].lottieView.currentProgress = 0
onboardingItems[currentIndex].lottieView.play(toProgress: 0.7)
}
// MARK: Retreving Index of the Item in the Array
func indexOf(_ item: OnboardingItem)->Int{
if let index = onboardingItems.firstIndex(of: item){
return index
}
return 0
}
}
//struct OnBoardingScreen_Previews: PreviewProvider {
// static var previews: some View {
// ContentView()
// }
//}
@@ -1,38 +0,0 @@
//
// ResizableLottieView.swift
// AnimatedOnBoardingScreen
//
// Created by Balaji on 17/12/22.
//
import SwiftUI
// MARK: Resizable Lottie View Without Background
struct ResizableLottieView: UIViewRepresentable{
@Binding var onboardingItem: OnboardingItem
func makeUIView(context: Context) -> UIView {
let view = UIView()
view.backgroundColor = .clear
setupLottieView(view)
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
}
func setupLottieView(_ to: UIView){
let lottieView = onboardingItem.lottieView
lottieView.loopMode = .loop
lottieView.backgroundColor = .clear
lottieView.translatesAutoresizingMaskIntoConstraints = false
// MARK: Applying Constraints
let constraints = [
lottieView.widthAnchor.constraint(equalTo: to.widthAnchor),
lottieView.heightAnchor.constraint(equalTo: to.heightAnchor),
]
to.addSubview(lottieView)
to.addConstraints(constraints)
}
}
@@ -1,256 +0,0 @@
//
// OrderListView.swift
// SFI
//
// Created by Mac on 2024/10/21.
//
import Foundation
import SwiftUI
struct OrderListView: View {
// @Binding var isPresented: Bool
@State private var planList: [OrderInfoDatum] = []
@State private var isLoading = false
@State private var errorMessage: String?
@State private var alert: Alert?
@Environment(\.openURL) private var openURL
@Environment(\.presentationMode) var presentationMode //
var body: some View {
NavigationView(content: {
VStack {
// Navigation Bar
ZStack{
HStack{
Button(action: {
presentationMode.wrappedValue.dismiss() //
}, label: {
Image(systemName: "chevron.left")
.foregroundColor(.white)
Text("返回")
.foregroundColor(.white)
}).padding(10) //
Spacer()
}
Spacer()
VStack(spacing: 5){
Text("我的订单")
.fontWeight(.bold).lineLimit(1).frame(width: UIScreen.main.bounds.width*0.5)
}
.foregroundColor(.white)
Spacer()
}
.padding(.all)
if isLoading {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
.padding()
}
if let errorMessage = errorMessage {
Text(errorMessage)
.bold()
.foregroundColor(.red)
.multilineTextAlignment(.center) //
.padding()
}
//
// // Subscription Plans
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 16) {
if planList.isEmpty {
// Text("Loading plans...") // Loading indicator
if !isLoading
{
Text("暂无记录").font(.caption)
.foregroundColor(.gray)
.multilineTextAlignment(.center) //
.padding()
}
} else {
ForEach(planList) { plan in
NavigationLink(destination: OrderPaymentView(orderinfo: plan )) {
OrderItemView(orderinfo: plan)
}
//
// Button(action: {
//
// }, label: {
// OrderItemView(orderinfo: plan)
// })
}
}
}
}.padding(.top, 20)
Spacer()
// Subscription Notice
Text("订阅须知:仅支持微信和支付宝付款")
.font(.footnote)
.foregroundColor(.gray)
.padding(.bottom, 20)
}
.navigationBarHidden(true)
.background(
BackgroundBg()
)
.onAppear(){
Task{
await getOrderList()
}
}.edgesIgnoringSafeArea(.bottom)
})
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true) //
.alertBinding($alert)
}
public func getOrderList() async {
isLoading = true
errorMessage = nil
let userInfoUrl = URL(string: "\(UserManager.shared.baseURL())user/order/fetch")!
var request = URLRequest(url: userInfoUrl)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(UserManager.shared.getAutoData(), forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
self.isLoading = false
if let error = error {
self.errorMessage = "数据请求失败: \(error.localizedDescription)"
return
}
guard let data = data else {
self.errorMessage = "数据请求失败"
return
}
// Parse the user info response
if let Subscribe = try? JSONDecoder().decode(OrderInfoReponse.self, from: data) {
if let data = Subscribe.data {
planList = data
}else{
self.errorMessage = Subscribe.message ?? ""
}
}else{
self.errorMessage = "数据请求失败: JSON 解析错误"
}
}
}
task.resume()
}
}
struct OrderItemView: View {
var orderinfo : OrderInfoDatum
var body: some View {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text(orderinfo.plan.name + " " + "\(orderinfo.plan.transferEnable ?? 0)GB/月")
.font(.headline)
.foregroundColor(.black).multilineTextAlignment(.leading)
Text("订单号:"+orderinfo.tradeNo)
.font(.headline)
.foregroundColor(.black).multilineTextAlignment(.leading)
//.fontWeight(orderinfo.status == 2 ? .bold : .regular)
Text("订单金额:¥\(String(format: "%.2f", orderinfo.totalAmount/100))")
.font(.subheadline)
.foregroundColor(.black)
HStack (spacing: 4) {
Text(orderinfo.status_zh)
.font(.caption)
.fontWeight(.bold)
.padding(2)
.background(orderinfo.status == 1 ? Color.green: (orderinfo.status == 0 ? Color.red : Color.gray) )
.cornerRadius(4)
.foregroundColor(.white)
.offset(y:10)
Text(orderinfo.period_zh)
.font(.caption)
.fontWeight(.bold)
.padding(2)
.background(Color("Main"))
.cornerRadius(4)
.foregroundColor(.white)
// .offset(x: -16, y:16)
.offset(y:10)
Spacer()
Text("创建时间:"+TimestampConverter.convertTimestampToDateString(orderinfo.createdAt))
.font(.caption)
.fontWeight(.bold)
.padding(2)
.foregroundColor(.gray)
// .offset(x: -16, y:16)
.offset(y:10)
}
}
}
.padding()
.background(Color.white)
.cornerRadius(10)
.shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 3)
.padding(.horizontal, 20)
}
}
//struct SubscriptionView_Previews: PreviewProvider {
// static var previews: some View {
// SubscriptionView()
// }
//}
@@ -1,445 +0,0 @@
//
// OrderPaymentView.swift
// SFI
//
// Created by Mac on 2024/10/21.
//
import Foundation
//
import SwiftUI
struct OrderPaymentView: View {
var orderinfo : OrderInfoDatum
@State private var alert: Alert?
@State private var isLoading = false
@State private var errorMessage: String?
@Environment(\.openURL) private var openURL
@State private var showAlert = false
@State private var payment :PaymentReponseDatum?
@State private var ischeckout = false
@Environment(\.presentationMode) var presentationMode //
var body: some View {
NavigationView(content: {
VStack{
// Navigation Bar
ZStack{
HStack{
Button(action: {
presentationMode.wrappedValue.dismiss() //
}, label: {
Image(systemName: "chevron.left")
.foregroundColor(.white)
Text("返回")
.foregroundColor(.white)
}).padding(10) //
Spacer()
if orderinfo.status == 0 {
Button(action: {
alert = Alert(YesOrNOMessage: "确定取消订单吗?"){
Task{
await cancelorder(orderNO: orderinfo.tradeNo)
}
}
}, label: {
Text("取消订单")
.foregroundColor(.red)
})
}
}
Spacer()
Text("订单详情")
.fontWeight(.bold).lineLimit(1).frame(width: UIScreen.main.bounds.width*0.5)
.foregroundColor(.white)
Spacer()
}
.padding(.all)
VStack(spacing: 20) {
ScrollView(.vertical, showsIndicators: false) {
// Product Details Section
VStack(alignment: .leading, spacing: 10) {
Text("订单详情")
.font(.headline)
.foregroundColor(.white)
HStack {
Text("订单号: ")
.font(.subheadline)
.foregroundColor(.white)
Text(orderinfo.tradeNo)
.font(.subheadline)
.foregroundColor(.white)
}
HStack {
Text("创建时间: ")
.font(.subheadline)
.foregroundColor(.white)
Text(TimestampConverter.convertTimestampToDateString(orderinfo.createdAt))
.font(.subheadline)
.foregroundColor(.white)
}
Spacer().frame(height: 20)
Text("商品详情")
.font(.headline)
.foregroundColor(.white)
HStack {
Text("商品名称: ")
.font(.subheadline)
.foregroundColor(.white)
Text(orderinfo.plan.name)
.font(.subheadline)
.foregroundColor(.yellow)
}
HStack {
Text("类型/周期: ")
.font(.subheadline)
.foregroundColor(.white)
Text(orderinfo.period_zh)
.font(.subheadline)
.foregroundColor(.white)
}
HStack {
Text("产品流量: ")
.font(.subheadline)
.foregroundColor(.white)
Text("\(orderinfo.plan.transferEnable ?? 0)GB/月")
.font(.subheadline)
.foregroundColor(.white)
}
// Pending payment stamp (badge)
Spacer()
HStack {
Spacer()
Text(orderinfo.status_zh)
.font(.subheadline)
.padding(10)
.background(orderinfo.status == 1 ? Color.green: (orderinfo.status == 0 ? Color.red : Color.gray))
.cornerRadius(10)
.foregroundColor(.white)
}
}
.padding()
.background(Color.black.opacity(0.2))
.cornerRadius(10)
// Payment Method Section
VStack(alignment: .leading) {
Text("支付方式")
.font(.headline)
.foregroundColor(.white)
if isLoading {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
.padding()
.frame(maxWidth: .infinity)
}
if let errorMessage = errorMessage {
Text(errorMessage)
.bold()
.foregroundColor(.red)
.multilineTextAlignment(.center) //
.padding()
.frame(maxWidth: .infinity)
}else{
Text("\(payment?.name ?? "") (\(payment?.handlingFeePercent ?? "")%手续费)")
.font(.subheadline)
.padding()
.frame(maxWidth: .infinity)
.background(Color.red.opacity(0.3))
.foregroundColor(.white)
.cornerRadius(10)
}
}
.padding()
.background(Color.black.opacity(0.2))
.cornerRadius(10)
// Order Summary Section
VStack(alignment: .leading) {
Text("订单摘要")
.font(.headline)
.foregroundColor(.white)
HStack {
Text("商品价格:")
.font(.subheadline)
.foregroundColor(.white)
Spacer()
Text("¥\(String(format: "%.2f", orderinfo.totalAmount/100))")
.font(.subheadline)
.foregroundColor(.white)
}
HStack {
Text("手续费:")
.font(.subheadline)
.foregroundColor(.white)
Spacer()
Text("¥\(String(format: "%.2f", shouxufei()))")
.font(.subheadline)
.foregroundColor(.white)
}
HStack {
Text("总计:")
.font(.subheadline)
.foregroundColor(.white)
Spacer()
Text("¥\(String(format: "%.2f", total()))")
.font(.headline)
.foregroundColor(.white)
}
}
.padding()
.background(Color.black.opacity(0.2))
.cornerRadius(10)
if orderinfo.status == 0 {
// Pay Now Button
Button(action: {
// Trigger the payment action
showAlert = true
}, label: {
HStack {
Image(systemName: "cart")
Text("立即支付")
if ischeckout {
ProgressView()
.progressViewStyle(CircularProgressViewStyle()).tint(.white)
}
}
.font(.headline)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue)
.cornerRadius(10)
}).disabled(ischeckout)
.padding()
.alert(isPresented: $showAlert) {
Alert(
title: Text("提示"),
message: Text("确认要支付吗?"),
primaryButton: .default(Text("确定"),action: {
Task{
if let paymentID = payment?.id {
await submitOrder(orderNO:orderinfo.tradeNo,paymentID:paymentID)
}
}
}),
secondaryButton: .cancel(Text("取消"))
)
}
}
}
}
}
.padding()
.navigationBarHidden(true)
.onAppear(){
Task{
await getPayment()
}
}
.edgesIgnoringSafeArea(.bottom)
.background(
BackgroundBg()
)
})
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true) //
.alertBinding($alert)
}
func shouxufei() -> Double {
return orderinfo.totalAmount/100 * (Double(payment?.handlingFeePercent ?? "") ?? 0.0)/100
}
func total() -> Double{
return orderinfo.totalAmount/100 + shouxufei()
}
func cancelorder(orderNO:String) async {
//cancel?trade_no=2024102108102956235125484
let userInfoUrl = URL(string: "\(UserManager.shared.baseURL())user/order/cancel?trade_no=\(orderNO)")!
var request = URLRequest(url: userInfoUrl)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(UserManager.shared.getAutoData(), forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
if let error = error {
alert = Alert(error)
return
}
guard let data = data else {
alert = Alert(errorMessage: "取消失败,返回数据为空")
return
}
struct cancelResponse: Codable {
let status, message: String?
}
// Parse the user info response
if let message = try? JSONDecoder().decode(cancelResponse.self, from: data) {
if let status = message.status,status == "success" {
alert = Alert(okMessage: "取消成功", {
presentationMode.wrappedValue.dismiss() //
})
}else{
alert = Alert(errorMessage: message.message ?? "" )
}
//2024101311320180720&sitename=
}
}
}
task.resume()
}
func submitOrder(orderNO:String,paymentID:Int) async {
ischeckout = true
let userInfoUrl = URL(string: "\(UserManager.shared.baseURL())user/order/checkout?trade_no=\(orderNO)&method=\(paymentID)")!
var request = URLRequest(url: userInfoUrl)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(UserManager.shared.getAutoData(), forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
ischeckout = false
if let error = error {
alert = Alert(error)
return
}
guard let data = data else {
alert = Alert(errorMessage: "支付失败,返回数据为空")
return
}
struct saveOrderResponse: Codable {
let status, message, data: String?
}
// Parse the user info response
if let message = try? JSONDecoder().decode(saveOrderResponse.self, from: data) {
if let payurl = message.data,payurl.count > 10 {
openURL(URL(string:payurl)!)
}else{
alert = Alert(errorMessage: message.message ?? "" )
}
//2024101311320180720&sitename=
}
}
}
task.resume()
}
func getPayment() async {
isLoading = true
errorMessage = nil
let userInfoUrl = URL(string: "\(UserManager.shared.baseURL())user/order/getPaymentMethod")!
var request = URLRequest(url: userInfoUrl)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(UserManager.shared.getAutoData(), forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
self.isLoading = false
if let error = error {
self.errorMessage = "数据请求失败: \(error.localizedDescription)"
return
}
guard let data = data else {
self.errorMessage = "数据请求失败"
return
}
// Parse the user info response
if let Subscribe = try? JSONDecoder().decode(PaymentReponse.self, from: data) {
if let data = Subscribe.data {
payment = data.first
}else{
self.errorMessage = Subscribe.message ?? ""
}
}else{
self.errorMessage = "数据请求失败: JSON 解析错误"
}
}
}
task.resume()
}
}
//struct PaymentView_Previews: PreviewProvider {
// static var previews: some View {
// OrderPaymentView()
// }
//}
@@ -1,183 +0,0 @@
//
// SettingsAboutView.swift
// SFI
//
// Created by Mac on 2024/10/12.
//
import Foundation
//
import SwiftUI
struct SettingsAboutView: View {
@State private var alert: Alert?
@Environment(\.openURL) private var openURL //
@Environment(\.presentationMode) var presentationMode //
var body: some View {
NavigationView(content: {
VStack() {
ZStack{
HStack{
Button(action: {
presentationMode.wrappedValue.dismiss() //
}, label: {
Image(systemName: "chevron.left")
.foregroundColor(.white)
Text("返回")
.foregroundColor(.white)
}).padding(10) //
Spacer(minLength: 0)
}
Text("关于我们")
.fontWeight(.bold).lineLimit(1)
}
.padding()
// .padding(.top,edges.top)
.foregroundColor(.white)
Spacer().frame(height: 40)
// Logo
VStack {
Image("applogo")
.resizable()
.frame(width: 100, height: 100)
.clipShape(Circle())
.foregroundColor(.orange) // 使 Image("logo")
Text("小熊加速器 for iOS \n在您的 iPhone 和 iPad 上体验最快的全球网络连接工具")
.font(.subheadline)
.padding(.top, 10)
.frame(width: 300)
.multilineTextAlignment(.center)
}
Spacer().frame(height: 40)
//
List {
HStack {
Text("当前版本")
Spacer()
Text( Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0").foregroundColor(.gray)
}.padding(.vertical, 10).listRowBackground(Color.clear)
Button(action: {
alert = Alert(okMessage: "没有检测到更新,已经是最新版本.")
}, label: {
HStack {
Text("检查更新")
Spacer()
Image(systemName: "chevron.right").foregroundColor(.gray)
}.padding(.vertical, 10)
}).listRowBackground(Color.clear)
Button(action: {
if let url = URL(string: UserManager.shared.websiteURL()) {
openURL(url) { accepted in // completion URL OpenURLAction
print(accepted ? "Success" : "Failure")
}
}
}, label: {
HStack {
Text("官方网址")
Spacer()
Image(systemName: "chevron.right").foregroundColor(.gray)
}.padding(.vertical, 10)
}).listRowBackground(Color.clear)
Button(action: {
if let url = URL(string: UserManager.shared.telegramUrl()) {
openURL(url) { accepted in // completion URL OpenURLAction
print(accepted ? "Success" : "Failure")
}
}
}, label: {
HStack {
Text("订阅Telegram频道")
Spacer()
Image(systemName: "chevron.right").foregroundColor(.gray)
}.padding(.vertical, 10)
}).listRowBackground(Color.clear)
}
.listStyle(PlainListStyle()) //
Spacer()
//
VStack {
HStack {
Button(action: {
//
if let url = URL(string: "https://minipanda.soccertt.com/teams.html") {
openURL(url) { accepted in // completion URL OpenURLAction
print(accepted ? "Success" : "Failure")
}
}
}) {
Text("用户协议")
.foregroundColor(.white)
.font(.subheadline)
.underline()
}
Button(action: {
//
if let url = URL(string: "https://minipanda.soccertt.com/privacy.html") {
openURL(url) { accepted in // completion URL OpenURLAction
print(accepted ? "Success" : "Failure")
}
}
}) {
Text("隐私政策")
.foregroundColor(.white)
.font(.subheadline)
.underline()
}
}
.padding(.horizontal)
Text("Copyright 2014 - 2024, 小熊加速器 版权所有")
.font(.footnote)
.foregroundColor(.gray)
.padding(.top, 5)
}
.padding(.bottom, 10)
}
.background(
BackgroundBg()
)
.alertBinding($alert)
// .padding()
.navigationBarHidden(true)
// .edgesIgnoringSafeArea(.bottom)
})
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true) //
}
}
#Preview {
SettingsAboutView()
}
@@ -1,497 +0,0 @@
//
// SideMenuView.swift
// SFI
//
// Created by Mac on 2024/10/12.
//
import Foundation
import SwiftUI
import ApplicationLibrary
import WebKit
import Libbox
import Library
struct SideMenuView: View {
@Binding var isPresented: Bool
@Binding var isLoggedIn: Bool
@State private var isLoading = false
@State private var errorMessage: String?
@State private var plandes: SubscribeReponseClass?
@State private var size: CGSize = .zero
@State private var avator = UserManager.shared.getUserInfo().avator
@State private var emailid = UserManager.shared.getUserInfo().email
@EnvironmentObject private var environments: ExtensionEnvironments
@State private var alert: Alert?
@State private var showAlert = false
@State private var isInviteActive = false
@State private var isLogouting = false
@AppStorage("paymentURLKey") private var paymentURLKey = ""
var body: some View {
NavigationView {
VStack() {
// Navigation Bar
//
ZStack{
HStack{
Button(action: {
isPresented = false // Close the side menu
}, label: {
Image(systemName: "chevron.left")
.foregroundColor(.white)
Text("返回")
.foregroundColor(.white)
}).padding(10) //
Spacer(minLength: 0)
}
Text("设置")
.fontWeight(.bold).lineLimit(1)
}
.padding()
// .padding(.top,edges.top)
.foregroundColor(.white)
ScrollView(.vertical, showsIndicators: false) {
// Profile Section
VStack {
HStack {
// AsyncImage(url: URL(string: avator)){ phase in
// if let image = phase.image {
// image // Displays the loaded image.
// } else if phase.error != nil {
// Color.red // Indicates an error.
// } else {
// Color.blue // Acts as a placeholder.
// }
// }.frame(width: 50, height: 50)
// .clipShape(Circle())
// Image("applogo").resizable().frame(width: 50, height: 50).clipShape(Circle())
VStack(alignment: .leading, spacing: 5) {
Text("账户信息:")
.font(.headline).fontWeight(.light)
Text(emailid)
.font(.headline)
.bold()
}
Spacer()
/*
Spacer()
Button {
showAlert = true
} label: {
Text("退").foregroundColor(.red)
Image(systemName: "rectangle.portrait.and.arrow.forward")
.foregroundColor(.red)
.aspectRatio(contentMode: .fill)
.frame(width: 4, height: 4)
}*/
}
.padding()
.background(Color.white.opacity(0.1))
.cornerRadius(12)
.shadow(color: .gray.opacity(0.1), radius: 5, x: 0, y: 5)
if (paymentURLKey.count > 3){
HStack {
if isLoading {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
.padding()
}
if let errorMessage = errorMessage {
Text(errorMessage)
.bold()
.foregroundColor(.red)
.multilineTextAlignment(.center) //
.padding()
}else{
if let plandesss = self.plandes {
if plandesss.transferEnable > 0 {
VStack(alignment: .leading, spacing: 5) {
// Text("") .font(.subheadline)
Text(plandesss.plan?.name ?? "")
.font(.subheadline)
let u = plandesss.d/1024/1024/1024
let t = plandesss.transferEnable/1024/1024/1024
Spacer().frame(height: 10)
Text("已用: \(u) GB / 总计:\(t) GB")
.font(.subheadline).bold()
HStack {
// White bar on the left
// RoundedRectangle(cornerRadius: 10)
// .fill(Color.red)
// .frame(width: 300*CGFloat(plandesss.d)/CGFloat(plandesss.transferEnable), height: 20)
//
Text("").frame(height: 20).frame(minWidth: (CGFloat(plandesss.d)/CGFloat(plandesss.transferEnable))<=0.1 ? 20 : 300*(CGFloat(plandesss.d)/CGFloat(plandesss.transferEnable))).background(
RoundedRectangle(cornerRadius: 10)
.fill(
Color.red
)
)
Spacer() // Fill the remaining space to push the white rectangle to the left
}
.frame(height: 20) // Set the total width and height for the bar
.background(
RoundedRectangle(cornerRadius: 10)
.fill(
Color.gray.opacity(0.5)
)
)
.padding()
// ScrollView {
// }.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
// AttributedText(htmlContent: plan.content, size: $size)
// .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, idealHeight: size.height, maxHeight: .infinity)
// .background(.clear)
}
Spacer()
}else{
Spacer()
Text("暂无订阅")
.font(.subheadline)
Spacer()
}
}
}
}.padding()
.background(Color.white.opacity(0.1))
.cornerRadius(12)
.shadow(color: .gray.opacity(0.1), radius: 5, x: 0, y: 5)
}
} .frame(maxWidth: .infinity)
.padding()
.foregroundColor(.white)
// Menu Items
// List {
// NavigationLink(destination: WebShuntView()) {
// MenuItem(icon: "globe", title: "App", color: .white, isNew: false)
// }
LazyVStack(spacing: 10, content: {
NavigationLink(destination: SupportTicketView()) {
MenuItem(icon: "listclipboard", title: "我的工单", color: .white, isNew: false)
}
Divider() // 线
if (paymentURLKey.count > 3){
NavigationLink(destination: UpgradeView()) {
MenuItem(icon: "crown.fill", title: "升级套餐", color: .yellow, isNew: true)
}
Divider() // 线
NavigationLink(destination: OrderListView()) {
MenuItem(icon: "checkout", title: "我的订单", color: .white, isNew: false)
}
Divider() // 线
NavigationLink(destination: InviteListView(isPresented: $isInviteActive)) {
MenuItem(icon: "star.fill", title: "邀请中心", color: .white, isNew: false)
}
Divider() // 线
}else{
NavigationLink(destination: MessageCenterView()) {
MenuItem(icon: "message.badge.circle", title: "消息通知", color: .white, isNew: false)
}
Divider() // 线
}
NavigationLink(destination: QuestionView()) {
MenuItem(icon: "questionmark.app", title: "问题解答", color: .white, isNew: false)
}
Divider() // 线
NavigationLink(destination: SupportView()) {
MenuItem(icon: "headphones", title: "联系客服", color: .white, isNew: false)
}
Divider() // 线
NavigationLink(destination: AboutUsView()) {
MenuItem(icon: "info.circle.fill", title: "关于我们", color: .white, isNew: false)
}
Divider() // 线
}).padding()
Button(action: {
// Trigger the payment action
// showAlert = true
alert = Alert(YesOrNOMessage: "确定退出登录吗?", {
isLogouting=true
UserManager.shared.clearUserData()
//
Task {
//await reloadSubscribe()
}
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {
isLoggedIn = false
}
})
}, label: {
HStack {
Image(systemName: "trash.fill")
Text("退出登录")
}
.font(.headline)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue)
.cornerRadius(10)
}).padding()
}
// }
// .listStyle(PlainListStyle())
}.background(
BackgroundBg()
).modifier(ActivityIndicatorModifier(isLoading: isLogouting, color: Color.black.opacity(0.8), lineWidth: 1))
.onAppear {
Task {
await reloadSubscribe()
}
}.navigationBarHidden(true)
}.alertBinding($alert)
}
private func writeProfilePreviewList() async throws {
let profiles = try await ProfileManager.list()
for profile in profiles {
print("detele : \(profile.path) \(profile.remoteURL ?? "" )")
await deleteProfile(profile)
}
}
private func deleteProfile(_ profile: Profile) async {
do {
_ = try await ProfileManager.delete(profile)
} catch {
alert = Alert(error)
return
}
environments.profileUpdate.send()
}
@MainActor
public func reloadSubscribe() async {
isLoading = true
errorMessage = nil
let userInfoUrl = URL(string: "\(UserManager.shared.baseURL())user/getSubscribe")!
var request = URLRequest(url: userInfoUrl)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(UserManager.shared.getAutoData(), forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
self.isLoading = false
if let error = error {
self.errorMessage = "套餐数据请求失败: \(error.localizedDescription)"
return
}
guard let data = data else {
self.errorMessage = "套餐数据请求失败:数据为空"
return
}
// Parse the user info response
if let Subscribe = try? JSONDecoder().decode(SubscribeReponse.self, from: data) {
if let data = Subscribe.data {
plandes = data
emailid = data.email
UserManager.shared.storeUserInfo(email: emailid, avator: "")
// UserManager.shared.storeSuburlData(data: data.subscribeURL)
}else{
self.errorMessage = Subscribe.message ?? ""
}
}else{
self.errorMessage = "套餐数据请求失败"
}
}
}
task.resume()
}
// Sample Destination Views for each menu item
struct UpgradeView: View {
@State private var isSubscriptionActive = false
var body: some View {
SubscriptionView(isPresented: $isSubscriptionActive)
}
}
struct WebShuntView: View {
var body: some View {
VStack(alignment: .trailing, content: {
LogView()
ProfileView()
})
}
}
struct Free30DaysView: View {
@State private var islogined = false
var body: some View {
InviteView(isPresented: $islogined)
}
}
struct AboutUsView: View {
var body: some View {
SettingsAboutView()
}
}
}
struct MenuItem: View {
let icon: String
let title: String
let color: Color
let isNew: Bool?
var body: some View {
// Button {
//
// } label: {
//
// }
HStack {
if UIImage(systemName: icon) != nil{
Image(systemName: icon)
.foregroundColor(color).tint(.white)
.frame(width: 24, height: 24)
}else{
Image(icon).resizable()
.foregroundColor(.white).tint(.white)
.frame(width: 24, height: 24)
}
Text(title)
.foregroundColor(.white)
.font(.system(size: 18))
Spacer()
if isNew == true {
Circle()
.fill(Color.red)
.frame(width: 8, height: 8)
}
}
.padding(.vertical, 10)
}
}
struct AttributedText: UIViewRepresentable {
let htmlContent: String
@Binding var size: CGSize
private let webView = WKWebView()
var sizeObserver: NSKeyValueObservation?
func makeUIView(context: Context) -> WKWebView {
webView.scrollView.isScrollEnabled = false //<-- Here
webView.navigationDelegate = context.coordinator
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.loadHTMLString(htmlContent, baseURL: nil)
}
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
class Coordinator: NSObject, WKNavigationDelegate {
let parent: AttributedText
var sizeObserver: NSKeyValueObservation?
init(parent: AttributedText) {
self.parent = parent
sizeObserver = parent.webView.scrollView.observe(\.contentSize, options: [.new], changeHandler: { (object, change) in
parent.size = change.newValue ?? .zero
})
}
}
}
@@ -1,477 +0,0 @@
//
// SubscriptionView.swift
// SFI
//
// Created by Mac on 2024/10/12.
//
import Foundation
import SwiftUI
struct SubscriptionView: View {
@Binding var isPresented: Bool
@State private var planList: [DatuPlanResponse] = []
@State private var isLoading = false
@State private var errorMessage: String?
@State private var alert: Alert?
@State var orderinfo : OrderInfoDatum?
@Environment(\.openURL) private var openURL
let adimages = ["10-35-24", "10-35-15", "10-35-03"] // 广
let adimagesString = ["小熊加速器 采用最高安全 ECC 加密技术,仅需一键操作,军事级的加密技术将护航您的任何互联网访问", "小熊加速器 拥有170 个热门城市的300+高速服务器,您可以随时随地获得高速安全的互联网体验", "小熊加速器 支持所有当前主流的平台系统,您可以在几乎任何设备上使用"]
@Environment(\.presentationMode) var presentationMode //
//
@State private var isNavigationActive = false //
@State private var currentIndex = 0
//
let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
var body: some View {
NavigationView(content: {
VStack {
ZStack{
HStack{
Button(action: {
withAnimation {
isPresented = false // Close the side menu
}
presentationMode.wrappedValue.dismiss() //
}, label: {
Image(systemName: "chevron.left")
.foregroundColor(.white)
Text("返回")
.foregroundColor(.white)
}).padding(10) //
Spacer()
NavigationLink(destination: OrderListView()) {
Text("我的订单").foregroundColor(.red)
}.listRowBackground(Color.clear)
}
Spacer()
VStack(spacing: 5){
Text( "升级套餐")
.fontWeight(.bold).lineLimit(1).frame(width: UIScreen.main.bounds.width*0.5)
}
.foregroundColor(.white)
Spacer()
}
.padding(.all)
if let orderinfoS = orderinfo{
NavigationLink(destination: OrderPaymentView(orderinfo: orderinfoS),isActive: $isNavigationActive, label: {
EmptyView() //
})
}
//
// // Subscription Plans
ScrollView(.vertical, showsIndicators: false) {
// VPN Info and Trial Offer
VStack(spacing: 8) {
//
TabView(selection: $currentIndex) {
ForEach(0..<adimages.count, id: \.self) { index in
VStack(spacing: 8) {
Image(adimages[index])
.resizable()
.scaledToFill()
.frame(width: UIScreen.main.bounds.width * 0.8,height: 150)
.cornerRadius(10)
.tag(index)
Text(adimagesString[index])
.font(.subheadline)
.multilineTextAlignment(.center)
.foregroundColor(.gray)
.padding(.horizontal, 20)
}
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) //
.frame(height: 220)
.onReceive(timer) { _ in
// 广
withAnimation {
currentIndex = (currentIndex + 1) % adimages.count
}
}
//
HStack(spacing: 8) {
ForEach(0..<adimages.count, id: \.self) { index in
Circle()
.fill(index == currentIndex ? Color.green : Color.gray)
.frame(width: 5, height: 5)
}
}
// .padding(.top, 3)
// Free Trial Text
HStack(spacing: 8) {
Image(systemName: "checkmark.shield.fill")
.foregroundColor(.green)
Text("首次订阅免费试用 2 天")
.font(.title3)
.fontWeight(.bold)
}
.padding(.top, 10)
Text("订阅可随时取消,试用期内取消不收取任何费用")
.font(.footnote)
.foregroundColor(.gray)
}
.padding(.top, 20)
if isLoading {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
.padding()
}
if let errorMessage = errorMessage {
Text(errorMessage)
.bold()
.foregroundColor(.red)
.multilineTextAlignment(.center) //
.padding()
}
VStack(spacing: 16) {
if planList.isEmpty {
// Text("Loading plans...") // Loading indicator
} else {
ForEach(planList) { plan in
Button(action: {
//print( plan.content ?? "")
Task{
await subPlanOrder(plan_id:plan.idOLD ?? 0,amount : plan.monthPrice ?? 0)
}
}, label: {
SubscriptionPlanView(planName: plan.name, originalPrice: "¥\(String(format: "%.2f", Double(plan.monthPrice ?? 0)/100)) /月", discountedPrice: "", discountPercentage: "", monthlyPrice: "", content: plan.content ?? "", bestPlan: plan.idOLD == planList.count,planContent: "\(plan.transferEnable ?? 0)GB/月")
// Text( plan.content ?? "").padding()
})
}
}
}
}.padding(.top, 20)
Spacer()
// Subscription Notice
Text("订阅须知:仅支持微信和支付宝付款")
.font(.footnote)
.foregroundColor(.gray)
.padding(.bottom, 20)
}
.background(
LinearGradient(colors: [
Color("BG1"),
Color("BG1"),
Color("BG2"),
Color("BG2"),
], startPoint: .top, endPoint: .bottom)
)
// .background(.white)
.onAppear(){
Task{
await getPlanList()
}
}
.navigationBarHidden(true)
.edgesIgnoringSafeArea(.bottom)
})
.navigationBarHidden(true)
.alertBinding($alert)
.navigationBarBackButtonHidden(true) //
}
public func subPlanOrder(plan_id: Int,amount: Int) async {
let userInfoUrl = URL(string: "\(UserManager.shared.baseURL())user/order/save?plan_id=\(plan_id)&period=month_price&coupon_code=")!
var request = URLRequest(url: userInfoUrl)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(UserManager.shared.getAutoData(), forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
if let error = error {
alert = Alert(error)
return
}
guard let data = data else {
alert = Alert(errorMessage: "提交订单信息错误,请重试")
return
}
struct saveOrderResponse: Codable {
let status, message, data: String?
}
// Parse the user info response
if let message = try? JSONDecoder().decode(saveOrderResponse.self, from: data) {
if let dataNO = message.data,dataNO.count > 10 {
Task{
await getOrderInfo(OrderNO: dataNO)
}
}else{
alert = Alert(errorMessage: message.message ?? "" )
}
//2024101311320180720&sitename=
}
}
}
task.resume()
}
func getOrderInfo(OrderNO: String) async {
let userInfoUrl = URL(string: "\(UserManager.shared.baseURL())user/order/detail?trade_no=\(OrderNO)")!
var request = URLRequest(url: userInfoUrl)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(UserManager.shared.getAutoData(), forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
self.isLoading = false
if let error = error {
self.errorMessage = "数据请求失败: \(error.localizedDescription)"
return
}
guard let data = data else {
self.errorMessage = "数据请求失败"
return
}
// Parse the user info response
if let Subscribe = try? JSONDecoder().decode(OrderInfoSingleReponse.self, from: data) {
if let data = Subscribe.data {
orderinfo = data
isNavigationActive = true
}else{
self.errorMessage = Subscribe.message ?? ""
}
}else{
self.errorMessage = "数据请求失败: JSON 解析错误"
}
}
}
task.resume()
}
func getPlanList() async {
isLoading = true
errorMessage = nil
let userInfoUrl = URL(string: "\(UserManager.shared.baseURL())user/plan/fetch")!
var request = URLRequest(url: userInfoUrl)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(UserManager.shared.getAutoData(), forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
self.isLoading = false
if let error = error {
self.errorMessage = "数据请求失败: \(error.localizedDescription)"
return
}
guard let data = data else {
self.errorMessage = "数据请求失败"
return
}
// Parse the user info response
if let Subscribe = try? JSONDecoder().decode(PlanResponse.self, from: data) {
if let data = Subscribe.data {
planList = data
}else{
self.errorMessage = Subscribe.message ?? ""
}
}else{
self.errorMessage = "数据请求失败: JSON 解析错误"
}
}
}
task.resume()
}
}
struct SubscriptionPlanView: View {
var planName: String
var originalPrice: String
var discountedPrice: String
var discountPercentage: String
var monthlyPrice: String
var content: String
var bestPlan: Bool = false
var planContent: String
var body: some View {
VStack(spacing: 5, content: {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text(planName)
.font(.headline)
.fontWeight(bestPlan ? .bold : .regular)
.foregroundColor(bestPlan ? .green : .black)
Text(planContent).font(.subheadline).fontWeight(bestPlan ? .bold : .regular).foregroundColor( .black)
if bestPlan {
Text("Best")
.font(.caption)
.fontWeight(.bold)
.padding(4)
.background(Color.red)
.cornerRadius(4)
.foregroundColor(.white)
.offset(x: -15, y:16)
}
}
Spacer()
VStack(alignment: .trailing, spacing: 4) {
HStack {
/* if bestPlan {
Text("\(discountPercentage) OFF")
.font(.caption)
.padding(4)
.foregroundColor(.white).background(.green)
.cornerRadius(16)
}else{
Text("\(discountPercentage) OFF")
.font(.caption)
.padding(4)
.foregroundColor(.black).background(.gray.opacity(0.5))
.cornerRadius(16)
}
Text(discountedPrice)
.strikethrough()
.foregroundColor(.gray)
*/
Text(originalPrice)
.font(.title3)
.fontWeight(.bold)
.foregroundColor(.black)
}
Text(monthlyPrice)
.font(.footnote)
.foregroundColor(.gray)
}
}
// Text(extractChinese(from: content)).font(.subheadline).foregroundStyle(.black).padding()
//HTMLTextView(htmlString:content)
}).padding()
.background(Color.white.opacity(0.9))
.cornerRadius(10)
.shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 3)
.padding(.horizontal, 20)
}
func extractChinese(from text: String) -> String {
let pattern = "[\\u4e00-\\u9fa5]+" //
let regex = try? NSRegularExpression(pattern: pattern, options: [])
let nsString = text as NSString
let results = regex?.matches(in: text, options: [], range: NSRange(location: 0, length: nsString.length))
//
let chineseStrings = results?.compactMap { result -> String? in
if let range = Range(result.range, in: text) {
return String(text[range])
}
return nil
}
dump(chineseStrings)
return chineseStrings?.joined(separator: " ") ?? ""
}
}
//struct SubscriptionView_Previews: PreviewProvider {
// static var previews: some View {
// SubscriptionView()
// }
//}
@@ -1,749 +0,0 @@
//
// SupportTicketChatView.swift
// SFI
//
// Created by Mac on 2024/10/20.
//
import Foundation
import SwiftUI
import Combine
// 1. KeyboardResponder
class KeyboardResponder: ObservableObject {
@Published var keyboardHeight: CGFloat = 0
private var cancellable: AnyCancellable?
private var cancellableSet: Set<AnyCancellable> = []
init() {
/*
cancellable = NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)
.merge(with: NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification))
.sink { [weak self] notification in
if let userInfo = notification.userInfo {
if notification.name == UIResponder.keyboardWillShowNotification,
let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
self?.keyboardHeight = keyboardFrame.cgRectValue.height
print(" \(keyboardFrame.cgRectValue.height)" )
}
else {
print(" \(notification.name)" )
// self?.keyboardHeight = 0 // Reset height when keyboard hides
}
}
}
*/
/*
let showPublisher = NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)
let hidePublisher = NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)
showPublisher
.merge(with: hidePublisher)
.compactMap { notification in
//
guard let userInfo = notification.userInfo else { return 0 }
let endFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
return notification.name == UIResponder.keyboardWillShowNotification ? endFrame?.height ?? 0 : 0
}
.assign(to: \.keyboardHeight, on: self)
.store(in: &cancellableSet)
*/
NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)
.compactMap { $0.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect }
.map { $0.height }
.sink { [weak self] height in
self?.keyboardHeight = height
}
.store(in: &cancellableSet)
NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)
.map { _ in CGFloat.zero }
.assign(to: &$keyboardHeight)
}
}
struct SupportTicketChatView : View {
@State var message = ""
@State var imagePicker = false
@State var imgData : Data = Data(count: 0)
@State private var alert: Alert?
var ticketID:Int
@State private var isMessageInputBarPresented = false
//StateObject is the owner of the object....
@State private var alltickets: [Message] = []
@State private var isLoading = false
@State private var isSendingMsg = false
@State private var errorMessage: String?
@State private var showAlert = false
@State private var tickchatData: TicketChatReponseDataClass?
@ObservedObject private var keyboard = KeyboardResponder()
@StateObject var allMessages = Messages()
@Environment(\.presentationMode) var presentationMode //
var body: some View{
NavigationView(content: {
VStack{
Spacer()
ZStack{
HStack{
Button(action: {
presentationMode.wrappedValue.dismiss() //
}, label: {
Image(systemName: "chevron.left")
.foregroundColor(.white)
Text("返回")
.foregroundColor(.white)
}).padding(10) //
Spacer()
if tickchatData?.status == 0 {
Button(action: {
alert = Alert(YesOrNOMessage: "确定关闭工单吗?", {
Task{
await closeThisTicket()
}
})
}, label: {
Text("关闭工单")
.foregroundColor(.white)
})
}
}
Spacer()
VStack(spacing: 5){
Text(tickchatData?.subject ?? "")
.fontWeight(.bold).lineLimit(1).frame(width: UIScreen.main.bounds.width*0.5)
if tickchatData?.status == 0 {
Text("待回复").font(.caption).foregroundColor(.red)
}else if tickchatData?.status == 1 {
Text("已关闭").font(.caption).foregroundColor(.gray)
}else if tickchatData?.status == 2 {
Text("已回复").font(.caption).foregroundColor(.green)
}
}
.foregroundColor(.white)
Spacer()
}
.padding(.all)
VStack{
// Displaying Message....
if isLoading {
ProgressView()
.progressViewStyle(CircularProgressViewStyle()).tint(Color("Main"))
.padding().padding(.horizontal)
Spacer()
}
if let errorMessage = errorMessage {
Text(errorMessage)
.bold()
.foregroundColor(.red)
.multilineTextAlignment(.center) //
.padding(.horizontal)
Spacer()
}
if (alltickets.count > 0 ){
Spacer()
ScrollView(.vertical, showsIndicators: false, content: {
ScrollViewReader{reader in
VStack(spacing: 20){
ForEach(allMessages.messages){msg in
// Chat Bubbles...
Text("\(TimestampConverter.convertTimestampToDateString(msg.updatedAt))").font(.caption).foregroundColor(.black.opacity(0.8))
ChatBubble(msg: msg)
}
// when ever a new data is inserted scroll to bottom...
.onChange(of: allMessages.messages) { (value) in
// scrolling only user message...
//if value.last!.myMsg{}//
withAnimation {
reader.scrollTo(value.last?.id)
}
}
}
.padding([.horizontal,.bottom])
.padding(.top, 25)
}
})
}
}
// since bottom edge is ignored....
.padding(.bottom,getSafeAreaInsets().bottom)// + keyboard.keyboardHeight
.background((self.isLoading || errorMessage?.isEmpty==false)
?
AnyView(Color.clear.clipped())
:
AnyView(Color.white.clipShape(RoundedShape())))
}
.navigationBarHidden(true)
.edgesIgnoringSafeArea(.bottom)//
//.background(Color("bg").ignoresSafeArea(.all, edges: .all))
// .ignoresSafeArea(.all, edges: .top)
.background(
LinearGradient(colors: [
Color("BG1"),
Color("BG1"),
Color("BG2"),
Color("BG2"),
], startPoint: .top, endPoint: .bottom).ignoresSafeArea(.all, edges: .all))
.onAppear(){
Task{
await getPlanList()
}
}
})
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true) //
.alertBinding($alert)
.overlay(
GeometryReader { geometry in
VStack {
Spacer() // Pus
Group {
if isMessageInputBarPresented {
MessageInputBar(isPresented: $isMessageInputBarPresented, message: $message,isSendingMsg: $isSendingMsg) { message in
print("发送消息: \(message)")
Task{
await SendMsg(msg:message)
}
}//.shadow(radius: 5)
//.padding(.bottom, keyboard.keyboardHeight) // Adjust for keyboard height
//.animation(.easeIn(duration: 0.3), value: keyboard.keyboardHeight) // Smooth transition
}
}//.edgesIgnoringSafeArea(.bottom)
}.frame(height: geometry.size.height * 1 / 2) // 1/2
.offset(y: geometry.size.height / 2) // 1/2
}
)
//.background(Color("Color").edgesIgnoringSafeArea(.top))
// Full Screen Image Picker...
.fullScreenCover(isPresented: self.$imagePicker, onDismiss: {
// when ever image picker closes...
// verifying if image is selected or cancelled...
if self.imgData.count != 0{
//allMessages.writeMessage(id: Date(), msg: "", photo: self.imgData, myMsg: true, profilePic: "p1")
}
}) {
ImagePicker(imagePicker: self.$imagePicker, imgData: self.$imgData)
}
}
struct MessageInputBar: View {
@Binding var isPresented: Bool
@Binding var message:String
@Binding var isSendingMsg: Bool
@ObservedObject private var keyboard = KeyboardResponder()
var onSend: (String) -> Void
var body: some View {
VStack {
Spacer()
HStack(spacing: 1){
HStack(spacing: 15){
//TextField("", text: self.$message).foregroundColor(.black)
CustomTF(sfIcon: "message", hint: "输入消息", hasDriveLine:false, value: $message).foregroundColor(.black)
/* Button(action: {
// toogling image picker...
imagePicker.toggle()
}, label: {
Image(systemName: "paperclip.circle.fill")
.font(.system(size: 22))
.foregroundColor(.gray)
})*/
}
.padding(.vertical, 12)
.padding(.horizontal)
.background(Color.black.opacity(0.06))
.clipShape(Capsule())
//.animation(.easeInOut(duration: 0.3)) //
// Send Button...
// hiding view...
if message != "" {
if isSendingMsg == false {
Button(action: {
onSend(message)
// appeding message...
}, label: {
Image(systemName: "paperplane.fill")
.font(.system(size: 22))
.foregroundColor(Color("Main"))
// rotating the image...
.rotationEffect(.init(degrees: 45))
// adjusting padding shape...
.padding(.vertical,12)
.padding(.leading,12)
.padding(.trailing,17)
.background(Color.black.opacity(0.07))
.clipShape(Circle())
})
}else{
ProgressView()
.progressViewStyle(CircularProgressViewStyle()).tint(Color("Main"))
}
}
}
.padding(.horizontal)
.padding(.bottom)
.background(Color.white)
}
}
}
public func getPlanList() async {
isLoading = true
errorMessage = nil
let userInfoUrl = URL(string: "\(UserManager.shared.baseURL())user/ticket/fetch?id=\(ticketID)")!
var request = URLRequest(url: userInfoUrl)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(UserManager.shared.getAutoData(), forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
self.isLoading = false
if let error = error {
self.errorMessage = "数据请求失败: \(error.localizedDescription)"
return
}
guard let data = data else {
self.errorMessage = "数据请求失败"
return
}
// Parse the user info response
if let Subscribe = try? JSONDecoder().decode(TicketChatReponse.self, from: data), let data = Subscribe.data {
tickchatData = data
if let message = Subscribe.data?.message {
alltickets = message
alltickets.forEach { msg in
allMessages.messages.append(msg)
}
if (alltickets.count > 0 && tickchatData?.status == 0){
isMessageInputBarPresented.toggle()
}
}else{
self.errorMessage = Subscribe.message ?? ""
}
}else{
self.errorMessage = "数据请求失败: JSON 解析错误"
}
}
}
task.resume()
}
public func closeThisTicket() async {
let userInfoUrl = URL(string: "\(UserManager.shared.baseURL())user/ticket/close?id=\(ticketID)")!
var request = URLRequest(url: userInfoUrl)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(UserManager.shared.getAutoData(), forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
if let error = error {
self.alert = Alert(errorMessage: "数据请求失败: \(error.localizedDescription)")
return
}
guard let data = data else {
self.alert = Alert(errorMessage: "数据请求失败")
return
}
// MARK: - TicketSendMsgReponse
struct TicketSendMsgReponse: Codable {
let status, message: String?
}
// Parse the user info response
if let SendMsgReponse = try? JSONDecoder().decode(TicketSendMsgReponse.self, from: data), let message = SendMsgReponse.message {
if let stutas = SendMsgReponse.status , stutas == "success" {
self.alert = Alert(okMessage: message)
}else{
self.alert = Alert(errorMessage: message)
}
}else{
self.alert = Alert(errorMessage: "数据请求失败: JSON 解析错误")
}
}
}
task.resume()
}
public func SendMsg(msg: String) async {
isSendingMsg = true
if let encodedMsg = msg.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),let userInfoUrl = URL(string: "\(UserManager.shared.baseURL())user/ticket/reply?id=\(ticketID)&message=\(encodedMsg)"){
var request = URLRequest(url: userInfoUrl)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(UserManager.shared.getAutoData(), forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
self.isSendingMsg = false
if let error = error {
self.alert = Alert(errorMessage: "数据请求失败: \(error.localizedDescription)")
return
}
guard let data = data else {
self.alert = Alert(errorMessage: "数据请求失败")
return
}
// MARK: - TicketSendMsgReponse
struct TicketSendMsgReponse: Codable {
let status, message: String?
}
// Parse the user info response
if let SendMsgReponse = try? JSONDecoder().decode(TicketSendMsgReponse.self, from: data), let message = SendMsgReponse.message {
let randomInt = Int.random(in: 100...1000)
if let stutas = SendMsgReponse.status , stutas == "success" {
withAnimation(.easeIn){
allMessages.writeMessage(idold: randomInt, msg: msg, myMsg: true, profilePic: "p1")
}
self.message = ""
}else{
self.alert = Alert(errorMessage: message)
}
}else{
self.alert = Alert(errorMessage: "数据请求失败: JSON 解析错误")
}
}
}
task.resume()
}
}
}
// Chat Bubbles...
struct ChatBubble : View {
var msg : Message
var body: some View{
// Automatic scroll To Bottom...
// First Assigning Id To Each Row...
HStack(alignment: .top,spacing: 10){
if msg.isMe{
// pushing msg to left...
// minimum space ...
// Modifying for Image...
Spacer(minLength: 25)
if msg.photo == nil{
Text("\(msg.message)")
.padding(.all)
.background(Color.black.opacity(0.06))
.clipShape(BubbleArrow(myMsg: msg.isMe)).foregroundColor(.black)
}
else{
// Image(uiImage: UIImage(data: msg.photo!)!).resizable()
AsyncImage(url: URL(string: msg.photo ?? ""))
.frame(width: UIScreen.main.bounds.width - 150, height: 150)
.clipShape(BubbleArrow(myMsg: msg.isMe))
}
// profile Image...
Image(msg.profilePic ?? "p1" )
.resizable()
.frame(width: 30, height: 30)
.clipShape(Circle())
}
else{
// pushing msg to right...
// profile Image...
Image(msg.profilePic ?? "p2")
.resizable()
.frame(width: 30, height: 30)
.clipShape(Circle())
if msg.photo == nil{
Text(msg.message)
.foregroundColor(.white)
.padding(.all)
.background(Color("Main"))
.clipShape(BubbleArrow(myMsg: msg.isMe))
// Text(":\(TimestampConverter.convertTimestampToDateString(msg.updatedAt)).font(.caption).foregroundColor(.black.opacity(0.8))")
}
else{
// Image(uiImage: UIImage(data: msg.photo!)!)
// .resizable()
AsyncImage(url: URL(string: msg.photo ?? ""))
.frame(width: UIScreen.main.bounds.width - 150, height: 150)
.clipShape(BubbleArrow(myMsg: msg.isMe))
}
Spacer(minLength: 25)
}
}
.id(msg.id)
}
}
// Bubble Arrow...
struct BubbleArrow : Shape {
var myMsg : Bool
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: myMsg ? [.topLeft,.bottomLeft,.bottomRight] : [.topRight,.bottomLeft,.bottomRight], cornerRadii: CGSize(width: 10, height: 10))
return Path(path.cgPath)
}
}
// Custom Rounded Shape...
struct RoundedShape : Shape {
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: [.topLeft,.topRight], cornerRadii: CGSize(width: 35, height: 35))
return Path(path.cgPath)
}
}
// Model Data For Message...
/*struct Message : Identifiable,Equatable{
var id : Date
var message : String
var myMsg : Bool
var profilePic : String
var photo: Data?
}*/
class Messages : ObservableObject{
@Published var messages : [Message] = []
// sample data...
init() {
/*let strings = ["Hii","Hello !!!!","What's Up, What Are You Doing ???","Nothing Just Simply Enjoying Quarintine Holidays..You???","Same :))","Ohhhhh","What About Your Country ???","Very Very Bad...","Ok Be Safe","Bye....","Ok...."]
//simple logic for two side messages
for i in 0..<strings.count{
messages.append(Message(id: Date(), message: strings[i], myMsg: i % 2 == 0 ? true : false, profilePic: i % 2 == 0 ? "p1" : "p2"))
}*/
}
func writeMessage(idold: Int,msg: String,myMsg: Bool,profilePic: String?){
messages.append(Message(idold: idold, ticketID: 0, isMe: myMsg, message: msg, photo: nil, createdAt: Int(Date().timeIntervalSince1970), updatedAt: Int(Date().timeIntervalSince1970), profilePic: profilePic))
//messages.append(Message(id: id, message: msg, myMsg: myMsg, profilePic: profilePic, photo: photo))
}
}
// Image Picker...
struct ImagePicker : UIViewControllerRepresentable {
func makeCoordinator() -> Coordinator {
return ImagePicker.Coordinator(parent1: self)
}
@Binding var imagePicker : Bool
@Binding var imgData : Data
func makeUIViewController(context: Context) -> UIImagePickerController{
let picker = UIImagePickerController()
picker.sourceType = .photoLibrary
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {
}
class Coordinator : NSObject,UIImagePickerControllerDelegate,UINavigationControllerDelegate{
var parent : ImagePicker
init(parent1 : ImagePicker) {
parent = parent1
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
parent.imagePicker.toggle()
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let image = info[.originalImage] as! UIImage
parent.imgData = image.jpegData(compressionQuality: 0.5)!
parent.imagePicker.toggle()
}
}
}
@@ -1,530 +0,0 @@
//
// SupportticketView.swift
// SFI
//
// Created by Mac on 2024/10/20.
//
import Foundation
import SwiftUI
import ApplicationLibrary
struct SupportTicketView : View {
var edges = getSafeAreaInsets()
@State var selectedTab = "Chats"
@Namespace var animation
@Environment(\.presentationMode) var presentationMode //
@State private var TicketsList: [TicketsReponseDatum] = []
@State private var isLoading = false
@State private var errorMessage: String?
@State private var alert: Alert?
@State private var showInputSheet = false
@State private var tickettitle = ""
@State private var userInput = ""
@State private var isInputLoading = false
@State private var submitmessage = ""
var body: some View{
NavigationView(content: {
VStack(spacing: 0){
VStack{
ZStack{
HStack{
Button(action: {
presentationMode.wrappedValue.dismiss() //
}, label: {
Image(systemName: "chevron.left")
.foregroundColor(.white)
Text("返回")
.foregroundColor(.white)
}).padding(10) //
Spacer(minLength: 0)
Button(action: {
showInputSheet.toggle()
}, label: {
// Image(systemName: "note.text.badge.plus")
Text("提交新工单").foregroundColor(.red)
})
}
Text("我的工单").fontWeight(.bold).lineLimit(1)
}
.padding()
// .padding(.top,edges.top)
.foregroundColor(.white)
// HStack(spacing: 20){
//
// ForEach(tabs,id: \.self){title in
//
// TabButton(selectedTab: $selectedTab, title: title, animation: animation)
// }
// }
// .padding()
// .background(Color.white.opacity(0.08))
// .cornerRadius(15)
// .padding(.vertical)
}
.padding(.bottom)
// .background(Color("top"))
// .clipShape(CustomCorner(corner: .bottomLeft, size: 65))
ZStack{
// Color("top")
// Color("bg")
// .clipShape(CustomCorner(corner: .topRight, size: 65))
ScrollView(.vertical, showsIndicators: false, content: {
VStack(spacing: 20){
/*HStack{
Text("All Chats")
.font(.title2)
.fontWeight(.bold)
Spacer(minLength: 0)
Button(action: {}, label: {
Image(systemName: "slider.horizontal.3")
.font(.system(size: 22))
.foregroundColor(.primary)
})
}
.padding([.horizontal,.top])*/
if isLoading {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
.padding()
}
if let errorMessage = errorMessage {
Text(errorMessage)
.bold()
.foregroundColor(.red)
.multilineTextAlignment(.center) //
.padding()
}
if TicketsList.count > 0 {
ForEach(TicketsList){chatData in
// Chat View...
NavigationLink(destination: SupportTicketChatView(ticketID: chatData.idOLD)) {
ChatView(chatData: chatData)
}
}
}else{
if !isLoading
{
Text("暂无记录").font(.caption)
.foregroundColor(.gray)
.multilineTextAlignment(.center) //
.padding()
}
}
}
.padding(.vertical)
})
//.clipShape(CustomCorner(corner: .topRight, size: 65))
// its cutting off inside view may be its a bug....
}
}.onAppear(){
Task{
await getPlanList()
}
}
.navigationBarHidden(true)
//.background(Color("bg").ignoresSafeArea(.all, edges: .all))
.background(
LinearGradient(colors: [
Color("BG1"),
Color("BG1"),
Color("BG2"),
Color("BG2"),
], startPoint: .top, endPoint: .bottom).ignoresSafeArea(.all, edges: .all))
// .ignoresSafeArea(.all, edges: .top)
.sheet(isPresented: $showInputSheet) {
InputSheet(tickettitle: $tickettitle, userInput: $userInput, isInputLoading: $isInputLoading, submitmessage: $submitmessage, onSubmit: {
Task {
//
await submitTicketData(title: tickettitle, userInput: userInput)
}
})
}
})
.navigationBarHidden(true)
.alertBinding($alert)
.navigationBarBackButtonHidden(true) //
}
public func submitTicketData(title: String, userInput: String) async{
isInputLoading = true
let userInfoUrl = URL(string: "\(UserManager.shared.baseURL())user/ticket/save?subject=\(title)&level=0&message=\(userInput)")!
var request = URLRequest(url: userInfoUrl)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(UserManager.shared.getAutoData(), forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
isInputLoading = false
if let error = error {
submitmessage = ( "数据请求失败: \(error.localizedDescription)")
return
}
guard let data = data else {
submitmessage = ( "数据请求失败")
return
}
// MARK: - TicketSendMsgReponse
struct TicketSendMsgReponse: Codable {
let status, message: String?
}
// Parse the user info response
if let SendMsgReponse = try? JSONDecoder().decode(TicketSendMsgReponse.self, from: data), let message = SendMsgReponse.message {
if let stutas = SendMsgReponse.status , stutas == "success" {
submitmessage = message
//1s
withAnimation {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
showInputSheet = false
//
Task{
await getPlanList()
}
}
}
}else{
submitmessage = (message)
}
}else{
submitmessage = ( "数据请求失败: JSON 解析错误")
}
}
}
task.resume()
}
public func getPlanList() async {
isLoading = true
errorMessage = nil
let userInfoUrl = URL(string: "\(UserManager.shared.baseURL())user/ticket/fetch")!
var request = URLRequest(url: userInfoUrl)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(UserManager.shared.getAutoData(), forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
self.isLoading = false
if let error = error {
self.errorMessage = "数据请求失败: \(error.localizedDescription)"
return
}
guard let data = data else {
self.errorMessage = "数据请求失败"
return
}
// Parse the user info response
if let Subscribe = try? JSONDecoder().decode(TicketsReponse.self, from: data) {
if let data = Subscribe.data {
TicketsList = data
}else{
self.errorMessage = Subscribe.message ?? ""
}
}else{
self.errorMessage = "数据请求失败: JSON 解析错误"
}
}
}
task.resume()
}
}
var tabs = ["Chats","Status","Calls"]
struct TabButton : View {
@Binding var selectedTab : String
var title : String
var animation : Namespace.ID
var body: some View{
Button(action: {
withAnimation{
selectedTab = title
}
}, label: {
Text(title)
.foregroundColor(.white)
.padding(.vertical,10)
.padding(.horizontal)
// Sliding Effect...
.background(
ZStack{
if selectedTab == title{
RoundedRectangle(cornerRadius: 10)
.fill(Color("top"))
.matchedGeometryEffect(id: "Tab", in: animation)
}
}
)
})
}
}
struct CustomCorner : Shape {
var corner : UIRectCorner
var size : CGFloat
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corner, cornerRadii: CGSize(width: size, height: size))
return Path(path.cgPath)
}
}
struct ChatView : View {
var chatData : TicketsReponseDatum
var body: some View{
HStack(spacing: 10){
VStack(spacing: 12) {
HStack {
VStack(alignment: .leading, spacing: 8, content: {
Text(chatData.subject)
.fontWeight(.bold)
.lineLimit(1).foregroundColor(.white)
// Text("\(chatData.message ?? "")")
// .font(.caption)
// .lineLimit(1)
HStack(content: {
if(chatData.status == 1){
Text("工单状态 : 已关闭").fontWeight(.light).foregroundColor(.gray).font(.subheadline)
}else if(chatData.status == 0){
Text("工单状态 : 待回复").fontWeight(.light).foregroundColor(.red).font(.subheadline)
}else if(chatData.status == 2){
Text("工单状态 : 已回复").fontWeight(.light).foregroundColor(.green).font(.subheadline)
}
Spacer(minLength: 0)
Text("\(TimestampConverter.convertTimestampToDateString(chatData.updatedAt))")
.font(.subheadline).foregroundColor(.white)
})
})
}
Divider()
}
}
.padding(.horizontal)
}
}
// Model And Sample Data....
//
//struct Chat : Identifiable {
//
// var id = UUID().uuidString
// var name : String
// var image : String
// var msg : String
// var time : String
//}
//
//// were going to do custom grouping of views....
//
//struct HomeData {
//
// var groupName : String
// var groupData : [Chat]
//}
//var FriendsChat : [Chat] = [
//
// Chat(name: "iJustine",image: "p0", msg: "Hey EveryOne !!!", time: "02:45"),
// Chat(name: "Kavsoft",image: "p1", msg: "Learn - Develop - Deploy", time: "03:45"),
// Chat(name: "SwiftUI",image: "p2", msg: "New Framework For iOS", time: "04:55"),
// Chat(name: "Bill Gates",image: "p3", msg: "Founder Of Microsoft", time: "06:25"),
// Chat(name: "Tim Cook",image: "p4", msg: "Apple lnc CEO", time: "07:19"),
// Chat(name: "Jeff",image: "p5", msg: "I dont Know How To Spend Money :)))", time: "08:22"),
//]
//
//var GroupChat : [Chat] = [
//
// Chat(name: "iTeam",image: "p0", msg: "Hey EveryOne !!!", time: "02:45"),
// Chat(name: "Kavsoft - Developers",image: "p1", msg: "Next Video :))))", time: "03:45"),
// Chat(name: "SwiftUI - Community",image: "p2", msg: "New File Importer/Exporter", time: "04:55"),
//]
//
//var data = [
//
// // Group 1
// HomeData(groupName: "", groupData: FriendsChat),
//]
struct InputSheet: View {
@Binding var tickettitle: String
@Binding var userInput: String
@Binding var isInputLoading: Bool
@Binding var submitmessage:String
var onSubmit: () -> Void
var body: some View {
VStack(spacing: 20) {
Spacer()
Text("新的工单").fontWeight(.bold).lineLimit(1)
Text("请输入工单标题").font(.subheadline) .foregroundColor(.gray)
.frame(maxWidth: .infinity)
.multilineTextAlignment(.leading)
.padding(.leading, 5) //
.padding(.top, 8) //
TextEditor( text: $tickettitle)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.frame(width: UIScreen.main.bounds.width * 0.9,height: 50)
.background(Color.clear) //
.cornerRadius(10) //
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.gray.opacity(0.5), lineWidth: 1)
) //
.background(Color.clear) // //
.font(.system(size: 18))
.transparentScrolling()
Text("请输入工单内容").font(.subheadline) .foregroundColor(.gray)
.frame(maxWidth: .infinity)
.padding(.leading, 5) //
.padding(.top, 8) //
.multilineTextAlignment(.leading)
TextEditor(text: $userInput)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.frame(width: UIScreen.main.bounds.width * 0.9,height: UIScreen.main.bounds.height * 0.4)
.background(Color.clear) //
.cornerRadius(10) //
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.gray.opacity(0.5), lineWidth: 1)
) //
.font(.system(size: 18))
.transparentScrolling()
Spacer()
if !submitmessage.isEmpty{
Text(submitmessage).font(.subheadline).foregroundColor(Color.red.opacity(0.9))
}
Button(action: {
if( tickettitle.count > 0 && userInput.count > 0){
onSubmit() //
}
}) {
if isInputLoading {
ProgressView()
.progressViewStyle(CircularProgressViewStyle()).tint(Color("Main"))
} else {
Text("提交工单")
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
}
.padding()
.frame(maxWidth: .infinity)
.padding(.bottom, 20)
}
.background(Color("bg").ignoresSafeArea(.all, edges: .all))
.ignoresSafeArea(edges: .bottom) //
}
}
@@ -1,66 +0,0 @@
//
// SupportView.swift
// SFI
//
// Created by Mac on 2024/10/21.
//
import Foundation
import SwiftUI
import Crisp
struct SupportView: View {
@Environment(\.presentationMode) var presentationMode //
var body: some View {
NavigationView(content: {
VStack{
ZStack{
HStack{
Button(action: {
presentationMode.wrappedValue.dismiss() //
}, label: {
Image(systemName: "chevron.left")
.foregroundColor(.white)
Text("返回")
.foregroundColor(.white)
}).padding(10) //
Spacer(minLength: 0)
}
Text("客服(技术支持)")
.fontWeight(.bold).lineLimit(1)
}
.padding()
// .padding(.top,edges.top)
.foregroundColor(.white)
ChatViewControllerWrapper()
// SomeUIElement()
// WebView(url: URL(string: UserManager.shared.kefuUrl())!)
}.navigationBarHidden(true).edgesIgnoringSafeArea(.bottom)
.background(
LinearGradient(colors: [
Color("BG1"),
Color("BG1"),
Color("BG2"),
Color("BG2"),
], startPoint: .top, endPoint: .bottom)
)
})
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true) //
}
}
@@ -1,40 +0,0 @@
//
// WebView.swift
// SFI
//
// Created by Mac on 2024/10/12.
//
import Foundation
import SwiftUI
import WebKit
struct WebView: UIViewRepresentable {
var url: URL
func makeUIView(context: Context) -> WKWebView {
return WKWebView()
}
func updateUIView(_ webView: WKWebView, context: Context) {
let request = URLRequest(url: url)
webView.load(request)
}
}
struct WebViewContent: UIViewRepresentable {
var content: String
func makeUIView(context: Context) -> WKWebView {
return WKWebView()
}
func updateUIView(_ webView: WKWebView, context: Context) {
// let request = URLRequest(url: url)
// webView.load(request)
webView.loadHTMLString(content, baseURL: nil)
}
}