Ferrite: Properly inline lists

The inset grouped list style has a top inset that adds extra space
between the navigation bar title and the list rows. Use introspect
to remove this space on UITableView and UICollectionView (for iOS 16).

Sections completely ignore the introspect changes, so add a section
header which removes the list row insets.

Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
kingbri 2022-09-05 12:23:05 -04:00 committed by Brian Dashore
parent 9a3573a222
commit 52409099d7
13 changed files with 158 additions and 19 deletions

View file

@ -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 = "<group>"; };
0CA3FB1F28B91D9500FA10A8 /* IndeterminateProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndeterminateProgressView.swift; sourceTree = "<group>"; };
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 = "<group>"; };
0CB6516428C5A5D700DCA721 /* InlinedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlinedList.swift; sourceTree = "<group>"; };
0CB6516928C5B4A600DCA721 /* InlineHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineHeader.swift; sourceTree = "<group>"; };
0CBC76FC288D914F0054BE44 /* BatchChoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchChoiceView.swift; sourceTree = "<group>"; };
0CBC76FE288DAAD00054BE44 /* NavigationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationViewModel.swift; sourceTree = "<group>"; };
0CBC7704288DE7F40054BE44 /* PersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceController.swift; sourceTree = "<group>"; };
@ -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 = "<group>";
@ -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 */

View file

@ -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: Hashable>(_ id: ID) -> some View {
modifier(ConditionalId(id: id))
}
func inlinedList() -> some View {
modifier(InlinedList())
}
}

View file

@ -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<ID: Hashable>: ViewModifier {
let id: ID
func body(content: Content) -> some View {
if #available(iOS 16, *) {
content
} else {
content
.id(id)
}
}
}

View file

@ -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())
}
}

View file

@ -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))
}
}
}
}

View file

@ -54,6 +54,7 @@ struct SearchResultsView: View {
}
}
.listStyle(.insetGrouped)
.inlinedList()
.overlay {
if scrapingModel.searchResults.isEmpty {
if navModel.showSearchProgress {

View file

@ -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())

View file

@ -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)
}

View file

@ -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)
}

View file

@ -66,6 +66,7 @@ struct SettingsSourceListView: View {
}
}
.listStyle(.insetGrouped)
.inlinedList()
.sheet(isPresented: $presentSourceSheet) {
if #available(iOS 16, *) {
SourceListEditorView(sourceUrl: navModel.selectedSourceList?.urlString ?? "")

View file

@ -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 ?? ""

View file

@ -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

View file

@ -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)
}
}