Files
evgeniyChepelevandClaude Opus 4.5 f5b1de0cc7 Redesign app (#60)
* fix ui state for airplaine mode

* fix slide bar

* Keep VPN tunnel alive during network unavailability

- Add isNetworkUnavailable flag to NetBirdAdapter to track network state
- Modify ConnectionListener to stay in 'connecting' state when network
  is unavailable instead of transitioning to 'disconnected'
- Update PacketTunnelProvider to set network unavailable flag and
  trigger automatic reconnect when network returns
- Fix CustomLottieView to show grey 'Disconnected' state immediately
  when network is lost, without closing the VPN tunnel
- Ensure UI shows correct state after app foreground/background cycle

This allows the VPN tunnel to survive temporary network outages
(e.g. airplane mode) and automatically reconnect when network returns.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix UI state after app foreground/background cycle

Show correct connected/disconnected state immediately when app returns
from background, without replaying animations. Use extensionStatus
(iOS VPN state) as the source of truth for UI state.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* netbird credential

* Update MainView.swift

* Separate button by state

* Add gogoleServiceInfo plist reference

* Remove dead code in animation state machine and fix copy-to-clipboard UX  - Remove unreachable shouldForceReset block in CustomLottieView (already handled by earlier return) - Guard against copying empty fqdn/ip strings when disconnected - Use consistent .smooth animation for both fqdn and ip copy feedback

- Remove unreachable shouldForceReset block in CustomLottieView (already handled by earlier return)
- Guard against copying empty fqdn/ip strings when disconnected
- Use consistent .smooth animation for both fqdn and ip copy feedback

* Tab bar

* Update peer view

* Add offline state handling and network warning banner

- Show "Offline" instead of "Connected" when VPN tunnel is active but device has no internet
- Add NetworkWarningBanner with "Network Issues" warning when connected without internet
- Remove InternetStatusView (online/offline indicator) from connection screen
- Add FirstLaunchView onboarding screen
- Remove unused components (CustomBackButton, Extensions, JustifiedText, SolidButton, TransparentGradientButton)
- Update AboutView, AdvancedView, ServerView, PeerTabView, RouteTabView, iOSNetworksView styling
- Add EmptyTabPlaceholder component

* Add AppButton component with liquid glass support for iOS 26+

* Update FirstLaunchView.swift

* old version

* Update project.pbxproj

* Code refactoring

* Update project.pbxproj

* Update MainViewModel.swift

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-02 15:23:51 +01:00

96 lines
3.2 KiB
Swift

//
// PeerDetailSheet.swift
// NetBird
//
import SwiftUI
struct PeerDetailSheet: View {
@ObservedObject var peer: PeerInfo
@Environment(\.presentationMode) var presentationMode
@State private var relativeDateText: String = ""
private let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
formatter.timeZone = TimeZone(secondsFromGMT: 0)
return formatter
}()
private let relativeFormatter: RelativeDateTimeFormatter = {
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .full
return formatter
}()
var body: some View {
NavigationView {
List {
Section {
detailRow("Status", peer.connStatus)
detailRow("Last status update", relativeDateText)
detailRow("Connection type", peer.relayed ? "Relayed" : "P2P")
detailRow("Direct", peer.direct.description)
detailRow("Latency", peer.latency)
detailRow("Quantum resistance", peer.rosenpassEnabled.description)
}
Section(header: Text("ICE")) {
detailRow("Candidate (Local/Remote)", "\(peer.localIceCandidateType)/\(peer.remoteIceCandidateType)")
detailRow("Local endpoint", peer.localIceCandidateEndpoint)
detailRow("Remote endpoint", peer.remoteIceCandidateEndpoint)
}
if !peer.routes.isEmpty {
Section(header: Text("Routes")) {
ForEach(peer.routes, id: \.self) { route in
Text(route)
.font(.system(.body, design: .monospaced))
}
}
}
Section {
detailRow("Public key", peer.pubKey)
}
}
.listStyle(.insetGrouped)
.navigationTitle(peer.fqdn)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Done") { presentationMode.wrappedValue.dismiss() }
}
}
}
.onAppear { updateRelativeDate() }
}
@ViewBuilder
private func detailRow(_ label: String, _ value: String) -> some View {
HStack {
Text(label)
.foregroundColor(Color("TextPrimary"))
Spacer()
Text(value)
.foregroundColor(.gray)
.multilineTextAlignment(.trailing)
}
.font(.subheadline)
}
private func updateRelativeDate() {
if peer.connStatusUpdate == "0001-01-01 00:00:00" {
relativeDateText = "never"
return
}
if let date = dateFormatter.date(from: peer.connStatusUpdate) {
relativeDateText = relativeFormatter.localizedString(for: date, relativeTo: Date())
} else {
relativeDateText = peer.connStatusUpdate
}
}
}