Files
evgeniyChepelev 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

95 lines
2.3 KiB
Swift

//
// AppButton.swift
// NetBird
//
// Reusable button with liquid glass on iOS 26+ and classic fallback on earlier versions.
//
import SwiftUI
struct AppButton: View {
let title: String
let style: Style
let action: () -> Void
init(_ title: String, style: Style = .default, action: @escaping () -> Void) {
self.title = title
self.style = style
self.action = action
}
var body: some View {
Button {
action()
} label: {
ZStack {
background
HStack {
Spacer()
Text(title)
.font(style.font)
.foregroundColor(style.foregroundColor)
.lineLimit(1)
Spacer()
}
}
.frame(height: style.height)
}
}
@ViewBuilder
private var background: some View {
if #available(iOS 26.0, tvOS 26.0, *) {
switch style {
case .primary:
Capsule()
.fill(Color.accentColor.opacity(0.8))
.glassEffect(.regular)
case .secondary:
Capsule()
.fill(Color.white.opacity(0.5))
.glassEffect(.regular)
}
} else {
switch style {
case .primary:
Capsule()
.fill(Color.accentColor)
.overlay(
Capsule()
.stroke(Color.orange.opacity(0.8), lineWidth: 2)
)
case .secondary:
Capsule()
.fill(Color.gray.opacity(0.15))
.overlay(
Capsule()
.stroke(Color.primary.opacity(0.12), lineWidth: 1)
)
}
}
}
enum Style {
case primary
case secondary
static let `default`: Self = .primary
var foregroundColor: Color {
switch self {
case .primary: return .white
case .secondary: return .primary
}
}
var font: Font {
.body.weight(.medium)
}
var height: CGFloat {
48
}
}
}