diff --git a/Ferrite.xcodeproj/project.pbxproj b/Ferrite.xcodeproj/project.pbxproj index 47c3300..cb07a28 100644 --- a/Ferrite.xcodeproj/project.pbxproj +++ b/Ferrite.xcodeproj/project.pbxproj @@ -71,6 +71,10 @@ 0CA148EC288903F000DE2211 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA148D4288903F000DE2211 /* ContentView.swift */; }; 0CA3FB2028B91D9500FA10A8 /* IndeterminateProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA3FB1F28B91D9500FA10A8 /* IndeterminateProgressView.swift */; }; 0CAF1C7B286F5C8600296F86 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = 0CAF1C7A286F5C8600296F86 /* SwiftSoup */; }; + 0CB6516328C5A57300DCA721 /* ConditionalId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB6516228C5A57300DCA721 /* ConditionalId.swift */; }; + 0CB6516528C5A5D700DCA721 /* InlinedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB6516428C5A5D700DCA721 /* InlinedList.swift */; }; + 0CB6516828C5A5EC00DCA721 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 0CB6516728C5A5EC00DCA721 /* Introspect */; }; + 0CB6516A28C5B4A600DCA721 /* InlineHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB6516928C5B4A600DCA721 /* InlineHeader.swift */; }; 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 */; }; @@ -137,6 +141,9 @@ 0CA148D4288903F000DE2211 /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 0CA3FB1F28B91D9500FA10A8 /* IndeterminateProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndeterminateProgressView.swift; sourceTree = ""; }; 0CAF1C68286F5C0E00296F86 /* Ferrite.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ferrite.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 0CB6516228C5A57300DCA721 /* ConditionalId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalId.swift; sourceTree = ""; }; + 0CB6516428C5A5D700DCA721 /* InlinedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlinedList.swift; sourceTree = ""; }; + 0CB6516928C5B4A600DCA721 /* InlineHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineHeader.swift; sourceTree = ""; }; 0CBC76FC288D914F0054BE44 /* BatchChoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchChoiceView.swift; sourceTree = ""; }; 0CBC76FE288DAAD00054BE44 /* NavigationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationViewModel.swift; sourceTree = ""; }; 0CBC7704288DE7F40054BE44 /* PersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceController.swift; sourceTree = ""; }; @@ -153,6 +160,7 @@ 0C64A4B4288903680079976D /* Base32 in Frameworks */, 0C4CFC462897030D00AD9FAD /* Regex in Frameworks */, 0C7376F028A97D1400D60918 /* SwiftUIX in Frameworks */, + 0CB6516828C5A5EC00DCA721 /* Introspect in Frameworks */, 0C64A4B7288903880079976D /* KeychainSwift in Frameworks */, 0CAF1C7B286F5C8600296F86 /* SwiftSoup in Frameworks */, ); @@ -241,6 +249,9 @@ 0C32FB562890D1F2002BD219 /* ListRowViews.swift */, 0C7D11FB28AA01E900ED92DB /* DynamicAccentColor.swift */, 0CA3FB1F28B91D9500FA10A8 /* IndeterminateProgressView.swift */, + 0CB6516228C5A57300DCA721 /* ConditionalId.swift */, + 0CB6516428C5A5D700DCA721 /* InlinedList.swift */, + 0CB6516928C5B4A600DCA721 /* InlineHeader.swift */, ); path = CommonViews; sourceTree = ""; @@ -366,6 +377,7 @@ 0C4CFC452897030D00AD9FAD /* Regex */, 0C7376EF28A97D1400D60918 /* SwiftUIX */, 0C7506D628B1AC9A008BEE38 /* SwiftyJSON */, + 0CB6516728C5A5EC00DCA721 /* Introspect */, ); productName = Torrenter; productReference = 0CAF1C68286F5C0E00296F86 /* Ferrite.app */; @@ -402,6 +414,7 @@ 0C4CFC442897030D00AD9FAD /* XCRemoteSwiftPackageReference "Regex" */, 0C7376EE28A97D1400D60918 /* XCRemoteSwiftPackageReference "SwiftUIX" */, 0C7506D528B1AC9A008BEE38 /* XCRemoteSwiftPackageReference "SwiftyJSON" */, + 0CB6516628C5A5EC00DCA721 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */, ); productRefGroup = 0CAF1C69286F5C0E00296F86 /* Products */; projectDirPath = ""; @@ -431,8 +444,10 @@ buildActionMask = 2147483647; files = ( 0C0D50E5288DFE7F0035ECC8 /* SourceModels.swift in Sources */, + 0CB6516528C5A5D700DCA721 /* InlinedList.swift in Sources */, 0C60B1EF28A1A00000E3FD7E /* SearchProgressView.swift in Sources */, 0C32FB532890D19D002BD219 /* AboutView.swift in Sources */, + 0CB6516328C5A57300DCA721 /* ConditionalId.swift in Sources */, 0C84F4832895BFED0074B7C9 /* Source+CoreDataProperties.swift in Sources */, 0CA148DB288903F000DE2211 /* NavView.swift in Sources */, 0C750745289B003E004B3906 /* SourceRssParser+CoreDataProperties.swift in Sources */, @@ -454,6 +469,7 @@ 0C794B69289DACC800DD1CC8 /* InstalledSourceView.swift in Sources */, 0C79DC082899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift in Sources */, 0CA148DD288903F000DE2211 /* ScrapingViewModel.swift in Sources */, + 0CB6516A28C5B4A600DCA721 /* InlineHeader.swift in Sources */, 0CA148D8288903F000DE2211 /* MagnetChoiceView.swift in Sources */, 0C84F4862895BFED0074B7C9 /* SourceList+CoreDataClass.swift in Sources */, 0C68135028BC1A2D00FAD890 /* GithubWrapper.swift in Sources */, @@ -747,6 +763,14 @@ minimumVersion = 2.0.0; }; }; + 0CB6516628C5A5EC00DCA721 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/siteline/SwiftUI-Introspect"; + requirement = { + branch = master; + kind = branch; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -780,6 +804,11 @@ package = 0CAF1C79286F5C8600296F86 /* XCRemoteSwiftPackageReference "SwiftSoup" */; productName = SwiftSoup; }; + 0CB6516728C5A5EC00DCA721 /* Introspect */ = { + isa = XCSwiftPackageProductDependency; + package = 0CB6516628C5A5EC00DCA721 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; + productName = Introspect; + }; /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ diff --git a/Ferrite/Extensions/View.swift b/Ferrite/Extensions/View.swift index 7b7e4cc..c0290fd 100644 --- a/Ferrite/Extensions/View.swift +++ b/Ferrite/Extensions/View.swift @@ -6,11 +6,34 @@ // import SwiftUI +import Introspect extension View { + // MARK: Custom introspect functions + + func introspectCollectionView(customize: @escaping (UICollectionView) -> ()) -> some View { + return inject(UIKitIntrospectionView( + selector: { introspectionView in + guard let viewHost = Introspect.findViewHost(from: introspectionView) else { + return nil + } + return Introspect.previousSibling(containing: UICollectionView.self, from: viewHost) + }, + customize: customize + )) + } + // MARK: Modifiers func dynamicAccentColor(_ color: Color) -> some View { modifier(DynamicAccentColor(color: color)) } + + func conditionalId(_ id: ID) -> some View { + modifier(ConditionalId(id: id)) + } + + func inlinedList() -> some View { + modifier(InlinedList()) + } } diff --git a/Ferrite/Views/CommonViews/ConditionalId.swift b/Ferrite/Views/CommonViews/ConditionalId.swift new file mode 100644 index 0000000..53a033d --- /dev/null +++ b/Ferrite/Views/CommonViews/ConditionalId.swift @@ -0,0 +1,24 @@ +// +// ConditionalId.swift +// Ferrite +// +// Created by Brian Dashore on 9/4/22. +// +// Only applies an ID for below iOS 16 +// This is due to ID workarounds making iOS 16 apps crash +// + +import SwiftUI + +struct ConditionalId: ViewModifier { + let id: ID + + func body(content: Content) -> some View { + if #available(iOS 16, *) { + content + } else { + content + .id(id) + } + } +} diff --git a/Ferrite/Views/CommonViews/InlineHeader.swift b/Ferrite/Views/CommonViews/InlineHeader.swift new file mode 100644 index 0000000..dbb70c4 --- /dev/null +++ b/Ferrite/Views/CommonViews/InlineHeader.swift @@ -0,0 +1,30 @@ +// +// InlineHeader.swift +// Ferrite +// +// Created by Brian Dashore on 9/5/22. +// + +import SwiftUI + +struct InlineHeader: View { + let title: String + + init(_ title: String) { + self.title = title + } + + var body: some View { + Group { + if #available(iOS 16, *) { + Text(title) + .padding(.vertical, 5) + } else { + Text(title) + .padding(.vertical, 10) + } + } + .padding(.horizontal, 20) + .listRowInsets(EdgeInsets()) + } +} diff --git a/Ferrite/Views/CommonViews/InlinedList.swift b/Ferrite/Views/CommonViews/InlinedList.swift new file mode 100644 index 0000000..572693e --- /dev/null +++ b/Ferrite/Views/CommonViews/InlinedList.swift @@ -0,0 +1,28 @@ +// +// InlinedList.swift +// Ferrite +// +// Created by Brian Dashore on 9/4/22. +// +// Removes the top padding on lists for iOS 16 +// Use UITableView.appearance().contentInset.top = -20 for iOS 15 and below in the App file +// + +import SwiftUI +import Introspect + +struct InlinedList: ViewModifier { + func body(content: Content) -> some View { + if #available(iOS 16, *) { + content + .introspectCollectionView { collectionView in + collectionView.contentInset.top = -20 + } + } else { + content + .introspectTableView { tableView in + tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 20)) + } + } + } +} diff --git a/Ferrite/Views/SearchResultsView.swift b/Ferrite/Views/SearchResultsView.swift index 4ca3a17..9cf050d 100644 --- a/Ferrite/Views/SearchResultsView.swift +++ b/Ferrite/Views/SearchResultsView.swift @@ -54,6 +54,7 @@ struct SearchResultsView: View { } } .listStyle(.insetGrouped) + .inlinedList() .overlay { if scrapingModel.searchResults.isEmpty { if navModel.showSearchProgress { diff --git a/Ferrite/Views/SettingsView.swift b/Ferrite/Views/SettingsView.swift index 72bfa53..9d9c1d9 100644 --- a/Ferrite/Views/SettingsView.swift +++ b/Ferrite/Views/SettingsView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import Introspect struct SettingsView: View { @EnvironmentObject var debridManager: DebridManager @@ -21,7 +22,7 @@ struct SettingsView: View { var body: some View { NavView { Form { - Section(header: "Debrid services") { + Section(header: InlineHeader("Debrid Services")) { HStack { Text("Real Debrid") Spacer() @@ -40,11 +41,11 @@ struct SettingsView: View { } } - Section(header: "Source management") { + Section(header: InlineHeader("Source management")) { NavigationLink("Source lists", destination: SettingsSourceListView()) } - Section(header: "Default actions") { + Section(header: InlineHeader("Default actions")) { if debridManager.realDebridEnabled { NavigationLink( destination: DebridActionPickerView(), @@ -94,14 +95,14 @@ struct SettingsView: View { ) } - Section(header: Text("Updates")) { + Section(header: InlineHeader("Updates")) { Toggle(isOn: $autoUpdateNotifs) { Text("Show update alerts") } NavigationLink("Version history", destination: SettingsAppVersionView()) } - Section(header: Text("Information")) { + Section(header: InlineHeader("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/DefaultActionsPickerViews.swift b/Ferrite/Views/SettingsViews/DefaultActionsPickerViews.swift index 8eb1c8d..a03be82 100644 --- a/Ferrite/Views/SettingsViews/DefaultActionsPickerViews.swift +++ b/Ferrite/Views/SettingsViews/DefaultActionsPickerViews.swift @@ -29,6 +29,7 @@ struct MagnetActionPickerView: View { } } .listStyle(.insetGrouped) + .inlinedList() .navigationTitle("Default magnet action") .navigationBarTitleDisplayMode(.inline) } @@ -67,6 +68,7 @@ struct DebridActionPickerView: View { } } .listStyle(.insetGrouped) + .inlinedList() .navigationTitle("Default debrid action") .navigationBarTitleDisplayMode(.inline) } diff --git a/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift b/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift index 3553c64..be1d19f 100644 --- a/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift +++ b/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift @@ -21,7 +21,7 @@ struct SettingsAppVersionView: View { ProgressView() } else if !releases.isEmpty { List { - Section(header: Text("GitHub links")) { + Section(header: InlineHeader("GitHub links")) { ForEach(releases, id: \.self) { release in ListRowLinkView(text: release.tagName, link: release.htmlUrl) } diff --git a/Ferrite/Views/SettingsViews/SettingsSourceListView.swift b/Ferrite/Views/SettingsViews/SettingsSourceListView.swift index c3649df..320ab00 100644 --- a/Ferrite/Views/SettingsViews/SettingsSourceListView.swift +++ b/Ferrite/Views/SettingsViews/SettingsSourceListView.swift @@ -66,6 +66,7 @@ struct SettingsSourceListView: View { } } .listStyle(.insetGrouped) + .inlinedList() .sheet(isPresented: $presentSourceSheet) { if #available(iOS 16, *) { SourceListEditorView(sourceUrl: navModel.selectedSourceList?.urlString ?? "") diff --git a/Ferrite/Views/SettingsViews/SourceListEditorView.swift b/Ferrite/Views/SettingsViews/SourceListEditorView.swift index d82d367..4b9a728 100644 --- a/Ferrite/Views/SettingsViews/SourceListEditorView.swift +++ b/Ferrite/Views/SettingsViews/SourceListEditorView.swift @@ -24,12 +24,10 @@ struct SourceListEditorView: View { var body: some View { NavView { Form { - Section { - TextField("Enter URL", text: $sourceUrl) - .disableAutocorrection(true) - .keyboardType(.URL) - .autocapitalization(.none) - } + TextField("Enter URL", text: $sourceUrl) + .disableAutocorrection(true) + .keyboardType(.URL) + .autocapitalization(.none) } .onAppear { sourceUrl = navModel.selectedSourceList?.urlString ?? "" diff --git a/Ferrite/Views/SourceViews/SourceSettingsView.swift b/Ferrite/Views/SourceViews/SourceSettingsView.swift index f195a16..6a0daa8 100644 --- a/Ferrite/Views/SourceViews/SourceSettingsView.swift +++ b/Ferrite/Views/SourceViews/SourceSettingsView.swift @@ -16,7 +16,7 @@ struct SourceSettingsView: View { NavView { List { if let selectedSource = navModel.selectedSource { - Section(header: "Info") { + Section(header: InlineHeader("Info")) { VStack(alignment: .leading, spacing: 5) { HStack { Text(selectedSource.name) @@ -78,7 +78,7 @@ struct SourceSettingsBaseUrlView: View { @State private var tempBaseUrl: String = "" var body: some View { Section( - header: Text("Base URL"), + header: InlineHeader("Base URL"), footer: Text("Enter the base URL of your server.") ) { TextField("https://...", text: $tempBaseUrl, onEditingChanged: { isFocused in @@ -110,7 +110,7 @@ struct SourceSettingsApiView: View { var body: some View { Section( - header: Text("API credentials"), + header: InlineHeader("API credentials"), footer: Text("Grab the required API credentials from the website. A client secret can be an API token.") ) { if let clientId = selectedSourceApi.clientId, clientId.dynamic { @@ -146,7 +146,7 @@ struct SourceSettingsMethodView: View { @ObservedObject var selectedSource: Source var body: some View { - Section(header: Text("Fetch method")) { + Section(header: InlineHeader("Fetch method")) { if selectedSource.api != nil, selectedSource.jsonParser != nil { Button { selectedSource.preferredParser = SourcePreferredParser.siteApi.rawValue diff --git a/Ferrite/Views/SourcesView.swift b/Ferrite/Views/SourcesView.swift index 7e11757..faf7388 100644 --- a/Ferrite/Views/SourcesView.swift +++ b/Ferrite/Views/SourcesView.swift @@ -7,6 +7,7 @@ import SwiftUI import SwiftUIX +import Introspect struct SourcesView: View { @Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass? @@ -65,7 +66,7 @@ struct SourcesView: View { } else { List { if !filteredUpdatedSources.isEmpty { - Section(header: "Updates") { + Section(header: InlineHeader("Updates")) { ForEach(filteredUpdatedSources, id: \.self) { source in SourceUpdateButtonView(updatedSource: source) } @@ -73,7 +74,7 @@ struct SourcesView: View { } if !sources.isEmpty { - Section(header: "Installed") { + Section(header: InlineHeader("Installed")) { ForEach(sources, id: \.self) { source in InstalledSourceView(installedSource: source) } @@ -89,7 +90,7 @@ struct SourcesView: View { } ) }) { - Section(header: "Catalog") { + Section(header: InlineHeader("Catalog")) { ForEach(filteredAvailableSources, id: \.self) { availableSource in if !sources.contains( where: { @@ -104,6 +105,7 @@ struct SourcesView: View { } } } + .conditionalId(UUID()) .listStyle(.insetGrouped) } }