From 2bde08e0040f7b02f5d37c1100fe41f592251cd7 Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Wed, 16 Oct 2024 11:26:29 -0400 Subject: [PATCH] #1593: Fix spinner and flashing tab view --- Nos.xcodeproj/project.pbxproj | 4 + Nos/AppController.swift | 4 +- Nos/Views/AppView.swift | 253 ++++++++++++++++--------------- Nos/Views/SplashScreenView.swift | 21 +++ 4 files changed, 160 insertions(+), 122 deletions(-) create mode 100644 Nos/Views/SplashScreenView.swift diff --git a/Nos.xcodeproj/project.pbxproj b/Nos.xcodeproj/project.pbxproj index 7fcd4826c..7b1ebb890 100644 --- a/Nos.xcodeproj/project.pbxproj +++ b/Nos.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 030024192CC00DFC0073ED56 /* SplashScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030024182CC00DF70073ED56 /* SplashScreenView.swift */; }; 030036852C5D39DD002C71F5 /* RefreshController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030036842C5D39DD002C71F5 /* RefreshController.swift */; }; 030036942C5D3AD3002C71F5 /* RefreshController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030036842C5D39DD002C71F5 /* RefreshController.swift */; }; 030036AB2C5D872B002C71F5 /* NewNotesButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030036AA2C5D872B002C71F5 /* NewNotesButton.swift */; }; @@ -595,6 +596,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 030024182CC00DF70073ED56 /* SplashScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenView.swift; sourceTree = ""; }; 030036842C5D39DD002C71F5 /* RefreshController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshController.swift; sourceTree = ""; }; 030036AA2C5D872B002C71F5 /* NewNotesButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNotesButton.swift; sourceTree = ""; }; 0304D0A62C9B4BF2001D16C7 /* OpenGraphMetatdata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGraphMetatdata.swift; sourceTree = ""; }; @@ -1978,6 +1980,7 @@ isa = PBXGroup; children = ( C9F84C20298DC36800C6714D /* AppView.swift */, + 030024182CC00DF70073ED56 /* SplashScreenView.swift */, 5B79F6402BA11618002DA9BE /* Components */, 65BD8DC12BDAF2C300802039 /* Discover */, 03618B112C825D8700BCBC55 /* Fixtures */, @@ -2435,6 +2438,7 @@ C94C4CF32AD993CA00F801CA /* UNSErrorView.swift in Sources */, C98CA9042B14FA3D00929141 /* PagedRelaySubscription.swift in Sources */, 5B0D99032A94090A0039F0C5 /* DoubleTapToPopModifier.swift in Sources */, + 030024192CC00DFC0073ED56 /* SplashScreenView.swift in Sources */, 0357299B2BE415E5005FEE85 /* ContentWarningController.swift in Sources */, 5BFF66B42A58853D00AA79DD /* PublishedEventsView.swift in Sources */, 03D1B42C2C3C1B0D001778CD /* TLVElement.swift in Sources */, diff --git a/Nos/AppController.swift b/Nos/AppController.swift index 0b34d0cbc..f46b6846a 100644 --- a/Nos/AppController.swift +++ b/Nos/AppController.swift @@ -7,16 +7,18 @@ import Logger enum CurrentState { case onboarding + case loading case loggedIn } - private(set) var currentState: CurrentState? + private(set) var currentState: CurrentState @ObservationIgnored @Dependency(\.analytics) private var analytics @ObservationIgnored @Dependency(\.router) private var router @ObservationIgnored @Dependency(\.currentUser) private var currentUser init() { + currentState = .loading Log.info("App Version: \(Bundle.current.versionAndBuild)") } diff --git a/Nos/Views/AppView.swift b/Nos/Views/AppView.swift index d84d64707..fcdd2d0a6 100644 --- a/Nos/Views/AppView.swift +++ b/Nos/Views/AppView.swift @@ -4,7 +4,7 @@ import Dependencies struct AppView: View { @State var showNewPost = false - @State var newPostContents: String? + @State var newPostContents: String? @Environment(AppController.self) var appController @EnvironmentObject private var router: Router @@ -13,132 +13,21 @@ struct AppView: View { @Dependency(\.crashReporting) private var crashReporting @Dependency(\.userDefaults) private var userDefaults @Environment(CurrentUser.self) var currentUser - + @State private var lastSelectedTab = AppDestination.home @State private var showNIP05Wizard = false var body: some View { ZStack { - if appController.currentState == .onboarding { + switch appController.currentState { + case .loading: + SplashScreenView() + .ignoresSafeArea() + case .onboarding: OnboardingView(completion: appController.completeOnboarding) - } else { - TabView(selection: $router.selectedTab) { - if let author = currentUser.author { - HomeTab(user: author) - .tabItem { - VStack { - let text = Text(.localizable.homeFeed) - if $router.selectedTab.wrappedValue == .home { - Image.tabIconHomeSelected - text - } else { - Image.tabIconHome - text.foregroundColor(.secondaryTxt) - } - } - } - .toolbarBackground(.visible, for: .tabBar) - .toolbarBackground(Color.cardBgBottom, for: .tabBar) - .tag(AppDestination.home) - .onAppear { - // TODO: Move this somewhere better like CurrentUser when it becomes the source of truth - // for who is logged in - if let keyPair = currentUser.keyPair { - analytics.identify( - with: keyPair, - nip05: currentUser.author?.nip05 - ) - crashReporting.identify(with: keyPair) - } - } - } - - DiscoverTab() - .tabItem { - VStack { - let text = Text(.localizable.discover) - if $router.selectedTab.wrappedValue == .discover { - Image.tabIconEveryoneSelected - text.foregroundColor(.primaryTxt) - } else { - Image.tabIconEveryone - text.foregroundColor(.secondaryTxt) - } - } - } - .toolbarBackground(.visible, for: .tabBar) - .toolbarBackground(Color.cardBgBottom, for: .tabBar) - .tag(AppDestination.discover) - - VStack {} - .tabItem { - VStack { - Image.newPostButton - Text(.localizable.post) - } - } - .tag(AppDestination.noteComposer(nil)) - - NotificationsView(user: currentUser.author) - .tabItem { - VStack { - let text = Text(.localizable.notifications) - if $router.selectedTab.wrappedValue == .notifications { - Image.tabIconNotificationsSelected - text.foregroundColor(.primaryTxt) - } else { - Image.tabIconNotifications - text.foregroundColor(.secondaryTxt) - } - } - } - .toolbarBackground(.visible, for: .tabBar) - .toolbarBackground(Color.cardBgBottom, for: .tabBar) - .tag(AppDestination.notifications) - .badge(pushNotificationService.badgeCount) - - if let author = currentUser.author { - ProfileTab(author: author, path: $router.profilePath) - .tabItem { - VStack { - let text = Text(.localizable.profileTitle) - if $router.selectedTab.wrappedValue == .profile { - Image.tabProfileSelected - text.foregroundColor(.primaryTxt) - } else { - Image.tabProfile - text.foregroundColor(.secondaryTxt) - } - } - } - .toolbarBackground(.visible, for: .tabBar) - .toolbarBackground(Color.cardBgBottom, for: .tabBar) - .tag(AppDestination.profile) - } - } - .onChange(of: router.selectedTab) { _, newTab in - if case let AppDestination.noteComposer(contents) = newTab { - newPostContents = contents - showNewPost = true - router.selectedTab = lastSelectedTab - } else if !showNewPost { - lastSelectedTab = newTab - } - } - .overlay { - if router.isLoading { - ZStack { - Rectangle().fill(.black.opacity(0.4)) - ProgressView() - } - } - } - .sheet(isPresented: $showNewPost, content: { - NoteComposer(initialContents: newPostContents, isPresented: $showNewPost) - .environment(currentUser) - .interactiveDismissDisabled() - }) - + case .loggedIn: + tabView + SideMenu( menuWidth: 300, menuOpened: router.sideMenuOpened, @@ -159,6 +48,128 @@ struct AppView: View { .tint(.primaryTxt) } + private var tabView: some View { + TabView(selection: $router.selectedTab) { + if let author = currentUser.author { + HomeTab(user: author) + .tabItem { + VStack { + let text = Text(.localizable.homeFeed) + if $router.selectedTab.wrappedValue == .home { + Image.tabIconHomeSelected + text + } else { + Image.tabIconHome + text.foregroundColor(.secondaryTxt) + } + } + } + .toolbarBackground(.visible, for: .tabBar) + .toolbarBackground(Color.cardBgBottom, for: .tabBar) + .tag(AppDestination.home) + .onAppear { + // TODO: Move this somewhere better like CurrentUser when it becomes the source of truth + // for who is logged in + if let keyPair = currentUser.keyPair { + analytics.identify( + with: keyPair, + nip05: currentUser.author?.nip05 + ) + crashReporting.identify(with: keyPair) + } + } + } + + DiscoverTab() + .tabItem { + VStack { + let text = Text(.localizable.discover) + if $router.selectedTab.wrappedValue == .discover { + Image.tabIconEveryoneSelected + text.foregroundColor(.primaryTxt) + } else { + Image.tabIconEveryone + text.foregroundColor(.secondaryTxt) + } + } + } + .toolbarBackground(.visible, for: .tabBar) + .toolbarBackground(Color.cardBgBottom, for: .tabBar) + .tag(AppDestination.discover) + + VStack {} + .tabItem { + VStack { + Image.newPostButton + Text(.localizable.post) + } + } + .tag(AppDestination.noteComposer(nil)) + + NotificationsView(user: currentUser.author) + .tabItem { + VStack { + let text = Text(.localizable.notifications) + if $router.selectedTab.wrappedValue == .notifications { + Image.tabIconNotificationsSelected + text.foregroundColor(.primaryTxt) + } else { + Image.tabIconNotifications + text.foregroundColor(.secondaryTxt) + } + } + } + .toolbarBackground(.visible, for: .tabBar) + .toolbarBackground(Color.cardBgBottom, for: .tabBar) + .tag(AppDestination.notifications) + .badge(pushNotificationService.badgeCount) + + if let author = currentUser.author { + ProfileTab(author: author, path: $router.profilePath) + .tabItem { + VStack { + let text = Text(.localizable.profileTitle) + if $router.selectedTab.wrappedValue == .profile { + Image.tabProfileSelected + text.foregroundColor(.primaryTxt) + } else { + Image.tabProfile + text.foregroundColor(.secondaryTxt) + } + } + } + .toolbarBackground(.visible, for: .tabBar) + .toolbarBackground(Color.cardBgBottom, for: .tabBar) + .tag(AppDestination.profile) + } + } + .onChange(of: router.selectedTab) { _, newTab in + if case let AppDestination.noteComposer(contents) = newTab { + newPostContents = contents + showNewPost = true + router.selectedTab = lastSelectedTab + } else if !showNewPost { + lastSelectedTab = newTab + } + } + .overlay { + if router.isLoading { + ZStack { + Rectangle().fill(.black.opacity(0.4)) + ProgressView() + } + } + } + .sheet(isPresented: $showNewPost) { + NoteComposer(initialContents: newPostContents, isPresented: $showNewPost) + .environment(currentUser) + .interactiveDismissDisabled() + } + } +} + +extension AppView { + private func presentNIP05SheetIfNeeded() async { guard let author = currentUser.author, let npub = author.npubString else { return diff --git a/Nos/Views/SplashScreenView.swift b/Nos/Views/SplashScreenView.swift new file mode 100644 index 000000000..a00803a45 --- /dev/null +++ b/Nos/Views/SplashScreenView.swift @@ -0,0 +1,21 @@ +import SwiftUI + +/// Displays our splash screen as a SwiftUI view +struct SplashScreenView: UIViewControllerRepresentable { + + let storyboardName = "Launch Screen" + + // This function loads the storyboard and returns the view controller + func makeUIViewController(context: Context) -> UIViewController { + let storyboard = UIStoryboard(name: storyboardName, bundle: Bundle.main) + let viewController = storyboard.instantiateInitialViewController() ?? UIViewController() + return viewController + } + + // Required but typically unused + func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} +} + +#Preview { + SplashScreenView() +}