From a99ac232ca4c95eda0574c638351fcc19352b0d2 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Thu, 21 Dec 2023 19:23:52 +0800 Subject: [PATCH] More tab bar pop behavior --- .../Sources/App/Views/Labels/LabelsView.swift | 5 + .../App/Views/Profile/FiltersView.swift | 5 + .../Views/Profile/NewsletterEmailsView.swift | 41 +--- .../App/Views/Profile/ProfileView.swift | 6 +- .../Profile/PushNotificationDevicesView.swift | 5 + .../PushNotificationSettingsView.swift | 5 + .../Profile/RecommendationGroupsView.swift | 178 ------------------ .../App/Views/Profile/SubscriptionsView.swift | 48 +---- .../Profile/TextToSpeechLanguageView.swift | 4 + .../App/Views/Profile/TextToSpeechView.swift | 4 + .../TextToSpeechVoiceSelectionView.swift | 3 + .../Views/WebReader/WebReaderContainer.swift | 36 +--- 12 files changed, 48 insertions(+), 292 deletions(-) delete mode 100644 apple/OmnivoreKit/Sources/App/Views/Profile/RecommendationGroupsView.swift diff --git a/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsView.swift b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsView.swift index bc13bdb04..86c3f4c59 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsView.swift @@ -10,6 +10,8 @@ struct LabelsView: View { @State private var showDeleteConfirmation = false @State private var labelToRemove: LinkedItemLabel? + @Environment(\.dismiss) private var dismiss + @AppStorage(UserDefaultKey.hideSystemLabels.rawValue, store: UserDefaults(suiteName: "group.app.omnivoreapp")) var hideSystemLabels = false var body: some View { @@ -55,6 +57,9 @@ struct LabelsView: View { await viewModel.loadLabels(dataService: dataService, item: nil) } } + .onReceive(NotificationCenter.default.publisher(for: Notification.Name("ScrollToTop"))) { _ in + dismiss() + } .sheet(isPresented: $viewModel.showCreateLabelModal) { CreateLabelView(viewModel: viewModel, newLabelName: viewModel.labelSearchFilter) } diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/FiltersView.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/FiltersView.swift index d4628b384..fa743272a 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Profile/FiltersView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Profile/FiltersView.swift @@ -59,6 +59,8 @@ import Views struct FiltersView: View { @EnvironmentObject var dataService: DataService + @Environment(\.dismiss) private var dismiss + @StateObject var viewModel = FiltersViewModel() var body: some View { @@ -79,6 +81,9 @@ struct FiltersView: View { viewModel.loadBadgePermission() await viewModel.loadFilters(dataService: dataService) } + .onReceive(NotificationCenter.default.publisher(for: Notification.Name("ScrollToTop"))) { _ in + dismiss() + } } private var innerBody: some View { diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/NewsletterEmailsView.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/NewsletterEmailsView.swift index f9abe596f..2ab0b17c9 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Profile/NewsletterEmailsView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Profile/NewsletterEmailsView.swift @@ -64,6 +64,8 @@ import Views struct NewsletterEmailsView: View { @EnvironmentObject var dataService: DataService + @Environment(\.dismiss) private var dismiss + @StateObject var viewModel = NewsletterEmailsViewModel() @State var snackbarOperation: SnackbarOperation? @@ -71,7 +73,7 @@ struct NewsletterEmailsView: View { var body: some View { Group { WindowLink(level: .alert, transition: .move(edge: .bottom), isPresented: $viewModel.showOperationToast) { - NewsletterOperationToast(viewModel: viewModel) + OperationToast(operationMessage: $viewModel.operationMessage, showOperationToast: $viewModel.showOperationToast, operationStatus: $viewModel.operationStatus) } label: { EmptyView() } @@ -92,6 +94,9 @@ struct NewsletterEmailsView: View { .listStyle(InsetListStyle()) #endif } + .onReceive(NotificationCenter.default.publisher(for: Notification.Name("ScrollToTop"))) { _ in + dismiss() + } .task { await viewModel.loadEmails(dataService: dataService) } .refreshable { Task { @@ -204,37 +209,3 @@ struct MessageToast: View { .ignoresSafeArea(.all, edges: .bottom) } } - -struct NewsletterOperationToast: View { - @ObservedObject var viewModel: NewsletterEmailsViewModel - - var body: some View { - VStack { - HStack { - if viewModel.operationStatus == .isPerforming { - Text(viewModel.operationMessage ?? "Performing...") - Spacer() - ProgressView() - } else if viewModel.operationStatus == .success { - Text(viewModel.operationMessage ?? "Success") - Spacer() - } else if viewModel.operationStatus == .failure { - Text(viewModel.operationMessage ?? "Failure") - Spacer() - Button(action: { viewModel.showOperationToast = false }, label: { - Text("Done").bold() - }) - } - } - .padding(10) - .frame(minHeight: 50) - .frame(maxWidth: 380) - .background(Color(hex: "2A2A2A")) - .cornerRadius(4.0) - .tint(Color.green) - } - .padding(.bottom, 70) - .padding(.horizontal, 10) - .ignoresSafeArea(.all, edges: .bottom) - } -} diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/ProfileView.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/ProfileView.swift index 732b543dd..db8b0f8c5 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Profile/ProfileView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Profile/ProfileView.swift @@ -64,12 +64,13 @@ struct ProfileView: View { @StateObject private var viewModel = ProfileContainerViewModel() + @State var shouldScrollToTop = false @State private var showLogoutConfirmation = false var body: some View { #if os(iOS) - Form { - innerBody + List { + innerBody.tag("TOP") } .toolbar { toolbarItems @@ -125,6 +126,7 @@ struct ProfileView: View { Group { Section { ProfileCard(data: viewModel.profileCardData) + .tag("PROFILE") .task { await viewModel.loadProfileData(dataService: dataService) } diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/PushNotificationDevicesView.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/PushNotificationDevicesView.swift index 2e3346a00..0d2a1d485 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Profile/PushNotificationDevicesView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Profile/PushNotificationDevicesView.swift @@ -28,6 +28,8 @@ import Views struct PushNotificationDevicesView: View { @EnvironmentObject var dataService: DataService + @Environment(\.dismiss) private var dismiss + @StateObject var viewModel = PushNotificationDevicesViewModel() var body: some View { @@ -43,6 +45,9 @@ struct PushNotificationDevicesView: View { .listStyle(InsetListStyle()) #endif } + .onReceive(NotificationCenter.default.publisher(for: Notification.Name("ScrollToTop"))) { _ in + dismiss() + } .task { viewModel.loadDevices(dataService: dataService) } } diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/PushNotificationSettingsView.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/PushNotificationSettingsView.swift index 60fb512f9..df3be30d7 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Profile/PushNotificationSettingsView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Profile/PushNotificationSettingsView.swift @@ -47,6 +47,8 @@ struct PushNotificationSettingsView: View { @EnvironmentObject var dataService: DataService + @Environment(\.dismiss) private var dismiss + @StateObject var viewModel = PushNotificationSettingsViewModel() @State var desiredNotificationsEnabled: Bool = false @@ -63,6 +65,9 @@ .listStyle(InsetListStyle()) #endif } + .onReceive(NotificationCenter.default.publisher(for: Notification.Name("ScrollToTop"))) { _ in + dismiss() + } .task { viewModel.checkPushNotificationsStatus() } } diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/RecommendationGroupsView.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/RecommendationGroupsView.swift deleted file mode 100644 index fb741b32d..000000000 --- a/apple/OmnivoreKit/Sources/App/Views/Profile/RecommendationGroupsView.swift +++ /dev/null @@ -1,178 +0,0 @@ -#if os(iOS) - import Models - import Services - import SwiftUI - import Views - - @MainActor final class RecommendationsGroupsViewModel: ObservableObject { - @Published var isLoading = false - @Published var isCreating = false - @Published var networkError = false - @Published var recommendationGroups = [InternalRecommendationGroup]() - - @Published var showCreateSheet = false - - @Published var showCreateError = false - @Published var createGroupError: String? - - @Published var onlyAdminCanPost = false - @Published var onlyAdminCanSeeMembers = false - - func loadGroups(dataService: DataService) async { - isLoading = true - - do { - recommendationGroups = try await dataService.recommendationGroups() - } catch { - networkError = true - } - - isLoading = false - } - - func createGroup(dataService: DataService, name: String) async { - isCreating = true - - let group = try? await dataService.createRecommendationGroup(name: name, - onlyAdminCanPost: onlyAdminCanPost, - onlyAdminCanSeeMembers: onlyAdminCanSeeMembers) - - if group != nil { - await loadGroups(dataService: dataService) - showCreateSheet = false - } else { - createGroupError = "Error creating club" - showCreateError = true - } - - isCreating = false - } - } - - struct CreateRecommendationGroupView: View { - @State var name = "" - @EnvironmentObject var dataService: DataService - @StateObject var viewModel = RecommendationsGroupsViewModel() - - var nextButton: some View { - if viewModel.isCreating { - return AnyView(ProgressView()) - } else { - return AnyView(Button(action: { - Task { - await viewModel.createGroup(dataService: dataService, name: self.name) - } - }, label: { - Text(LocalText.genericNext) - }) - .disabled(name.isEmpty) - ) - } - } - - var body: some View { - NavigationView { - Form { - TextField(LocalText.genericName, text: $name, prompt: Text(LocalText.clubsName)) - - Section("Club Rules") { - Toggle("Only admins can post", isOn: $viewModel.onlyAdminCanPost) - Toggle("Only admins can see members", isOn: $viewModel.onlyAdminCanSeeMembers) - } - - Section { - Section { - Text(""" - [Learn more about clubs](https://blog.omnivore.app/p/dca38ba4-8a74-42cc-90ca-d5ffa5d075cc) - """) - .accentColor(.blue) - } - } - } - .alert(isPresented: $viewModel.showCreateError) { - Alert( - title: Text(viewModel.createGroupError ?? "Error creating group"), - dismissButton: .cancel(Text(LocalText.genericOk)) { - viewModel.createGroupError = nil - viewModel.showCreateError = false - } - ) - } - } - #if os(iOS) - .navigationViewStyle(.stack) - .navigationBarTitleDisplayMode(.inline) - .navigationBarItems(leading: - Button(action: { - viewModel.showCreateSheet = false - }, label: { Text(LocalText.cancelGeneric) }), - trailing: nextButton) - #endif - .navigationTitle("Create Club") - } - } - - struct GroupsView: View { - @EnvironmentObject var dataService: DataService - @StateObject var viewModel = RecommendationsGroupsViewModel() - - var body: some View { - Group { - #if os(iOS) - Form { - innerBody - } - #elseif os(macOS) - List { - innerBody - } - .listStyle(InsetListStyle()) - #endif - } - .sheet(isPresented: $viewModel.showCreateSheet) { - NavigationView { - CreateRecommendationGroupView(viewModel: self.viewModel) - } - } - .task { await viewModel.loadGroups(dataService: dataService) } - .navigationTitle(LocalText.clubsGeneric) - } - - private var innerBody: some View { - Group { - Section { - Button( - action: { viewModel.showCreateSheet = true }, - label: { - HStack { - Image(systemName: "plus.circle.fill").foregroundColor(.green) - Text(LocalText.clubsCreate) - Spacer() - } - } - ) - } - - if !viewModel.isLoading { - if viewModel.recommendationGroups.count > 0 { - Section(header: Text(LocalText.clubsYours)) { - ForEach(viewModel.recommendationGroups) { recommendationGroup in - let viewModel = RecommendationsGroupViewModel(recommendationGroup: recommendationGroup) - NavigationLink( - destination: RecommendationGroupView(viewModel: viewModel) - ) { - Text(recommendationGroup.name) - } - } - } - } else { - Section { - Text(LocalText.clubsNotAMemberMessage) - .accentColor(.blue) - } - } - } - } - } - } -#endif diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/SubscriptionsView.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/SubscriptionsView.swift index 62267165f..371b16b77 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Profile/SubscriptionsView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Profile/SubscriptionsView.swift @@ -5,13 +5,6 @@ import SwiftUI import Transmission import Views -enum OperationStatus { - case none - case isPerforming - case success - case failure -} - @MainActor struct ToastOperationHandler { let performOperation: (_: Sendable?) -> Void @@ -152,42 +145,10 @@ typealias OperationStatusHandler = (_: OperationStatus) -> Void } } -struct OperationToast: View { - @ObservedObject var viewModel: SubscriptionsViewModel - - var body: some View { - VStack { - HStack { - if viewModel.operationStatus == .isPerforming { - Text(viewModel.operationMessage ?? "Performing...") - Spacer() - ProgressView() - } else if viewModel.operationStatus == .success { - Text(viewModel.operationMessage ?? "Success") - Spacer() - } else if viewModel.operationStatus == .failure { - Text(viewModel.operationMessage ?? "Failure") - Spacer() - Button(action: { viewModel.showOperationToast = false }, label: { - Text("Done").bold() - }) - } - } - .padding(10) - .frame(minHeight: 50) - .frame(maxWidth: 380) - .background(Color(hex: "2A2A2A")) - .cornerRadius(4.0) - .tint(Color.green) - } - .padding(.bottom, 70) - .padding(.horizontal, 10) - .ignoresSafeArea(.all, edges: .bottom) - } -} - struct SubscriptionsView: View { @EnvironmentObject var dataService: DataService + @Environment(\.dismiss) private var dismiss + @StateObject var viewModel = SubscriptionsViewModel() @State private var deleteConfirmationShown = false @State private var showDeleteCompleted = false @@ -197,7 +158,7 @@ struct SubscriptionsView: View { var body: some View { Group { WindowLink(level: .alert, transition: .move(edge: .bottom), isPresented: $viewModel.showOperationToast) { - OperationToast(viewModel: viewModel) + OperationToast(operationMessage: $viewModel.operationMessage, showOperationToast: $viewModel.showOperationToast, operationStatus: $viewModel.operationStatus) } label: { EmptyView() } @@ -231,6 +192,9 @@ struct SubscriptionsView: View { #endif } } + .onReceive(NotificationCenter.default.publisher(for: Notification.Name("ScrollToTop"))) { _ in + dismiss() + } .sheet(isPresented: $showAddFeedView) { let handler = ToastOperationHandler(performOperation: { sendable in self.viewModel.showOperationToast = true diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechLanguageView.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechLanguageView.swift index 5469618b3..a6e4e21bf 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechLanguageView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechLanguageView.swift @@ -6,6 +6,7 @@ struct TextToSpeechLanguageView: View { @EnvironmentObject var audioController: AudioController + @Environment(\.dismiss) private var dismiss var body: some View { Group { @@ -20,6 +21,9 @@ .listStyle(InsetListStyle()) #endif } + .onReceive(NotificationCenter.default.publisher(for: Notification.Name("ScrollToTop"))) { _ in + dismiss() + } } private var innerBody: some View { diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechView.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechView.swift index 4fe368349..cd63fba3f 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechView.swift @@ -7,6 +7,7 @@ // swiftlint:disable line_length struct TextToSpeechView: View { @EnvironmentObject var audioController: AudioController + @Environment(\.dismiss) private var dismiss var body: some View { Group { @@ -20,6 +21,9 @@ innerBody } }.navigationTitle(LocalText.textToSpeechGeneric) + .onReceive(NotificationCenter.default.publisher(for: Notification.Name("ScrollToTop"))) { _ in + dismiss() + } } private var innerBody: some View { diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechVoiceSelectionView.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechVoiceSelectionView.swift index 2f1bb8c6f..46389bfbe 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechVoiceSelectionView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechVoiceSelectionView.swift @@ -8,6 +8,7 @@ struct TextToSpeechVoiceSelectionView: View { @EnvironmentObject var audioController: AudioController @EnvironmentObject var dataService: DataService + @Environment(\.dismiss) private var dismiss @StateObject var viewModel = TextToSpeechVoiceSelectionViewModel() @@ -87,6 +88,8 @@ } else if !value { audioController.useUltraRealisticVoices = false } + }.onReceive(NotificationCenter.default.publisher(for: Notification.Name("ScrollToTop"))) { _ in + dismiss() } } diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift index 5497f094e..c4ae5923f 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift @@ -369,7 +369,7 @@ struct WebReaderContainerView: View { var body: some View { ZStack { WindowLink(level: .alert, transition: .move(edge: .bottom), isPresented: $viewModel.showOperationToast) { - ReaderOperationToast(viewModel: viewModel) + OperationToast(operationMessage: $viewModel.operationMessage, showOperationToast: $viewModel.showOperationToast, operationStatus: $viewModel.operationStatus) } label: { EmptyView() } @@ -697,37 +697,3 @@ struct WebReaderContainerView: View { openURL(url) } } - -struct ReaderOperationToast: View { - @State var viewModel: WebReaderViewModel - - var body: some View { - VStack { - HStack { - if viewModel.operationStatus == .isPerforming { - Text(viewModel.operationMessage ?? "Performing...") - Spacer() - ProgressView() - } else if viewModel.operationStatus == .success { - Text(viewModel.operationMessage ?? "Success") - Spacer() - } else if viewModel.operationStatus == .failure { - Text(viewModel.operationMessage ?? "Failure") - Spacer() - Button(action: { viewModel.showOperationToast = false }, label: { - Text("Done").bold() - }) - } - } - .padding(10) - .frame(minHeight: 50) - .frame(maxWidth: 380) - .background(Color(hex: "2A2A2A")) - .cornerRadius(4.0) - .tint(Color.green) - } - .padding(.bottom, 60) - .padding(.horizontal, 10) - .ignoresSafeArea(.all, edges: .bottom) - } -}