From 8306ca1f9bbfced2b4ef4ff64154d5bc5742c3bd Mon Sep 17 00:00:00 2001 From: kingbri Date: Mon, 5 Sep 2022 14:58:02 -0400 Subject: [PATCH] Ferrite: Clean up UI changes - Migrate the empty view to a common view which vertically centers itself to the screen's bounds - Don't initialize underlying state variables in init as this is discouraged behavior. Instead, hook the source list editor to an ID that refreshes when an existing source list URL has been set Signed-off-by: kingbri --- Ferrite.xcodeproj/project.pbxproj | 4 ++ Ferrite/Extensions/View.swift | 6 +- Ferrite/Views/AboutView.swift | 2 +- Ferrite/Views/BatchChoiceView.swift | 1 + .../CommonViews/EmptyInstructionView.swift | 27 +++++++ Ferrite/Views/CommonViews/InlineHeader.swift | 19 +++-- Ferrite/Views/CommonViews/InlinedList.swift | 2 +- Ferrite/Views/SettingsView.swift | 10 +-- .../SettingsAppVersionView.swift | 1 + .../SettingsSourceListView.swift | 72 ++++++++++--------- .../SettingsViews/SourceListEditorView.swift | 8 +-- Ferrite/Views/SourcesView.swift | 27 +++---- 12 files changed, 102 insertions(+), 77 deletions(-) create mode 100644 Ferrite/Views/CommonViews/EmptyInstructionView.swift diff --git a/Ferrite.xcodeproj/project.pbxproj b/Ferrite.xcodeproj/project.pbxproj index cb07a28..414aeda 100644 --- a/Ferrite.xcodeproj/project.pbxproj +++ b/Ferrite.xcodeproj/project.pbxproj @@ -78,6 +78,7 @@ 0CBC76FD288D914F0054BE44 /* BatchChoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBC76FC288D914F0054BE44 /* BatchChoiceView.swift */; }; 0CBC76FF288DAAD00054BE44 /* NavigationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBC76FE288DAAD00054BE44 /* NavigationViewModel.swift */; }; 0CBC7705288DE7F40054BE44 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBC7704288DE7F40054BE44 /* PersistenceController.swift */; }; + 0CDCB91828C662640098B513 /* EmptyInstructionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CDCB91728C662640098B513 /* EmptyInstructionView.swift */; }; 0CFEFCFD288A006200B3F490 /* GroupBoxStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CFEFCFC288A006200B3F490 /* GroupBoxStyle.swift */; }; /* End PBXBuildFile section */ @@ -148,6 +149,7 @@ 0CBC76FE288DAAD00054BE44 /* NavigationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationViewModel.swift; sourceTree = ""; }; 0CBC7704288DE7F40054BE44 /* PersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceController.swift; sourceTree = ""; }; 0CC6E4D428A45BA000AF2BCC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 0CDCB91728C662640098B513 /* EmptyInstructionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyInstructionView.swift; sourceTree = ""; }; 0CFEFCFC288A006200B3F490 /* GroupBoxStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupBoxStyle.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -252,6 +254,7 @@ 0CB6516228C5A57300DCA721 /* ConditionalId.swift */, 0CB6516428C5A5D700DCA721 /* InlinedList.swift */, 0CB6516928C5B4A600DCA721 /* InlineHeader.swift */, + 0CDCB91728C662640098B513 /* EmptyInstructionView.swift */, ); path = CommonViews; sourceTree = ""; @@ -482,6 +485,7 @@ 0C794B67289DACB600DD1CC8 /* SourceUpdateButtonView.swift in Sources */, 0CA148E6288903F000DE2211 /* WebView.swift in Sources */, 0C4CFC4E28970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift in Sources */, + 0CDCB91828C662640098B513 /* EmptyInstructionView.swift in Sources */, 0CA148E2288903F000DE2211 /* Data.swift in Sources */, 0C57D4CC289032ED008534E8 /* SearchResultRDView.swift in Sources */, 0C7D11FC28AA01E900ED92DB /* DynamicAccentColor.swift in Sources */, diff --git a/Ferrite/Extensions/View.swift b/Ferrite/Extensions/View.swift index c0290fd..d1dc471 100644 --- a/Ferrite/Extensions/View.swift +++ b/Ferrite/Extensions/View.swift @@ -5,14 +5,14 @@ // Created by Brian Dashore on 8/15/22. // -import SwiftUI import Introspect +import SwiftUI extension View { // MARK: Custom introspect functions - func introspectCollectionView(customize: @escaping (UICollectionView) -> ()) -> some View { - return inject(UIKitIntrospectionView( + func introspectCollectionView(customize: @escaping (UICollectionView) -> Void) -> some View { + inject(UIKitIntrospectionView( selector: { introspectionView in guard let viewHost = Introspect.findViewHost(from: introspectionView) else { return nil diff --git a/Ferrite/Views/AboutView.swift b/Ferrite/Views/AboutView.swift index 6ee081a..2933955 100644 --- a/Ferrite/Views/AboutView.swift +++ b/Ferrite/Views/AboutView.swift @@ -21,7 +21,7 @@ struct AboutView: View { Image("AppImage") .resizable() .frame(width: 100, height: 100) - .clipShape(RoundedRectangle(cornerRadius: 100*0.225, style: .continuous)) + .clipShape(RoundedRectangle(cornerRadius: 100 * 0.225, style: .continuous)) .padding(.top, 24) Text("Ferrite is a free and open source application developed by kingbri under the GNU-GPLv3 license.") diff --git a/Ferrite/Views/BatchChoiceView.swift b/Ferrite/Views/BatchChoiceView.swift index 54b6237..692855c 100644 --- a/Ferrite/Views/BatchChoiceView.swift +++ b/Ferrite/Views/BatchChoiceView.swift @@ -38,6 +38,7 @@ struct BatchChoiceView: View { presentationMode.wrappedValue.dismiss() } + .dynamicAccentColor(.primary) } } .listStyle(.insetGrouped) diff --git a/Ferrite/Views/CommonViews/EmptyInstructionView.swift b/Ferrite/Views/CommonViews/EmptyInstructionView.swift new file mode 100644 index 0000000..8cd0055 --- /dev/null +++ b/Ferrite/Views/CommonViews/EmptyInstructionView.swift @@ -0,0 +1,27 @@ +// +// EmptyInstructionView.swift +// Ferrite +// +// Created by Brian Dashore on 9/5/22. +// + +import SwiftUI + +struct EmptyInstructionView: View { + let title: String + let message: String + + var body: some View { + VStack(spacing: 5) { + Text(title) + .font(.system(size: 25, weight: .semibold)) + + Text(message) + .padding(.horizontal, 50) + } + .multilineTextAlignment(.center) + .foregroundColor(.secondaryLabel) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .ignoresSafeArea() + } +} diff --git a/Ferrite/Views/CommonViews/InlineHeader.swift b/Ferrite/Views/CommonViews/InlineHeader.swift index dbb70c4..faff7b7 100644 --- a/Ferrite/Views/CommonViews/InlineHeader.swift +++ b/Ferrite/Views/CommonViews/InlineHeader.swift @@ -4,6 +4,8 @@ // // Created by Brian Dashore on 9/5/22. // +// For iOS 15's weird defaults regarding sectioned list padding +// import SwiftUI @@ -15,16 +17,13 @@ struct InlineHeader: View { } var body: some View { - Group { - if #available(iOS 16, *) { - Text(title) - .padding(.vertical, 5) - } else { - Text(title) - .padding(.vertical, 10) - } + if #available(iOS 16, *) { + Text(title) + } else if #available(iOS 15, *) { + Text(title) + .listRowInsets(EdgeInsets(top: 10, leading: 15, bottom: 0, trailing: 0)) + } else { + Text(title) } - .padding(.horizontal, 20) - .listRowInsets(EdgeInsets()) } } diff --git a/Ferrite/Views/CommonViews/InlinedList.swift b/Ferrite/Views/CommonViews/InlinedList.swift index 572693e..a0eb034 100644 --- a/Ferrite/Views/CommonViews/InlinedList.swift +++ b/Ferrite/Views/CommonViews/InlinedList.swift @@ -8,8 +8,8 @@ // Use UITableView.appearance().contentInset.top = -20 for iOS 15 and below in the App file // -import SwiftUI import Introspect +import SwiftUI struct InlinedList: ViewModifier { func body(content: Content) -> some View { diff --git a/Ferrite/Views/SettingsView.swift b/Ferrite/Views/SettingsView.swift index 9d9c1d9..1531a9a 100644 --- a/Ferrite/Views/SettingsView.swift +++ b/Ferrite/Views/SettingsView.swift @@ -5,8 +5,8 @@ // Created by Brian Dashore on 7/11/22. // -import SwiftUI import Introspect +import SwiftUI struct SettingsView: View { @EnvironmentObject var debridManager: DebridManager @@ -41,11 +41,11 @@ struct SettingsView: View { } } - Section(header: InlineHeader("Source management")) { + Section(header: Text("Source management")) { NavigationLink("Source lists", destination: SettingsSourceListView()) } - Section(header: InlineHeader("Default actions")) { + Section(header: Text("Default actions")) { if debridManager.realDebridEnabled { NavigationLink( destination: DebridActionPickerView(), @@ -95,14 +95,14 @@ struct SettingsView: View { ) } - Section(header: InlineHeader("Updates")) { + Section(header: Text("Updates")) { Toggle(isOn: $autoUpdateNotifs) { Text("Show update alerts") } NavigationLink("Version history", destination: SettingsAppVersionView()) } - Section(header: InlineHeader("Information")) { + Section(header: Text("Information")) { ListRowLinkView(text: "Donate", link: "https://ko-fi.com/kingbri") ListRowLinkView(text: "Report issues", link: "https://github.com/bdashore3/Ferrite/issues") NavigationLink("About", destination: AboutView()) diff --git a/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift b/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift index be1d19f..f15f15d 100644 --- a/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift +++ b/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift @@ -41,6 +41,7 @@ struct SettingsAppVersionView: View { } catch { toastModel.updateToastDescription("Github error: \(error)") } + withAnimation { loadedReleases = true } diff --git a/Ferrite/Views/SettingsViews/SettingsSourceListView.swift b/Ferrite/Views/SettingsViews/SettingsSourceListView.swift index 320ab00..cc1032e 100644 --- a/Ferrite/Views/SettingsViews/SettingsSourceListView.swift +++ b/Ferrite/Views/SettingsViews/SettingsSourceListView.swift @@ -21,58 +21,60 @@ struct SettingsSourceListView: View { @State private var selectedSourceList: SourceList? var body: some View { - List { + ZStack { if sourceLists.isEmpty { - Text("No source lists") + EmptyInstructionView(title: "No Lists", message: "Add a source list using the + button in the top-right") } else { - ForEach(sourceLists, id: \.self) { sourceList in - VStack(alignment: .leading, spacing: 5) { - Text(sourceList.name) + List { + ForEach(sourceLists, id: \.self) { sourceList in + VStack(alignment: .leading, spacing: 5) { + Text(sourceList.name) - Text(sourceList.author) - .foregroundColor(.gray) + Text(sourceList.author) + .foregroundColor(.gray) - Text("ID: \(sourceList.id)") - .font(.caption) - .foregroundColor(.gray) - } - .padding(.vertical, 2) - .contextMenu { - Button { - navModel.selectedSourceList = sourceList - presentSourceSheet.toggle() - } label: { - Text("Edit") - Image(systemName: "pencil") + Text("ID: \(sourceList.id)") + .font(.caption) + .foregroundColor(.gray) } - - if #available(iOS 15.0, *) { - Button(role: .destructive) { - PersistenceController.shared.delete(sourceList, context: backgroundContext) - } label: { - Text("Remove") - Image(systemName: "trash") - } - } else { + .padding(.vertical, 2) + .contextMenu { Button { - PersistenceController.shared.delete(sourceList, context: backgroundContext) + navModel.selectedSourceList = sourceList + presentSourceSheet.toggle() } label: { - Text("Remove") - Image(systemName: "trash") + Text("Edit") + Image(systemName: "pencil") + } + + if #available(iOS 15.0, *) { + Button(role: .destructive) { + PersistenceController.shared.delete(sourceList, context: backgroundContext) + } label: { + Text("Remove") + Image(systemName: "trash") + } + } else { + Button { + PersistenceController.shared.delete(sourceList, context: backgroundContext) + } label: { + Text("Remove") + Image(systemName: "trash") + } } } } } + .listStyle(.insetGrouped) + .inlinedList() } } - .listStyle(.insetGrouped) - .inlinedList() .sheet(isPresented: $presentSourceSheet) { if #available(iOS 16, *) { - SourceListEditorView(sourceUrl: navModel.selectedSourceList?.urlString ?? "") + SourceListEditorView() .presentationDetents([.medium]) } else { - SourceListEditorView(sourceUrl: navModel.selectedSourceList?.urlString ?? "") + SourceListEditorView() } } .navigationTitle("Source Lists") diff --git a/Ferrite/Views/SettingsViews/SourceListEditorView.swift b/Ferrite/Views/SettingsViews/SourceListEditorView.swift index 4b9a728..5ddfae3 100644 --- a/Ferrite/Views/SettingsViews/SourceListEditorView.swift +++ b/Ferrite/Views/SettingsViews/SourceListEditorView.swift @@ -15,11 +15,9 @@ struct SourceListEditorView: View { let backgroundContext = PersistenceController.shared.backgroundContext - @State private var sourceUrl: String + @State private var sourceUrlSet = false - init(sourceUrl: String = "") { - _sourceUrl = State(initialValue: sourceUrl) - } + @State private var sourceUrl: String = "" var body: some View { NavView { @@ -28,9 +26,11 @@ struct SourceListEditorView: View { .disableAutocorrection(true) .keyboardType(.URL) .autocapitalization(.none) + .conditionalId(sourceUrlSet) } .onAppear { sourceUrl = navModel.selectedSourceList?.urlString ?? "" + sourceUrlSet = true } .alert(isPresented: $sourceManager.showUrlErrorAlert) { Alert( diff --git a/Ferrite/Views/SourcesView.swift b/Ferrite/Views/SourcesView.swift index 6a99021..2d1ab4b 100644 --- a/Ferrite/Views/SourcesView.swift +++ b/Ferrite/Views/SourcesView.swift @@ -5,13 +5,11 @@ // Created by Brian Dashore on 7/24/22. // +import Introspect import SwiftUI import SwiftUIX -import Introspect struct SourcesView: View { - @Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass? - @EnvironmentObject var sourceManager: SourceManager @EnvironmentObject var navModel: NavigationViewModel @@ -53,15 +51,8 @@ struct SourcesView: View { ZStack { if !checkedForSources { ProgressView() - } else if sources.isEmpty && sourceManager.availableSources.isEmpty { - VStack { - Text("No Sources") - .font(.system(size: 25, weight: .semibold)) - .foregroundColor(.secondaryLabel) - Text("Add a source list in Settings") - .foregroundColor(.secondaryLabel) - } - .padding(.top, verticalSizeClass == .regular ? -50 : 0) + } else if sources.isEmpty, sourceManager.availableSources.isEmpty { + EmptyInstructionView(title: "No Sources", message: "Add a source list in Settings") } else { List { if !filteredUpdatedSources.isEmpty { @@ -80,12 +71,12 @@ struct SourcesView: View { } } - if !filteredAvailableSources.isEmpty && sourceManager.availableSources.contains(where: { availableSource in + if !filteredAvailableSources.isEmpty, sourceManager.availableSources.contains(where: { availableSource in !sources.contains( where: { availableSource.name == $0.name && - availableSource.listId == $0.listId && - availableSource.author == $0.author + availableSource.listId == $0.listId && + availableSource.author == $0.author } ) }) { @@ -94,8 +85,8 @@ struct SourcesView: View { if !sources.contains( where: { availableSource.name == $0.name && - availableSource.listId == $0.listId && - availableSource.author == $0.author + availableSource.listId == $0.listId && + availableSource.author == $0.author } ) { SourceCatalogButtonView(availableSource: availableSource) @@ -131,7 +122,7 @@ struct SourcesView: View { searchText = "" } } - .onChange(of: searchText) { newValue in + .onChange(of: searchText) { _ in filteredAvailableSources = sourceManager.availableSources.filter { searchText.isEmpty ? true : $0.name.contains(searchText) } filteredUpdatedSources = updatedSources.filter { searchText.isEmpty ? true : $0.name.contains(searchText) } if #available(iOS 15.0, *) {