mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
More tab bar pop behavior
This commit is contained in:
parent
dccee35fbe
commit
a99ac232ca
12 changed files with 48 additions and 292 deletions
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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() }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue