commit number 1

This commit is contained in:
neoarz 2025-03-31 20:11:17 -04:00
parent 2c2c145d51
commit 5941dc12d2
2 changed files with 225 additions and 41 deletions

View file

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

View 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()
}
}
}