Files
44da97bf04 Tv os changes (#63)
* 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

* Update fonts

* Redesign tvOS connection screen and add peer search

- Move logo to top-left brand anchor (smaller, semi-transparent)
- Restyle connect button with gradient fill, glow shadow, and
  press-down scale animation (TVConnectButtonStyle)
- Add search bar with clear button to peers list view

* Redesign tvOS UI with gradient backgrounds

* Update logo position

* Redesign tvOS peers & resources lists to match settings UI style

* Add color-coded connection status indicator and debug state overlay

* Stabilize connection screen layout and remove debug overlay

* Update project.pbxproj

* Delete mock data

* Update info plist

* Update project.pbxproj

* add go get

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: mlsmaycon <mlsmaycon@gmail.com>
2026-03-06 08:58:24 +01:00

217 lines
7.7 KiB
Swift

//
// MainView.swift
// NetBirdiOS
//
// Created by Pascal Fischer on 01.08.23.
//
import SwiftUI
import Lottie
import NetworkExtension
// MARK: - Main Entry Point
/// The root view that switches between iOS and tvOS layouts.
struct MainView: View {
@EnvironmentObject var viewModel: ViewModel
var body: some View {
#if os(tvOS)
// tvOS uses a completely different navigation structure
TVMainView()
#else
// iOS uses tab bar navigation
iOSMainView()
#endif
}
}
#if os(iOS)
enum MainAlertType: String, Identifiable {
case changeServer
case serverChanged
case preSharedKeyChanged
case authenticationRequired
var id: String { rawValue }
}
struct iOSMainView: View {
@EnvironmentObject var viewModel: ViewModel
@AppStorage("hasCompletedOnboarding") private var hasCompletedOnboarding = false
@State private var selectedTab = 0
@State private var activeAlert: MainAlertType?
init() {
let navAppearance = UINavigationBarAppearance()
navAppearance.configureWithOpaqueBackground()
navAppearance.backgroundColor = UIColor(named: "BgNavigationBar")
navAppearance.titleTextAttributes = [.foregroundColor: UIColor(named: "TextPrimary") ?? .white]
navAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor(named: "TextPrimary") ?? .white]
UINavigationBar.appearance().standardAppearance = navAppearance
UINavigationBar.appearance().compactAppearance = navAppearance
UINavigationBar.appearance().scrollEdgeAppearance = navAppearance
let tabAppearance = UITabBarAppearance()
tabAppearance.configureWithOpaqueBackground()
tabAppearance.backgroundColor = UIColor(named: "BgNavigationBar")
UITabBar.appearance().standardAppearance = tabAppearance
UITabBar.appearance().scrollEdgeAppearance = tabAppearance
}
var body: some View {
ZStack {
if !hasCompletedOnboarding {
FirstLaunchView(
hasCompletedOnboarding: $hasCompletedOnboarding,
onChangeServer: {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
activeAlert = .changeServer
}
}
)
} else {
mainContent
}
}
.alert(item: $activeAlert) { alertType in
switch alertType {
case .changeServer:
return Alert(
title: Text("Change server"),
message: Text("Changing server will erase the local config and disconnect this device from the current NetBird account."),
primaryButton: .destructive(Text("Confirm")) {
viewModel.handleServerChanged()
viewModel.navigateToServerView = true
},
secondaryButton: .cancel()
)
case .serverChanged:
return Alert(
title: Text("Server was changed"),
message: Text("Click on the connect button to continue."),
dismissButton: .default(Text("OK"))
)
case .preSharedKeyChanged:
return Alert(
title: Text("Preshared key was set"),
message: Text("Click on the connect button to continue."),
dismissButton: .default(Text("OK"))
)
case .authenticationRequired:
return Alert(
title: Text("Authentication required"),
message: Text("The server requires a new authentication."),
dismissButton: .default(Text("OK"))
)
}
}
}
private var mainContent: some View {
ZStack {
TabView(selection: $selectedTab) {
NavigationView {
iOSConnectionView()
}
.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Label("Connection", systemImage: "network")
}
.tag(0)
NavigationView {
iOSPeersView()
}
.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Label("Peers", systemImage: "person.3.fill")
}
.tag(1)
NavigationView {
iOSNetworksView()
}
.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Label("Resources", systemImage: "globe")
}
.tag(2)
NavigationView {
iOSSettingsView()
}
.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Label("Settings", systemImage: "gear")
}
.tag(3)
}
.onChange(of: viewModel.navigateToServerView) { newValue in
if newValue {
selectedTab = 0
}
}
.onChange(of: viewModel.showChangeServerAlert) { show in
if show { activeAlert = .changeServer; viewModel.showChangeServerAlert = false }
}
.onChange(of: viewModel.showServerChangedInfo) { show in
if show { activeAlert = .serverChanged; viewModel.showServerChangedInfo = false }
}
.onChange(of: viewModel.showPreSharedKeyChangedInfo) { show in
if show { activeAlert = .preSharedKeyChanged; viewModel.showPreSharedKeyChangedInfo = false }
}
.onChange(of: viewModel.showAuthenticationRequired) { show in
if show { activeAlert = .authenticationRequired; viewModel.showAuthenticationRequired = false }
}
// Toast alerts
VStack {
Spacer()
if viewModel.showFqdnCopiedAlert {
HStack {
Image("logo-onboarding")
.resizable()
.frame(width: 20, height: 15)
Text("Domain name copied!")
.foregroundColor(.white)
.font(.headline)
}
.padding(5)
.background(Color.black.opacity(0.5))
.cornerRadius(8)
.transition(AnyTransition.opacity.combined(with: .move(edge: .top)))
.animation(.default, value: viewModel.showFqdnCopiedAlert)
.zIndex(1)
}
if viewModel.showIpCopiedAlert {
HStack {
Image("logo-onboarding")
.resizable()
.frame(width: 20, height: 15)
Text("IP address copied!")
.foregroundColor(.white)
.font(.headline)
}
.padding(5)
.background(Color.black.opacity(0.5))
.cornerRadius(8)
.transition(AnyTransition.opacity.combined(with: .move(edge: .top)))
.animation(.default, value: viewModel.showIpCopiedAlert)
.zIndex(1)
}
Spacer().frame(height: 80)
}
}
}
}
struct MainView_Previews: PreviewProvider {
static var previews: some View {
MainView()
}
}
#endif // os(iOS)