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:
parent
9a3573a222
commit
52409099d7
13 changed files with 158 additions and 19 deletions
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
24
Ferrite/Views/CommonViews/ConditionalId.swift
Normal file
24
Ferrite/Views/CommonViews/ConditionalId.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Ferrite/Views/CommonViews/InlineHeader.swift
Normal file
30
Ferrite/Views/CommonViews/InlineHeader.swift
Normal 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())
|
||||
}
|
||||
}
|
||||
28
Ferrite/Views/CommonViews/InlinedList.swift
Normal file
28
Ferrite/Views/CommonViews/InlinedList.swift
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -54,6 +54,7 @@ struct SearchResultsView: View {
|
|||
}
|
||||
}
|
||||
.listStyle(.insetGrouped)
|
||||
.inlinedList()
|
||||
.overlay {
|
||||
if scrapingModel.searchResults.isEmpty {
|
||||
if navModel.showSearchProgress {
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ struct SettingsSourceListView: View {
|
|||
}
|
||||
}
|
||||
.listStyle(.insetGrouped)
|
||||
.inlinedList()
|
||||
.sheet(isPresented: $presentSourceSheet) {
|
||||
if #available(iOS 16, *) {
|
||||
SourceListEditorView(sourceUrl: navModel.selectedSourceList?.urlString ?? "")
|
||||
|
|
|
|||
|
|
@ -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 ?? ""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue