More tab bar pop behavior

This commit is contained in:
Jackson Harper 2023-12-21 19:23:52 +08:00
parent dccee35fbe
commit a99ac232ca
12 changed files with 48 additions and 292 deletions

View file

@ -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)
}

View file

@ -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 {

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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) }
}

View file

@ -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() }
}

View file

@ -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

View file

@ -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

View file

@ -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 {

View file

@ -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 {

View file

@ -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()
}
}

View file

@ -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)
}
}