mirror of
https://github.com/StikDebug/StikDebug.git
synced 2026-10-16 04:20:30 +00:00
commit number 1
This commit is contained in:
parent
2c2c145d51
commit
5941dc12d2
2 changed files with 225 additions and 41 deletions
|
|
@ -232,18 +232,25 @@ struct HeartbeatApp: App {
|
|||
alert_string = "[Download DDI Error]: " + result
|
||||
show_alert = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.alert(alert_title, isPresented: $show_alert) {
|
||||
Button("OK", role: .cancel) { }
|
||||
} message: {
|
||||
Text(alert_title)
|
||||
}
|
||||
.overlay(
|
||||
ZStack {
|
||||
if show_alert {
|
||||
CustomErrorView(
|
||||
title: alert_title,
|
||||
message: alert_string,
|
||||
onDismiss: {
|
||||
show_alert = false
|
||||
},
|
||||
showButton: true,
|
||||
primaryButtonText: "OK"
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -430,16 +437,15 @@ func startHeartbeatInBackground() {
|
|||
print("Error: \(message ?? "") (Code: \(result))")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if let mainWindow = UIApplication.shared.windows.last {
|
||||
let alert = UIAlertController(title: "Heartbeat Error", message: "\(message ?? "") (\(result))", preferredStyle: .alert)
|
||||
|
||||
let tryAgainAction = UIAlertAction(title: "Try Again", style: .default) { _ in
|
||||
showAlert(
|
||||
title: "Heartbeat Error",
|
||||
message: "Failed to connect to Heartbeat (\(result))",
|
||||
showOk: false,
|
||||
showTryAgain: true
|
||||
) { shouldTryAgain in
|
||||
if shouldTryAgain {
|
||||
startHeartbeatInBackground()
|
||||
}
|
||||
|
||||
alert.addAction(tryAgainAction)
|
||||
|
||||
mainWindow.rootViewController?.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -499,34 +505,68 @@ struct LoadingView: View {
|
|||
|
||||
public func showAlert(title: String, message: String, showOk: Bool, showTryAgain: Bool = false, completion: @escaping (Bool) -> Void) {
|
||||
DispatchQueue.main.async {
|
||||
if let mainWindow = UIApplication.shared.windows.last {
|
||||
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
|
||||
if showOk, !showTryAgain {
|
||||
let okAction = UIAlertAction(title: "OK", style: .default) { _ in
|
||||
completion(true)
|
||||
}
|
||||
|
||||
alert.addAction(okAction)
|
||||
} else if !showTryAgain {
|
||||
completion(false)
|
||||
}
|
||||
|
||||
if showTryAgain {
|
||||
let tryAgainAction = UIAlertAction(title: "Try Again", style: .default) { _ in
|
||||
completion(true)
|
||||
}
|
||||
|
||||
alert.addAction(tryAgainAction)
|
||||
|
||||
let okAction = UIAlertAction(title: "OK", style: .cancel) { _ in
|
||||
let rootViewController = UIApplication.shared.windows.last?.rootViewController
|
||||
|
||||
if showTryAgain {
|
||||
// Configure with "Try Again" button only (no Cancel button)
|
||||
let customErrorView = CustomErrorView(
|
||||
title: title,
|
||||
message: message,
|
||||
onDismiss: {
|
||||
// Called when tapped outside
|
||||
completion(false)
|
||||
},
|
||||
showButton: true,
|
||||
primaryButtonText: "Try Again",
|
||||
onPrimaryButtonTap: {
|
||||
// Try Again was tapped
|
||||
completion(true)
|
||||
}
|
||||
|
||||
alert.addAction(okAction)
|
||||
}
|
||||
)
|
||||
|
||||
mainWindow.rootViewController?.present(alert, animated: true, completion: nil)
|
||||
let hostingController = UIHostingController(rootView: customErrorView)
|
||||
hostingController.modalPresentationStyle = .overFullScreen
|
||||
hostingController.modalTransitionStyle = .crossDissolve
|
||||
hostingController.view.backgroundColor = .clear
|
||||
rootViewController?.present(hostingController, animated: true)
|
||||
} else if showOk {
|
||||
// Configure with just "OK" button
|
||||
let customErrorView = CustomErrorView(
|
||||
title: title,
|
||||
message: message,
|
||||
onDismiss: {
|
||||
// Called when tapped outside
|
||||
completion(true)
|
||||
},
|
||||
showButton: true,
|
||||
primaryButtonText: "OK",
|
||||
onPrimaryButtonTap: {
|
||||
// OK was tapped
|
||||
completion(true)
|
||||
}
|
||||
)
|
||||
|
||||
let hostingController = UIHostingController(rootView: customErrorView)
|
||||
hostingController.modalPresentationStyle = .overFullScreen
|
||||
hostingController.modalTransitionStyle = .crossDissolve
|
||||
hostingController.view.backgroundColor = .clear
|
||||
rootViewController?.present(hostingController, animated: true)
|
||||
} else {
|
||||
// No buttons case
|
||||
let customErrorView = CustomErrorView(
|
||||
title: title,
|
||||
message: message,
|
||||
onDismiss: {
|
||||
completion(false)
|
||||
},
|
||||
showButton: false
|
||||
)
|
||||
|
||||
let hostingController = UIHostingController(rootView: customErrorView)
|
||||
hostingController.modalPresentationStyle = .overFullScreen
|
||||
hostingController.modalTransitionStyle = .crossDissolve
|
||||
hostingController.view.backgroundColor = .clear
|
||||
rootViewController?.present(hostingController, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
144
StikJIT/Views/Custom Error View.swift
Normal file
144
StikJIT/Views/Custom Error View.swift
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
import SwiftUI
|
||||
|
||||
struct CustomErrorView: View {
|
||||
var title: String
|
||||
var message: String
|
||||
var onDismiss: () -> Void
|
||||
var showButton: Bool = true
|
||||
@State private var opacity: Double = 0
|
||||
@State private var scale: CGFloat = 0.8
|
||||
var primaryButtonText: String = "OK"
|
||||
var secondaryButtonText: String = "Cancel"
|
||||
var onPrimaryButtonTap: (() -> Void)? = nil
|
||||
var onSecondaryButtonTap: (() -> Void)? = nil
|
||||
var showSecondaryButton: Bool = false
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
// Background overlay
|
||||
Color.black.opacity(0.6)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
.opacity(opacity)
|
||||
.onTapGesture {
|
||||
if showButton {
|
||||
dismissWithAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
// Error card - made more compact
|
||||
VStack(spacing: 12) {
|
||||
// Error icon - smaller
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.font(.system(size: 32))
|
||||
.foregroundColor(.red.opacity(0.9))
|
||||
.padding(.top, 8)
|
||||
|
||||
// Title - slightly smaller
|
||||
Text(title)
|
||||
.font(.system(size: 18, weight: .bold, design: .rounded))
|
||||
.foregroundColor(.white)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
// Divider
|
||||
Rectangle()
|
||||
.frame(height: 1)
|
||||
.foregroundColor(.white.opacity(0.2))
|
||||
.padding(.horizontal, 12)
|
||||
|
||||
// Message - slightly smaller
|
||||
Text(message)
|
||||
.font(.system(size: 15, design: .rounded))
|
||||
.foregroundColor(.white.opacity(0.9))
|
||||
.multilineTextAlignment(.center)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.horizontal, 12)
|
||||
|
||||
// Restart instructions when button is hidden
|
||||
if !showButton {
|
||||
Text("Please connect to WireGuard VPN and restart the app.")
|
||||
.font(.system(size: 15, weight: .medium, design: .rounded))
|
||||
.foregroundColor(.white.opacity(0.9))
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.top, 4)
|
||||
.padding(.horizontal, 12)
|
||||
}
|
||||
|
||||
// Dismiss button (only shown when showButton is true)
|
||||
if showButton {
|
||||
VStack(spacing: 6) {
|
||||
Button(action: {
|
||||
dismissWithAnimation()
|
||||
onPrimaryButtonTap?() // Call primary action if provided
|
||||
}) {
|
||||
Text(primaryButtonText)
|
||||
.font(.system(size: 16, weight: .semibold, design: .rounded))
|
||||
.foregroundColor(.black)
|
||||
.frame(height: 38)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(Color.white)
|
||||
)
|
||||
}
|
||||
|
||||
if showSecondaryButton {
|
||||
Button(action: {
|
||||
dismissWithAnimation()
|
||||
onSecondaryButtonTap?() // Call secondary action if provided
|
||||
}) {
|
||||
Text(secondaryButtonText)
|
||||
.font(.system(size: 16, weight: .medium, design: .rounded))
|
||||
.foregroundColor(.white)
|
||||
.frame(height: 38)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(Color.gray.opacity(0.3))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.bottom, 12)
|
||||
.padding(.top, 6)
|
||||
} else {
|
||||
Spacer()
|
||||
.frame(height: 12)
|
||||
}
|
||||
}
|
||||
// Made the card smaller overall
|
||||
.frame(width: min(UIScreen.main.bounds.width - 80, 300))
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 16)
|
||||
.fill(Color(UIColor.systemGray6).opacity(0.95))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 16)
|
||||
.stroke(Color.white.opacity(0.2), lineWidth: 1)
|
||||
)
|
||||
)
|
||||
.shadow(color: Color.black.opacity(0.25), radius: 16, x: 0, y: 8)
|
||||
.scaleEffect(scale)
|
||||
.opacity(opacity)
|
||||
}
|
||||
.onAppear {
|
||||
// Made animation faster (0.25s instead of 0.4s)
|
||||
withAnimation(.spring(response: 0.25, dampingFraction: 0.7)) {
|
||||
opacity = 1
|
||||
scale = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func dismissWithAnimation() {
|
||||
// Made dismiss animation faster (0.15s instead of 0.2s)
|
||||
withAnimation(.easeOut(duration: 0.15)) {
|
||||
opacity = 0
|
||||
scale = 0.8
|
||||
}
|
||||
|
||||
// Delay the actual dismissal to allow the animation to complete
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue