Merge pull request #113 from omnivore-app/feature/tab-bar-alts
Remove TabBar on iPhone
This commit is contained in:
@ -29,21 +29,6 @@ public final class RootViewModel: ObservableObject {
|
||||
registerFonts()
|
||||
}
|
||||
|
||||
func updateWaitlistStatus() {
|
||||
services.dataService.viewerPublisher().sink(
|
||||
receiveCompletion: { completion in
|
||||
guard case let .failure(error) = completion else { return }
|
||||
print(error)
|
||||
},
|
||||
receiveValue: { [weak self] viewer in
|
||||
guard let self = self else { return }
|
||||
let isWaitlisted = viewer.isWaitlisted
|
||||
self.services.authenticator.updateWaitlistStatus(isWaitlistedUser: isWaitlisted)
|
||||
}
|
||||
)
|
||||
.store(in: &subscriptions)
|
||||
}
|
||||
|
||||
func configurePDFProvider(pdfViewerProvider: @escaping (URL, PDFViewerViewModel) -> AnyView) {
|
||||
PDFProvider.pdfViewerProvider = { [weak self] url, feedItem in
|
||||
guard let self = self else { return AnyView(Text("")) }
|
||||
@ -133,8 +118,6 @@ public struct RootView: View {
|
||||
@ObservedObject private var viewModel: RootViewModel
|
||||
@ObservedObject private var authenticator: Authenticator
|
||||
|
||||
private var primaryViewModel: PrimaryContentViewModel
|
||||
|
||||
public init(
|
||||
pdfViewerProvider: ((URL, PDFViewerViewModel) -> AnyView)?,
|
||||
intercomProvider: IntercomProvider?
|
||||
@ -142,7 +125,6 @@ public struct RootView: View {
|
||||
let rootViewModel = RootViewModel()
|
||||
self.viewModel = rootViewModel
|
||||
self.authenticator = rootViewModel.services.authenticator
|
||||
self.primaryViewModel = PrimaryContentViewModel.make(services: rootViewModel.services)
|
||||
|
||||
#if DEBUG
|
||||
if CommandLine.arguments.contains("--uitesting") {
|
||||
@ -163,34 +145,30 @@ public struct RootView: View {
|
||||
|
||||
@ViewBuilder private var innerBody: some View {
|
||||
if authenticator.isLoggedIn {
|
||||
if authenticator.isWaitlisted {
|
||||
WaitlistView(viewModel: WaitlistViewModel.make(services: viewModel.services))
|
||||
} else {
|
||||
PrimaryContentView(viewModel: primaryViewModel)
|
||||
.onAppear {
|
||||
viewModel.updateWaitlistStatus()
|
||||
viewModel.triggerPushNotificationRequestIfNeeded()
|
||||
PrimaryContentView(services: viewModel.services)
|
||||
.onAppear {
|
||||
viewModel.triggerPushNotificationRequestIfNeeded()
|
||||
}
|
||||
#if os(iOS)
|
||||
.fullScreenCover(item: $viewModel.webLinkPath, content: { safariLinkPath in
|
||||
NavigationView {
|
||||
FullScreenWebAppView(
|
||||
viewModel: viewModel.webAppWrapperViewModel(webLinkPath: safariLinkPath.path),
|
||||
handleClose: { viewModel.webLinkPath = nil }
|
||||
)
|
||||
}
|
||||
#if os(iOS)
|
||||
.fullScreenCover(item: $viewModel.webLinkPath, content: { safariLinkPath in
|
||||
NavigationView {
|
||||
FullScreenWebAppView(
|
||||
viewModel: viewModel.webAppWrapperViewModel(webLinkPath: safariLinkPath.path),
|
||||
handleClose: { viewModel.webLinkPath = nil }
|
||||
)
|
||||
}
|
||||
})
|
||||
#endif
|
||||
.snackBar(
|
||||
isShowing: $viewModel.showSnackbar,
|
||||
text: Text(viewModel.snackbarMessage ?? "")
|
||||
)
|
||||
#if os(iOS)
|
||||
.customAlert(isPresented: $viewModel.showPushNotificationPrimer) {
|
||||
pushNotificationPrimerView
|
||||
}
|
||||
#endif
|
||||
}
|
||||
})
|
||||
#endif
|
||||
.snackBar(
|
||||
isShowing: $viewModel.showSnackbar,
|
||||
text: Text(viewModel.snackbarMessage ?? "")
|
||||
)
|
||||
#if os(iOS)
|
||||
.customAlert(isPresented: $viewModel.showPushNotificationPrimer) {
|
||||
pushNotificationPrimerView
|
||||
}
|
||||
#endif
|
||||
|
||||
} else {
|
||||
WelcomeView(viewModel: WelcomeViewModel.make(services: viewModel.services))
|
||||
.accessibilityElement()
|
||||
|
||||
@ -11,6 +11,10 @@ extension HomeFeedViewModel {
|
||||
LinkItemDetailViewModel.make(feedItem: feedItem, services: services)
|
||||
}
|
||||
|
||||
if UIDevice.isIPhone {
|
||||
viewModel.profileContainerViewModel = ProfileContainerViewModel.make(services: services)
|
||||
}
|
||||
|
||||
viewModel.bind(services: services)
|
||||
viewModel.loadItems(dataService: services.dataService, searchQuery: nil, isRefresh: false)
|
||||
return viewModel
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
import Services
|
||||
import SwiftUI
|
||||
import Views
|
||||
|
||||
extension PrimaryContentViewModel {
|
||||
static func make(services: Services) -> PrimaryContentViewModel {
|
||||
let viewModel = PrimaryContentViewModel(
|
||||
homeFeedViewModel: HomeFeedViewModel.make(services: services),
|
||||
profileContainerViewModel: ProfileContainerViewModel.make(services: services)
|
||||
)
|
||||
viewModel.bind(services: services)
|
||||
return viewModel
|
||||
}
|
||||
|
||||
func bind(services _: Services) {
|
||||
performActionSubject.sink { action in
|
||||
switch action {
|
||||
case .nothing:
|
||||
break
|
||||
}
|
||||
}
|
||||
.store(in: &subscriptions)
|
||||
}
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
import Models
|
||||
import Services
|
||||
import SwiftUI
|
||||
import Utils
|
||||
import Views
|
||||
|
||||
extension WaitlistViewModel {
|
||||
static func make(services: Services) -> WaitlistViewModel {
|
||||
let viewModel = WaitlistViewModel()
|
||||
viewModel.bind(services: services)
|
||||
return viewModel
|
||||
}
|
||||
|
||||
func bind(services: Services) {
|
||||
performActionSubject.sink { [weak self] action in
|
||||
switch action {
|
||||
case .logout:
|
||||
services.authenticator.logout()
|
||||
case .checkStatus:
|
||||
self?.updateWaitlistStatus(services: services)
|
||||
}
|
||||
}
|
||||
.store(in: &subscriptions)
|
||||
}
|
||||
|
||||
private func updateWaitlistStatus(services: Services) {
|
||||
services.dataService.viewerPublisher().sink(
|
||||
receiveCompletion: { completion in
|
||||
guard case let .failure(error) = completion else { return }
|
||||
print(error)
|
||||
},
|
||||
receiveValue: { viewer in
|
||||
let isWaitlisted = viewer.isWaitlisted
|
||||
services.authenticator.updateWaitlistStatus(isWaitlistedUser: isWaitlisted)
|
||||
}
|
||||
)
|
||||
.store(in: &subscriptions)
|
||||
}
|
||||
}
|
||||
@ -1,34 +1,15 @@
|
||||
import Combine
|
||||
import Models
|
||||
import Services
|
||||
import SwiftUI
|
||||
|
||||
public final class PrimaryContentViewModel: ObservableObject {
|
||||
let categories: [PrimaryContentCategory]
|
||||
|
||||
public enum Action {
|
||||
case nothing
|
||||
}
|
||||
|
||||
public var subscriptions = Set<AnyCancellable>()
|
||||
public let performActionSubject = PassthroughSubject<Action, Never>()
|
||||
|
||||
public init(
|
||||
homeFeedViewModel: HomeFeedViewModel,
|
||||
profileContainerViewModel: ProfileContainerViewModel
|
||||
) {
|
||||
self.categories = [
|
||||
.feed(viewModel: homeFeedViewModel),
|
||||
.profile(viewModel: profileContainerViewModel)
|
||||
]
|
||||
}
|
||||
}
|
||||
import Views
|
||||
|
||||
public struct PrimaryContentView: View {
|
||||
@ObservedObject private var viewModel: PrimaryContentViewModel
|
||||
@State private var currentTab = 0
|
||||
let homeFeedViewModel: HomeFeedViewModel
|
||||
let profileContainerViewModel: ProfileContainerViewModel
|
||||
|
||||
public init(viewModel: PrimaryContentViewModel) {
|
||||
self.viewModel = viewModel
|
||||
public init(services: Services) {
|
||||
self.homeFeedViewModel = HomeFeedViewModel.make(services: services)
|
||||
self.profileContainerViewModel = ProfileContainerViewModel.make(services: services)
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
@ -45,27 +26,23 @@ public struct PrimaryContentView: View {
|
||||
|
||||
// iphone view container
|
||||
private var compactView: some View {
|
||||
TabView(selection: $currentTab) {
|
||||
ForEach(viewModel.categories.indices, id: \.self) { index in
|
||||
viewModel.categories[index].destinationView
|
||||
.tabItem {
|
||||
TabIcon(isSelected: currentTab == index, primaryContentCategory: viewModel.categories[index])
|
||||
}
|
||||
.tag(index)
|
||||
}
|
||||
}
|
||||
.accentColor(.appGrayTextContrast)
|
||||
HomeFeedView(viewModel: homeFeedViewModel)
|
||||
}
|
||||
|
||||
// ipad and mac view container
|
||||
private var regularView: some View {
|
||||
NavigationView {
|
||||
let categories = [
|
||||
PrimaryContentCategory.feed(viewModel: homeFeedViewModel),
|
||||
PrimaryContentCategory.profile(viewModel: profileContainerViewModel)
|
||||
]
|
||||
|
||||
return NavigationView {
|
||||
// The first column is the sidebar.
|
||||
PrimaryContentSidebar(categories: viewModel.categories)
|
||||
PrimaryContentSidebar(categories: categories)
|
||||
.navigationTitle("Categories")
|
||||
|
||||
// Initial Content of second column
|
||||
if let destinationView = viewModel.categories.first?.destinationView {
|
||||
if let destinationView = categories.first?.destinationView {
|
||||
destinationView
|
||||
} else {
|
||||
Text("Select a Category")
|
||||
@ -50,19 +50,16 @@ public struct Viewer {
|
||||
public let username: String
|
||||
public let name: String
|
||||
public let profileImageURL: String?
|
||||
public let isWaitlisted: Bool
|
||||
|
||||
public init(
|
||||
username: String,
|
||||
name: String,
|
||||
profileImageURL: String?,
|
||||
isWaitlisted: Bool,
|
||||
userID: String
|
||||
) {
|
||||
self.username = username
|
||||
self.name = name
|
||||
self.profileImageURL = profileImageURL
|
||||
self.isWaitlisted = isWaitlisted
|
||||
self.userID = userID
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,7 +14,6 @@ public final class Authenticator: ObservableObject {
|
||||
}
|
||||
|
||||
@Published public internal(set) var isLoggedIn: Bool
|
||||
@Published public internal(set) var isWaitlisted = false
|
||||
|
||||
let networker: Networker
|
||||
|
||||
@ -58,10 +57,6 @@ public final class Authenticator: ObservableObject {
|
||||
return !authToken.isEmpty
|
||||
}
|
||||
|
||||
public func updateWaitlistStatus(isWaitlistedUser: Bool) {
|
||||
isWaitlisted = isWaitlistedUser
|
||||
}
|
||||
|
||||
private func clearCookies() {
|
||||
HTTPCookieStorage.shared.removeCookies(since: Date.distantPast)
|
||||
|
||||
|
||||
@ -30,7 +30,6 @@ extension DataService {
|
||||
profileImageURL: try $0.profile(
|
||||
selection: .init { try $0.pictureUrl() }
|
||||
),
|
||||
isWaitlisted: try !($0.isFullUser() ?? false),
|
||||
userID: try $0.id()
|
||||
)
|
||||
}
|
||||
|
||||
@ -15,5 +15,6 @@ extension Image {
|
||||
static var homeTab: Image { Image("_homeTab", bundle: .module) }
|
||||
static var homeTabSelected: Image { Image("_homeTabSelected", bundle: .module) }
|
||||
static var profileTab: Image { Image("_profileTab", bundle: .module) }
|
||||
static var profile: Image { Image("_profile", bundle: .module) }
|
||||
static var profileTabSelected: Image { Image("_profileTabSelected", bundle: .module) }
|
||||
}
|
||||
|
||||
15
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/_profile.imageset/Contents.json
vendored
Normal file
15
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/_profile.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "user-circle-gear.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="192" fill="#000000" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"></rect><circle cx="128" cy="120" r="40" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-width="16"></circle><path d="M63.8,199.4a72,72,0,0,1,128.4,0" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"></path><circle cx="200" cy="56" r="16" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"></circle><line x1="200" y1="40" x2="200" y2="28" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"></line><line x1="186.1" y1="48" x2="175.8" y2="42" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"></line><line x1="186.1" y1="64" x2="175.8" y2="70" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"></line><line x1="200" y1="72" x2="200" y2="84" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"></line><line x1="213.9" y1="64" x2="224.2" y2="70" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"></line><line x1="213.9" y1="48" x2="224.2" y2="42" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"></line><path d="M223.3,116.5A87.7,87.7,0,0,1,224,128a96,96,0,1,1-96-96,87,87,0,0,1,8.9.4" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@ -6,6 +6,7 @@ import Utils
|
||||
public final class HomeFeedViewModel: ObservableObject {
|
||||
let detailViewModelCreator: (FeedItem) -> LinkItemDetailViewModel
|
||||
var currentDetailViewModel: LinkItemDetailViewModel?
|
||||
public var profileContainerViewModel: ProfileContainerViewModel?
|
||||
|
||||
@Published public var items = [FeedItem]()
|
||||
@Published public var isLoading = false
|
||||
@ -256,10 +257,26 @@ public struct HomeFeedView: View {
|
||||
|
||||
public var body: some View {
|
||||
#if os(iOS)
|
||||
if UIDevice.isIPhone {
|
||||
if UIDevice.isIPhone, let profileContainerViewModel = viewModel.profileContainerViewModel {
|
||||
NavigationView {
|
||||
conditionalInnerBody
|
||||
.toolbar {
|
||||
ToolbarItem {
|
||||
NavigationLink(
|
||||
destination: {
|
||||
ProfileContainerView(viewModel: profileContainerViewModel)
|
||||
},
|
||||
label: {
|
||||
Image.profile
|
||||
.resizable()
|
||||
.frame(width: 26, height: 26)
|
||||
.padding()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.accentColor(.appGrayTextContrast)
|
||||
} else {
|
||||
conditionalInnerBody
|
||||
}
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import SwiftUI
|
||||
|
||||
enum PrimaryContentCategory: Identifiable, Hashable, Equatable {
|
||||
public enum PrimaryContentCategory: Identifiable, Hashable, Equatable {
|
||||
case feed(viewModel: HomeFeedViewModel)
|
||||
case profile(viewModel: ProfileContainerViewModel)
|
||||
|
||||
static func == (lhs: PrimaryContentCategory, rhs: PrimaryContentCategory) -> Bool {
|
||||
public static func == (lhs: PrimaryContentCategory, rhs: PrimaryContentCategory) -> Bool {
|
||||
lhs.id == rhs.id
|
||||
}
|
||||
|
||||
var id: String {
|
||||
public var id: String {
|
||||
title
|
||||
}
|
||||
|
||||
@ -39,11 +39,11 @@ enum PrimaryContentCategory: Identifiable, Hashable, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
var listLabel: some View {
|
||||
public var listLabel: some View {
|
||||
Label { Text(title) } icon: { image.renderingMode(.template) }
|
||||
}
|
||||
|
||||
@ViewBuilder var destinationView: some View {
|
||||
@ViewBuilder public var destinationView: some View {
|
||||
switch self {
|
||||
case let .feed(viewModel: viewModel):
|
||||
HomeFeedView(viewModel: viewModel)
|
||||
@ -52,24 +52,7 @@ enum PrimaryContentCategory: Identifiable, Hashable, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
}
|
||||
}
|
||||
|
||||
struct TabIcon: View {
|
||||
let isSelected: Bool
|
||||
let primaryContentCategory: PrimaryContentCategory
|
||||
|
||||
var body: some View {
|
||||
if isSelected {
|
||||
Label {
|
||||
Text(primaryContentCategory.title)
|
||||
} icon: {
|
||||
primaryContentCategory.selectedImage
|
||||
}
|
||||
} else {
|
||||
primaryContentCategory.listLabel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,16 +30,8 @@ public struct ProfileContainerView: View {
|
||||
|
||||
public var body: some View {
|
||||
#if os(iOS)
|
||||
if UIDevice.isIPhone {
|
||||
NavigationView {
|
||||
Form {
|
||||
innerBody
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Form {
|
||||
innerBody
|
||||
}
|
||||
Form {
|
||||
innerBody
|
||||
}
|
||||
#elseif os(macOS)
|
||||
List {
|
||||
|
||||
@ -1,125 +0,0 @@
|
||||
import Combine
|
||||
import Models
|
||||
import SwiftUI
|
||||
|
||||
public final class WaitlistViewModel: ObservableObject {
|
||||
public enum Action {
|
||||
case logout
|
||||
case checkStatus
|
||||
}
|
||||
|
||||
public var subscriptions = Set<AnyCancellable>()
|
||||
public let performActionSubject = PassthroughSubject<Action, Never>()
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
public struct WaitlistView: View {
|
||||
@Environment(\.horizontalSizeClass) var horizontalSizeClass
|
||||
@ObservedObject private var viewModel: WaitlistViewModel
|
||||
|
||||
public init(viewModel: WaitlistViewModel) {
|
||||
self.viewModel = viewModel
|
||||
}
|
||||
|
||||
@ViewBuilder func userInteractiveView(width: CGFloat) -> some View {
|
||||
waitlistButtonView
|
||||
.frame(width: width)
|
||||
.zIndex(2)
|
||||
}
|
||||
|
||||
var titleLogo: some View {
|
||||
Image.omnivoreTitleLogo
|
||||
.renderingMode(.template)
|
||||
.foregroundColor(.appGrayTextContrast)
|
||||
.frame(height: 40)
|
||||
}
|
||||
|
||||
var waitlistButtonView: some View {
|
||||
VStack(alignment: .center, spacing: 32) {
|
||||
Text("Your username has been reserved. We will inform you by email when we open up Omnivore to more users.")
|
||||
.font(.appHeadline)
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(maxWidth: 300)
|
||||
|
||||
BorderedButton(color: .appGrayTextContrast, text: "Check Status") {
|
||||
viewModel.performActionSubject.send(.checkStatus)
|
||||
}
|
||||
.frame(width: 220)
|
||||
|
||||
BorderedButton(color: .appGrayTextContrast, text: "Logout") {
|
||||
viewModel.performActionSubject.send(.logout)
|
||||
}
|
||||
.frame(width: 220)
|
||||
}
|
||||
.padding(.horizontal, horizontalSizeClass == .compact ? 16 : 80)
|
||||
.padding(.top, horizontalSizeClass == .compact ? 16 : 0)
|
||||
}
|
||||
|
||||
@ViewBuilder func splitColorBackground(width: CGFloat) -> some View {
|
||||
HStack(spacing: 0) {
|
||||
Color.systemBackground.frame(width: width * 0.5)
|
||||
Color.appBackground.frame(width: width * 0.5)
|
||||
}
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
}
|
||||
|
||||
@ViewBuilder func largeBackgroundImage(width: CGFloat) -> some View {
|
||||
Image.readingIllustrationXXL
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: width)
|
||||
.clipped()
|
||||
.edgesIgnoringSafeArea([.vertical, .trailing])
|
||||
}
|
||||
|
||||
@ViewBuilder func primaryContent() -> some View {
|
||||
if horizontalSizeClass == .compact {
|
||||
GeometryReader { geometry in
|
||||
ZStack(alignment: .leading) {
|
||||
Color.systemBackground
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
|
||||
if geometry.size.width < geometry.size.height {
|
||||
VStack {
|
||||
Color.appDeepBackground.frame(height: 100)
|
||||
Spacer()
|
||||
}
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
}
|
||||
|
||||
VStack {
|
||||
if geometry.size.width < geometry.size.height {
|
||||
RegistrationHeroImageView(
|
||||
tapGestureHandler: {}
|
||||
)
|
||||
}
|
||||
userInteractiveView(width: geometry.size.width)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
GeometryReader { geometry in
|
||||
ZStack(alignment: .leading) {
|
||||
splitColorBackground(width: geometry.size.width)
|
||||
|
||||
VStack {
|
||||
titleLogo
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
|
||||
HStack(spacing: 0) {
|
||||
userInteractiveView(width: geometry.size.width * 0.5)
|
||||
largeBackgroundImage(width: geometry.size.width * 0.5)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
primaryContent()
|
||||
}
|
||||
}
|
||||
@ -42,9 +42,6 @@ struct MainApp: App {
|
||||
WindowGroup {
|
||||
RootView(pdfViewerProvider: nil, intercomProvider: nil)
|
||||
}
|
||||
// Settings {
|
||||
// SettingsView()
|
||||
// }
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -52,18 +49,3 @@ struct MainApp: App {
|
||||
AnyView(PDFViewer(pdfURL: url, viewModel: viewModel))
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsView: View {
|
||||
var appVersion: String {
|
||||
Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("Omnivore")
|
||||
.font(.largeTitle)
|
||||
Text("Omnivore Version: \(appVersion)")
|
||||
}
|
||||
.frame(width: 600, height: 600)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user