diff --git a/Multiplatform/Shared/Sidebar/CompactSidebarContainerView.swift b/Multiplatform/Shared/Sidebar/CompactSidebarContainerView.swift index ef4d32184..be76f6a48 100644 --- a/Multiplatform/Shared/Sidebar/CompactSidebarContainerView.swift +++ b/Multiplatform/Shared/Sidebar/CompactSidebarContainerView.swift @@ -12,6 +12,7 @@ struct CompactSidebarContainerView: View { @EnvironmentObject private var sceneModel: SceneModel @StateObject private var sidebarModel = SidebarModel() + @State private var showSettings: Bool = false var body: some View { SidebarView() @@ -22,8 +23,42 @@ struct CompactSidebarContainerView: View { sceneModel.sidebarModel = sidebarModel sidebarModel.delegate = sceneModel sidebarModel.rebuildSidebarItems() + }.overlay(Group { + #if os(iOS) + SidebarToolbar() + #endif + },alignment: .bottom) + } + + + var compactToolBar: some View { + VStack { + Divider() + HStack(alignment: .center) { + Button(action: { + showSettings = true + }, label: { + Image(systemName: "gear") + .font(.title3) + .foregroundColor(.accentColor) + }).help("Settings") + Spacer() + Text("Last updated") + .font(.caption) + .foregroundColor(.secondary) + Spacer() + Button(action: {}, label: { + Image(systemName: "plus") + .font(.title3) + .foregroundColor(.accentColor) + }).help("Add") } - + .padding(.horizontal, 16) + .padding(.bottom, 12) + .padding(.top, 4) + } + .background(VisualEffectBlur(blurStyle: .systemChromeMaterial).edgesIgnoringSafeArea(.bottom)) + } diff --git a/Multiplatform/Shared/Sidebar/RegularSidebarContainerView.swift b/Multiplatform/Shared/Sidebar/RegularSidebarContainerView.swift index 91d41e11b..af95dff28 100644 --- a/Multiplatform/Shared/Sidebar/RegularSidebarContainerView.swift +++ b/Multiplatform/Shared/Sidebar/RegularSidebarContainerView.swift @@ -13,7 +13,9 @@ struct RegularSidebarContainerView: View { @EnvironmentObject private var sceneModel: SceneModel @StateObject private var sidebarModel = SidebarModel() - var body: some View { + @State private var showSettings: Bool = false + + @ViewBuilder var body: some View { SidebarView() .environmentObject(sidebarModel) .navigationTitle(Text("Feeds")) @@ -23,7 +25,14 @@ struct RegularSidebarContainerView: View { sidebarModel.delegate = sceneModel sidebarModel.rebuildSidebarItems() } + .overlay(Group { + #if os(iOS) + SidebarToolbar() + #endif + },alignment: .bottom) + } + } struct RegularSidebarContainerView_Previews: PreviewProvider { diff --git a/Multiplatform/Shared/Sidebar/SidebarToolbar.swift b/Multiplatform/Shared/Sidebar/SidebarToolbar.swift new file mode 100644 index 000000000..e7df534f6 --- /dev/null +++ b/Multiplatform/Shared/Sidebar/SidebarToolbar.swift @@ -0,0 +1,53 @@ +// +// SidebarToolbar.swift +// Multiplatform iOS +// +// Created by Stuart Breckenridge on 30/6/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import SwiftUI + +struct SidebarToolbar: View { + + @State private var showSettings: Bool = false + + var body: some View { + VStack { + Divider() + HStack(alignment: .center) { + Button(action: { + showSettings = true + }, label: { + Image(systemName: "gear") + .font(.title3) + .foregroundColor(.accentColor) + }).help("Settings") + Spacer() + Text("Last updated") + .font(.caption) + .foregroundColor(.secondary) + Spacer() + Button(action: {}, label: { + Image(systemName: "plus") + .font(.title3) + .foregroundColor(.accentColor) + }).help("Add") + } + .padding(.horizontal, 16) + .padding(.bottom, 12) + .padding(.top, 4) + } + .background(VisualEffectBlur(blurStyle: .systemChromeMaterial).edgesIgnoringSafeArea(.bottom)) + .sheet(isPresented: $showSettings, onDismiss: { showSettings = false }) { + SettingsView() + } + + } +} + +struct SidebarToolbar_Previews: PreviewProvider { + static var previews: some View { + SidebarToolbar() + } +} diff --git a/Multiplatform/iOS/SafariView.swift b/Multiplatform/iOS/SafariView.swift new file mode 100644 index 000000000..2bc9af145 --- /dev/null +++ b/Multiplatform/iOS/SafariView.swift @@ -0,0 +1,65 @@ +// +// SafariView.swift +// Multiplatform iOS +// +// Created by Stuart Breckenridge on 30/6/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import SwiftUI +import SafariServices + + +private final class Safari: UIViewControllerRepresentable { + + typealias UIViewControllerType = SFSafariViewController + + var urlToLoad: URL + + init(url: URL) { + self.urlToLoad = url + } + + func makeUIViewController(context: Context) -> SFSafariViewController { + let viewController = SFSafariViewController(url: urlToLoad) + viewController.delegate = context.coordinator + return viewController + } + + func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) { + + } + + func makeCoordinator() -> Coordinator { + Coordinator(self) + } + + class Coordinator: NSObject, SFSafariViewControllerDelegate { + var parent: Safari + + init(_ parent: Safari) { + self.parent = parent + } + + func safariViewControllerDidFinish(_ controller: SFSafariViewController) { + + } + + } + +} + +struct SafariView: View { + + var url: URL + + var body: some View { + Safari(url: url) + } +} + +struct SafariView_Previews: PreviewProvider { + static var previews: some View { + SafariView(url: URL(string: "https://ranchero.com/netnewswire/")!) + } +} diff --git a/Multiplatform/iOS/Settings/SettingsView.swift b/Multiplatform/iOS/Settings/SettingsView.swift new file mode 100644 index 000000000..ca65c6daf --- /dev/null +++ b/Multiplatform/iOS/Settings/SettingsView.swift @@ -0,0 +1,204 @@ +// +// SettingsView.swift +// Multiplatform iOS +// +// Created by Stuart Breckenridge on 30/6/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import SwiftUI +import Account + + + +class SettingsViewModel: ObservableObject { + + enum HelpSites { + case netNewsWireHelp, netNewsWire, supportNetNewsWire, github, bugTracker, technotes, netNewsWireSlack, none + + var url: URL? { + switch self { + case .netNewsWireHelp: + return URL(string: "https://ranchero.com/netnewswire/help/ios/5.0/en/")! + case .netNewsWire: + return URL(string: "https://ranchero.com/netnewswire/")! + case .supportNetNewsWire: + return URL(string: "https://github.com/brentsimmons/NetNewsWire/blob/master/Technotes/HowToSupportNetNewsWire.markdown")! + case .github: + return URL(string: "https://github.com/brentsimmons/NetNewsWire")! + case .bugTracker: + return URL(string: "https://github.com/brentsimmons/NetNewsWire/issues")! + case .technotes: + return URL(string: "https://github.com/brentsimmons/NetNewsWire/tree/master/Technotes")! + case .netNewsWireSlack: + return URL(string: "https://ranchero.com/netnewswire/slack")! + case .none: + return nil + } + } + } + + @Published var presentSheet: Bool = false + var selectedWebsite: HelpSites = .none { + didSet { + if selectedWebsite == .none { + presentSheet = false + } else { + presentSheet = true + } + } + } + +} + +struct SettingsView: View { + + let sortedAccounts = AccountManager.shared.sortedAccounts + @Environment(\.presentationMode) var presentationMode + + @ObservedObject private var viewModel = SettingsViewModel() + + var body: some View { + NavigationView { + List { + systemSettings + accounts + importExport + timeline + articles + appearance + help + } + .listStyle(InsetGroupedListStyle()) + .navigationBarTitle("Settings", displayMode: .inline) + .navigationBarItems(leading: + HStack { + Button("Done") { + presentationMode.wrappedValue.dismiss() + } + } + ) + } + .sheet(isPresented: $viewModel.presentSheet, content: { + SafariView(url: viewModel.selectedWebsite.url!) + }) + } + + var systemSettings: some View { + Section(header: Text("Notifications, Badge, Data, & More"), content: { + Button(action: { + UIApplication.shared.open(URL(string: "\(UIApplication.openSettingsURLString)")!) + }, label: { + Text("Open System Settings").foregroundColor(.primary) + }) + }) + } + + var accounts: some View { + Section(header: Text("Accounts"), content: { + ForEach(0..: View { + var blurStyle: UIBlurEffect.Style + var vibrancyStyle: UIVibrancyEffectStyle? + var content: Content + + init(blurStyle: UIBlurEffect.Style = .systemMaterial, vibrancyStyle: UIVibrancyEffectStyle? = nil, @ViewBuilder content: () -> Content) { + self.blurStyle = blurStyle + self.vibrancyStyle = vibrancyStyle + self.content = content() + } + + var body: some View { + Representable(blurStyle: blurStyle, vibrancyStyle: vibrancyStyle, content: ZStack { content }) + .accessibility(hidden: Content.self == EmptyView.self) + } +} + +// MARK: - Representable + +extension VisualEffectBlur { + struct Representable: UIViewRepresentable { + var blurStyle: UIBlurEffect.Style + var vibrancyStyle: UIVibrancyEffectStyle? + var content: Content + + func makeUIView(context: Context) -> UIVisualEffectView { + context.coordinator.blurView + } + + func updateUIView(_ view: UIVisualEffectView, context: Context) { + context.coordinator.update(content: content, blurStyle: blurStyle, vibrancyStyle: vibrancyStyle) + } + + func makeCoordinator() -> Coordinator { + Coordinator(content: content) + } + } +} + +// MARK: - Coordinator + +extension VisualEffectBlur.Representable { + class Coordinator { + let blurView = UIVisualEffectView() + let vibrancyView = UIVisualEffectView() + let hostingController: UIHostingController + + init(content: Content) { + hostingController = UIHostingController(rootView: content) + hostingController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] + hostingController.view.backgroundColor = nil + blurView.contentView.addSubview(vibrancyView) + blurView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + vibrancyView.contentView.addSubview(hostingController.view) + vibrancyView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + } + + func update(content: Content, blurStyle: UIBlurEffect.Style, vibrancyStyle: UIVibrancyEffectStyle?) { + hostingController.rootView = content + let blurEffect = UIBlurEffect(style: blurStyle) + blurView.effect = blurEffect + if let vibrancyStyle = vibrancyStyle { + vibrancyView.effect = UIVibrancyEffect(blurEffect: blurEffect, style: vibrancyStyle) + } else { + vibrancyView.effect = nil + } + hostingController.view.setNeedsDisplay() + } + } +} + +// MARK: - Content-less Initializer + +extension VisualEffectBlur where Content == EmptyView { + init(blurStyle: UIBlurEffect.Style = .systemMaterial) { + self.init( blurStyle: blurStyle, vibrancyStyle: nil) { + EmptyView() + } + } +} + +// MARK: - Previews + +struct VisualEffectBlur_Previews: PreviewProvider { + static var previews: some View { + ZStack { + LinearGradient( + gradient: Gradient(colors: [.red, .blue]), + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + + VisualEffectBlur(blurStyle: .systemUltraThinMaterial, vibrancyStyle: .fill) { + Text("Hello World!") + .frame(width: 200, height: 100) + } + } + .previewLayout(.sizeThatFits) + } +} + diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index dcc2ebd26..8d476b95b 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 172199C924AB228900A31D04 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172199C824AB228900A31D04 /* SettingsView.swift */; }; + 172199ED24AB2E0100A31D04 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172199EC24AB2E0100A31D04 /* SafariView.swift */; }; + 172199EF24AB372D00A31D04 /* VisualEffectBlur.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172199EE24AB372D00A31D04 /* VisualEffectBlur.swift */; }; + 172199F124AB716900A31D04 /* SidebarToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172199F024AB716900A31D04 /* SidebarToolbar.swift */; }; 1729528E24AA1A4900D65E66 /* MacPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1729526C24AA1A4900D65E66 /* MacPreferences.swift */; }; 1729529324AA1CAA00D65E66 /* AccountsPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1729529024AA1CAA00D65E66 /* AccountsPreferencesView.swift */; }; 1729529424AA1CAA00D65E66 /* AdvancedPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1729529124AA1CAA00D65E66 /* AdvancedPreferencesView.swift */; }; @@ -1676,6 +1680,10 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 172199C824AB228900A31D04 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + 172199EC24AB2E0100A31D04 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = ""; }; + 172199EE24AB372D00A31D04 /* VisualEffectBlur.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VisualEffectBlur.swift; sourceTree = ""; }; + 172199F024AB716900A31D04 /* SidebarToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarToolbar.swift; sourceTree = ""; }; 1729526C24AA1A4900D65E66 /* MacPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacPreferences.swift; sourceTree = ""; }; 1729529024AA1CAA00D65E66 /* AccountsPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsPreferencesView.swift; sourceTree = ""; }; 1729529124AA1CAA00D65E66 /* AdvancedPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdvancedPreferencesView.swift; sourceTree = ""; }; @@ -2319,6 +2327,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 172199EB24AB228E00A31D04 /* Settings */ = { + isa = PBXGroup; + children = ( + 172199C824AB228900A31D04 /* SettingsView.swift */, + ); + path = Settings; + sourceTree = ""; + }; 1729528F24AA1A4F00D65E66 /* Preferences */ = { isa = PBXGroup; children = ( @@ -2623,6 +2639,9 @@ 51C051CE24A7A72100194D5E /* iOS.entitlements */, 51C051CF24A7A72100194D5E /* iOS-dev.entitlements */, 51E4993B24A8709900B667CB /* AppDelegate.swift */, + 172199EB24AB228E00A31D04 /* Settings */, + 172199EC24AB2E0100A31D04 /* SafariView.swift */, + 172199EE24AB372D00A31D04 /* VisualEffectBlur.swift */, ); path = iOS; sourceTree = ""; @@ -2829,6 +2848,7 @@ children = ( 172952AF24AA287100D65E66 /* CompactSidebarContainerView.swift */, 51E499FF24A91FC100B667CB /* RegularSidebarContainerView.swift */, + 172199F024AB716900A31D04 /* SidebarToolbar.swift */, 51408B7D24A9EC6F0073CF4E /* SidebarItem.swift */, 51E499FC24A9137600B667CB /* SidebarModel.swift */, 51919FA524AA64B000541E64 /* SidebarView.swift */, @@ -3841,46 +3861,46 @@ TargetAttributes = { 51314636235A7BBE00387FDC = { CreatedOnToolsVersion = 11.2; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; LastSwiftMigration = 1120; ProvisioningStyle = Automatic; }; 513C5CE5232571C2003D4054 = { CreatedOnToolsVersion = 11.0; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; }; 518B2ED12351B3DD00400001 = { CreatedOnToolsVersion = 11.2; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; TestTargetID = 840D617B2029031C009BC708; }; 51C0513C24A77DF800194D5E = { CreatedOnToolsVersion = 12.0; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; }; 51C0514324A77DF800194D5E = { CreatedOnToolsVersion = 12.0; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; }; 6581C73220CED60000F4AD34 = { - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; }; 65ED3FA2235DEF6C0081F399 = { - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; }; 65ED4090235DEF770081F399 = { - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; }; 840D617B2029031C009BC708 = { CreatedOnToolsVersion = 9.3; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.BackgroundModes = { @@ -3890,7 +3910,7 @@ }; 849C645F1ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.HardenedRuntime = { @@ -3900,7 +3920,7 @@ }; 849C64701ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; TestTargetID = 849C645F1ED37A5D003D8FC0; }; @@ -4681,8 +4701,10 @@ 51E4992324A8095700B667CB /* URL-Extensions.swift in Sources */, 51E4993624A867E800B667CB /* UserInfoKey.swift in Sources */, 51E4990924A808C500B667CB /* WebFeedIconDownloader.swift in Sources */, + 172199EF24AB372D00A31D04 /* VisualEffectBlur.swift in Sources */, 51E498F524A8085D00B667CB /* TodayFeedDelegate.swift in Sources */, 172952B024AA287100D65E66 /* CompactSidebarContainerView.swift in Sources */, + 172199F124AB716900A31D04 /* SidebarToolbar.swift in Sources */, 51E4990B24A808C500B667CB /* ImageDownloader.swift in Sources */, 51E498F424A8085D00B667CB /* SmartFeedDelegate.swift in Sources */, 51E4993024A8676400B667CB /* ArticleSorter.swift in Sources */, @@ -4701,6 +4723,7 @@ 51E4991D24A8092100B667CB /* NSAttributedString+NetNewsWire.swift in Sources */, 51E499FD24A9137600B667CB /* SidebarModel.swift in Sources */, 51E4995324A8734D00B667CB /* RedditFeedProvider-Extensions.swift in Sources */, + 172199C924AB228900A31D04 /* SettingsView.swift in Sources */, 51E4994224A8713C00B667CB /* ArticleStatusSyncTimer.swift in Sources */, 51E498F624A8085D00B667CB /* SearchFeedDelegate.swift in Sources */, 51E498F224A8085D00B667CB /* SmartFeedsController.swift in Sources */, @@ -4725,6 +4748,7 @@ 51E4990C24A808C500B667CB /* AuthorAvatarDownloader.swift in Sources */, 51E4992124A8095000B667CB /* RSImage-Extensions.swift in Sources */, 51E4990324A808BB00B667CB /* FaviconDownloader.swift in Sources */, + 172199ED24AB2E0100A31D04 /* SafariView.swift in Sources */, 51E4990224A808BB00B667CB /* ColorHash.swift in Sources */, 51919FAC24AA8CCA00541E64 /* UnreadCountView.swift in Sources */, 51E4991924A8090A00B667CB /* CacheCleaner.swift in Sources */,