diff --git a/apps/ios/Sources/RootTabs.swift b/apps/ios/Sources/RootTabs.swift index 3b450647b..fe3d2fe76 100644 --- a/apps/ios/Sources/RootTabs.swift +++ b/apps/ios/Sources/RootTabs.swift @@ -1,4 +1,5 @@ import SwiftUI +import UIKit struct RootTabs: View { @EnvironmentObject private var appModel: NodeAppModel @@ -13,29 +14,13 @@ struct RootTabs: View { .tabItem { Label("Voice", systemImage: "mic") } SettingsTab() - .tabItem { - VStack { - ZStack(alignment: .topTrailing) { - Image(systemName: "gearshape") - Circle() - .fill(self.settingsIndicatorColor) - .frame(width: 9, height: 9) - .overlay( - Circle() - .stroke(.black.opacity(0.2), lineWidth: 0.5)) - .shadow( - color: self.settingsIndicatorGlowColor, - radius: self.settingsIndicatorGlowRadius, - x: 0, - y: 0) - .scaleEffect(self.settingsIndicatorScale) - .opacity(self.settingsIndicatorOpacity) - .offset(x: 7, y: -2) - } - Text("Settings") - } - } + .tabItem { Label("Settings", systemImage: "gearshape") } } + .background(TabBarControllerAccessor { tabBarController in + guard let item = tabBarController.tabBar.items?[Self.settingsTabIndex] else { return } + item.badgeValue = " " + item.badgeColor = self.settingsBadgeColor + }) .onAppear { self.updateConnectingPulse(for: self.bridgeIndicatorState) } .onChange(of: self.bridgeIndicatorState) { _, newValue in self.updateConnectingPulse(for: newValue) @@ -48,55 +33,25 @@ struct RootTabs: View { case disconnected } + private static let settingsTabIndex = 2 + private var bridgeIndicatorState: BridgeIndicatorState { if self.appModel.bridgeServerName != nil { return .connected } if self.appModel.bridgeStatusText.localizedCaseInsensitiveContains("connecting") { return .connecting } return .disconnected } - private var settingsIndicatorColor: Color { + private var settingsBadgeColor: UIColor { switch self.bridgeIndicatorState { case .connected: - Color.green + UIColor.systemGreen case .connecting: - Color.yellow + UIColor.systemYellow.withAlphaComponent(self.isConnectingPulse ? 1.0 : 0.6) case .disconnected: - Color.red + UIColor.systemRed } } - private var settingsIndicatorGlowColor: Color { - switch self.bridgeIndicatorState { - case .connected: - Color.green.opacity(0.75) - case .connecting: - Color.yellow.opacity(0.6) - case .disconnected: - Color.clear - } - } - - private var settingsIndicatorGlowRadius: CGFloat { - switch self.bridgeIndicatorState { - case .connected: - 6 - case .connecting: - self.isConnectingPulse ? 6 : 3 - case .disconnected: - 0 - } - } - - private var settingsIndicatorScale: CGFloat { - guard self.bridgeIndicatorState == .connecting else { return 1 } - return self.isConnectingPulse ? 1.12 : 0.96 - } - - private var settingsIndicatorOpacity: Double { - guard self.bridgeIndicatorState == .connecting else { return 1 } - return self.isConnectingPulse ? 1.0 : 0.75 - } - private func updateConnectingPulse(for state: BridgeIndicatorState) { guard state == .connecting else { withAnimation(.easeOut(duration: 0.2)) { self.isConnectingPulse = false } @@ -109,3 +64,41 @@ struct RootTabs: View { } } } + +private struct TabBarControllerAccessor: UIViewControllerRepresentable { + let onResolve: (UITabBarController) -> Void + + func makeUIViewController(context: Context) -> ResolverViewController { + ResolverViewController(onResolve: self.onResolve) + } + + func updateUIViewController(_ uiViewController: ResolverViewController, context: Context) { + uiViewController.onResolve = self.onResolve + uiViewController.resolveIfPossible() + } +} + +private final class ResolverViewController: UIViewController { + var onResolve: (UITabBarController) -> Void + + init(onResolve: @escaping (UITabBarController) -> Void) { + self.onResolve = onResolve + super.init(nibName: nil, bundle: nil) + self.view.isHidden = true + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + self.resolveIfPossible() + } + + func resolveIfPossible() { + guard let tabBarController = self.tabBarController else { return } + self.onResolve(tabBarController) + } +}