import SwiftUI import OrgSocialKit /// Route identifiers used by `TabView(selection:)`. Kept as an enum so other views /// (e.g. TimelineView's empty state) can programmatically switch tabs via a binding. enum RootTab: Hashable { case timeline, notifications, discover, groups, profile } struct RootView: View { @AppStorage("publicFeedURL") private var publicFeedURL = "" @AppStorage("vfileURL") private var vfileURL = "" @AppStorage("githubToken") private var githubToken = "" @AppStorage("codebergToken") private var codebergToken = "" @AppStorage("webdavURL") private var webdavURL = "" @State private var selectedTab: RootTab = .timeline // User is "configured" once they have both a public feed URL and some kind of credential // for publishing. This gate avoids landing on an empty timeline before first-time setup. private var isConfigured: Bool { guard !publicFeedURL.isEmpty, URL(string: publicFeedURL) != nil else { return false } return !vfileURL.isEmpty || !githubToken.isEmpty || !codebergToken.isEmpty || !webdavURL.isEmpty } var body: some View { if isConfigured { tabs } else { WelcomeView() } } private var tabs: some View { TabView(selection: $selectedTab) { TimelineView(selectedTab: $selectedTab) .tabItem { Label("Timeline", systemImage: "house") } .tag(RootTab.timeline) NotificationsView() .tabItem { Label("Notifications", systemImage: "bell") } .tag(RootTab.notifications) DiscoverView() .tabItem { Label("Discover", systemImage: "globe") } .tag(RootTab.discover) GroupsView() .tabItem { Label("Groups", systemImage: "person.3") } .tag(RootTab.groups) OwnProfileView() .tabItem { Label("Profile", systemImage: "person.circle") } .tag(RootTab.profile) } .task { await registerWithRelayIfNeeded() } } /// Proactively registers the user's feed URL with the relay so it starts /// getting crawled (~every minute) instead of waiting for someone to /// follow the feed and for the relay's daily follow-crawl to pick it up. /// A brand-new account otherwise wouldn't appear anywhere on the relay /// for up to 24 hours. The registration is best-effort: a UserDefaults /// flag avoids repeating it every launch for the same (feed, relay) pair. private func registerWithRelayIfNeeded() async { let relayRaw = UserDefaults.standard.string(forKey: "relayURL") ?? "" guard !publicFeedURL.isEmpty, let feedURL = URL(string: publicFeedURL), let relayURL = URL(string: relayRaw) else { return } let registeredKey = "relayRegistration.\(relayURL.absoluteString).\(feedURL.absoluteString)" if UserDefaults.standard.bool(forKey: registeredKey) { return } do { try await RelayClient().registerFeed(feedURL, with: relayURL) UserDefaults.standard.set(true, forKey: registeredKey) } catch { // Silent: the next launch will retry, and notifications/timeline // code already re-registers reactively on 404. } } }