From 94804e0e8bf2834eac41d20b61951ee8f0d474ea Mon Sep 17 00:00:00 2001 From: Skitty Date: Sun, 4 Sep 2022 14:14:34 -0500 Subject: [PATCH 01/11] Various UI tweaks - Make about header scrollable - Add no sources text - Add no source lists text - Make edit source list start with current url - Add loading indicator for versions --- Ferrite/Views/AboutView.swift | 29 ++++--- Ferrite/Views/ContentView.swift | 8 +- .../SettingsAppVersionView.swift | 21 +++-- .../SettingsSourceListView.swift | 27 +++++-- .../SettingsViews/SourceListEditorView.swift | 6 +- Ferrite/Views/SourcesView.swift | 80 ++++++++++++------- 6 files changed, 111 insertions(+), 60 deletions(-) diff --git a/Ferrite/Views/AboutView.swift b/Ferrite/Views/AboutView.swift index b2285aa..70e8934 100644 --- a/Ferrite/Views/AboutView.swift +++ b/Ferrite/Views/AboutView.swift @@ -9,24 +9,31 @@ import SwiftUI struct AboutView: View { var body: some View { - VStack { - Image("AppImage") - .resizable() - .frame(width: 100, height: 100) - .cornerRadius(25) - - Text("Ferrite is a free and open source application developed by kingbri under the GNU-GPLv3 license.") - .padding() - - List { + List { + Section { ListRowTextView(leftText: "Version", rightText: UIApplication.shared.appVersion) ListRowTextView(leftText: "Build number", rightText: UIApplication.shared.appBuild) ListRowTextView(leftText: "Build type", rightText: UIApplication.shared.buildType) ListRowLinkView(text: "Discord server", link: "https://discord.gg/sYQxnuD7Fj") ListRowLinkView(text: "GitHub repository", link: "https://github.com/bdashore3/Ferrite") + } header: { + VStack(alignment: .center) { + Image("AppImage") + .resizable() + .frame(width: 100, height: 100) + .clipShape(RoundedRectangle(cornerRadius: 100*0.225, style: .continuous)) + + Text("Ferrite is a free and open source application developed by kingbri under the GNU-GPLv3 license.") + .textCase(.none) + .foregroundColor(.label) + .font(.body) + .padding(.top, 8) + .padding(.bottom, 20) + } + .listRowInsets(EdgeInsets(top: 0, leading: 7, bottom: 0, trailing: 0)) } - .listStyle(.insetGrouped) } + .listStyle(.insetGrouped) .navigationTitle("About") } } diff --git a/Ferrite/Views/ContentView.swift b/Ferrite/Views/ContentView.swift index 6f92f4f..1d9a60f 100644 --- a/Ferrite/Views/ContentView.swift +++ b/Ferrite/Views/ContentView.swift @@ -30,7 +30,7 @@ struct ContentView: View { var body: some View { NavView { VStack(spacing: 10) { - HStack { + HStack(spacing: 6) { Text("Filter") .foregroundColor(.secondary) @@ -50,10 +50,10 @@ struct ContentView: View { Button { selectedSource = source } label: { - Text(name) - if selectedSource == source { - Image(systemName: "checkmark") + Label(name, systemImage: "checkmark") + } else { + Text(name) } } } diff --git a/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift b/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift index eaf87eb..1a31c91 100644 --- a/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift +++ b/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import SwiftUIX struct SettingsAppVersionView: View { @EnvironmentObject var toastModel: ToastViewModel @@ -14,18 +15,27 @@ struct SettingsAppVersionView: View { @State private var releases: [GithubRelease] = [] var body: some View { - List { - Section(header: Text("GitHub links")) { - ForEach(releases, id: \.self) { release in - ListRowLinkView(text: release.tagName, link: release.htmlUrl) + ZStack { + if releases.isEmpty { + ActivityIndicator() + } else { + List { + Section(header: Text("GitHub links")) { + ForEach(releases, id: \.self) { release in + ListRowLinkView(text: release.tagName, link: release.htmlUrl) + } + } } + .listStyle(.insetGrouped) } } .onAppear { viewTask = Task { do { if let fetchedReleases = try await Github().fetchReleases() { - releases = fetchedReleases + withAnimation { + releases = fetchedReleases + } } else { toastModel.updateToastDescription("Github error: No releases found") } @@ -37,7 +47,6 @@ struct SettingsAppVersionView: View { .onDisappear { viewTask?.cancel() } - .listStyle(.insetGrouped) .navigationTitle("Version history") .navigationBarTitleDisplayMode(.inline) } diff --git a/Ferrite/Views/SettingsViews/SettingsSourceListView.swift b/Ferrite/Views/SettingsViews/SettingsSourceListView.swift index 38f3618..42f7995 100644 --- a/Ferrite/Views/SettingsViews/SettingsSourceListView.swift +++ b/Ferrite/Views/SettingsViews/SettingsSourceListView.swift @@ -22,7 +22,9 @@ struct SettingsSourceListView: View { var body: some View { List { - Section(header: Text("List information")) { + if sourceLists.isEmpty { + Text("No source lists") + } else { ForEach(sourceLists, id: \.self) { sourceList in VStack(alignment: .leading, spacing: 5) { Text(sourceList.name) @@ -43,11 +45,20 @@ struct SettingsSourceListView: View { Image(systemName: "pencil") } - Button { - PersistenceController.shared.delete(sourceList, context: backgroundContext) - } label: { - Text("Remove") - Image(systemName: "trash") + 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") + } } } } @@ -56,10 +67,10 @@ struct SettingsSourceListView: View { .listStyle(.insetGrouped) .sheet(isPresented: $presentSourceSheet) { if #available(iOS 16, *) { - SourceListEditorView() + SourceListEditorView(sourceUrl: navModel.selectedSourceList?.urlString ?? "") .presentationDetents([.medium]) } else { - SourceListEditorView() + SourceListEditorView(sourceUrl: navModel.selectedSourceList?.urlString ?? "") } } .navigationTitle("Source lists") diff --git a/Ferrite/Views/SettingsViews/SourceListEditorView.swift b/Ferrite/Views/SettingsViews/SourceListEditorView.swift index 9db11c7..d82d367 100644 --- a/Ferrite/Views/SettingsViews/SourceListEditorView.swift +++ b/Ferrite/Views/SettingsViews/SourceListEditorView.swift @@ -15,7 +15,11 @@ struct SourceListEditorView: View { let backgroundContext = PersistenceController.shared.backgroundContext - @State private var sourceUrl = "" + @State private var sourceUrl: String + + init(sourceUrl: String = "") { + _sourceUrl = State(initialValue: sourceUrl) + } var body: some View { NavView { diff --git a/Ferrite/Views/SourcesView.swift b/Ferrite/Views/SourcesView.swift index 570c92b..283bbf7 100644 --- a/Ferrite/Views/SourcesView.swift +++ b/Ferrite/Views/SourcesView.swift @@ -6,8 +6,11 @@ // import SwiftUI +import SwiftUIX struct SourcesView: View { + @Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass? + @EnvironmentObject var sourceManager: SourceManager @EnvironmentObject var navModel: NavigationViewModel @@ -37,51 +40,67 @@ struct SourcesView: View { } @State private var viewTask: Task? = nil + @State private var checkedForSources = false var body: some View { NavView { - List { - if !updatedSources.isEmpty { - Section(header: "Updates") { - ForEach(updatedSources, id: \.self) { source in - SourceUpdateButtonView(updatedSource: source) - } + ZStack { + if !checkedForSources { + ActivityIndicator() + } 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) } - } - - if !sources.isEmpty { - Section(header: "Installed") { - ForEach(sources, id: \.self) { source in - InstalledSourceView(installedSource: source) + .padding(.top, verticalSizeClass == .regular ? -50 : 0) + } else { + List { + if !updatedSources.isEmpty { + Section(header: "Updates") { + ForEach(updatedSources, id: \.self) { source in + SourceUpdateButtonView(updatedSource: source) + } + } } - } - } - if sourceManager.availableSources.contains(where: { availableSource in - !sources.contains( - where: { - availableSource.name == $0.name && - availableSource.listId == $0.listId && - availableSource.author == $0.author + if !sources.isEmpty { + Section(header: "Installed") { + ForEach(sources, id: \.self) { source in + InstalledSourceView(installedSource: source) + } + } } - ) - }) { - Section(header: "Catalog") { - ForEach(sourceManager.availableSources, id: \.self) { availableSource in - if !sources.contains( + + if 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 + } + ) + }) { + Section(header: "Catalog") { + ForEach(sourceManager.availableSources, id: \.self) { availableSource in + if !sources.contains( + where: { + availableSource.name == $0.name && + availableSource.listId == $0.listId && + availableSource.author == $0.author + } + ) { + SourceCatalogButtonView(availableSource: availableSource) + } } - ) { - SourceCatalogButtonView(availableSource: availableSource) } } } + .listStyle(.insetGrouped) } } - .listStyle(.insetGrouped) .sheet(isPresented: $navModel.showSourceSettings) { SourceSettingsView() .environmentObject(navModel) @@ -89,6 +108,7 @@ struct SourcesView: View { .onAppear { viewTask = Task { await sourceManager.fetchSourcesFromUrl() + checkedForSources = true } } .onDisappear { -- 2.45.2 From 62ace0bf8a3fe6335af47cf48f7c26e4b2f1ea9c Mon Sep 17 00:00:00 2001 From: Skitty Date: Sun, 4 Sep 2022 14:47:04 -0500 Subject: [PATCH 02/11] Add sources page searching - Fix table top inset --- Ferrite/Views/SourcesView.swift | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/Ferrite/Views/SourcesView.swift b/Ferrite/Views/SourcesView.swift index 283bbf7..3f3f175 100644 --- a/Ferrite/Views/SourcesView.swift +++ b/Ferrite/Views/SourcesView.swift @@ -42,6 +42,12 @@ struct SourcesView: View { @State private var viewTask: Task? = nil @State private var checkedForSources = false + @State private var searchText: String = "" + @State private var isEditing = false + + @State var filteredUpdatedSources: [SourceJson] = [] + @State var filteredAvailableSources: [SourceJson] = [] + var body: some View { NavView { ZStack { @@ -58,9 +64,9 @@ struct SourcesView: View { .padding(.top, verticalSizeClass == .regular ? -50 : 0) } else { List { - if !updatedSources.isEmpty { + if !filteredUpdatedSources.isEmpty { Section(header: "Updates") { - ForEach(updatedSources, id: \.self) { source in + ForEach(filteredUpdatedSources, id: \.self) { source in SourceUpdateButtonView(updatedSource: source) } } @@ -74,7 +80,7 @@ struct SourcesView: View { } } - if sourceManager.availableSources.contains(where: { availableSource in + if !filteredAvailableSources.isEmpty && sourceManager.availableSources.contains(where: { availableSource in !sources.contains( where: { availableSource.name == $0.name && @@ -84,7 +90,7 @@ struct SourcesView: View { ) }) { Section(header: "Catalog") { - ForEach(sourceManager.availableSources, id: \.self) { availableSource in + ForEach(filteredAvailableSources, id: \.self) { availableSource in if !sources.contains( where: { availableSource.name == $0.name && @@ -106,8 +112,10 @@ struct SourcesView: View { .environmentObject(navModel) } .onAppear { + filteredUpdatedSources = updatedSources viewTask = Task { await sourceManager.fetchSourcesFromUrl() + filteredAvailableSources = sourceManager.availableSources checkedForSources = true } } @@ -115,6 +123,21 @@ struct SourcesView: View { viewTask?.cancel() } .navigationTitle("Sources") + .navigationSearchBar { + SearchBar("Search", text: $searchText, isEditing: $isEditing) + .showsCancelButton(isEditing) + } + .onChange(of: searchText) { newValue 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, *) { + if searchText.isEmpty { + sources.nsPredicate = nil + } else { + sources.nsPredicate = NSPredicate(format: "name CONTAINS[cd] %@", searchText) + } + } + } } } } -- 2.45.2 From 3e5bf46b9347d4a1ce53ca65039f9ebf02a6c5a5 Mon Sep 17 00:00:00 2001 From: Skitty Date: Sun, 4 Sep 2022 14:56:49 -0500 Subject: [PATCH 03/11] Fix link actions view done button color --- Ferrite/Views/ContentView.swift | 1 - Ferrite/Views/MagnetChoiceView.swift | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Ferrite/Views/ContentView.swift b/Ferrite/Views/ContentView.swift index 1d9a60f..0c1332f 100644 --- a/Ferrite/Views/ContentView.swift +++ b/Ferrite/Views/ContentView.swift @@ -94,7 +94,6 @@ struct ContentView: View { } } } - .dynamicAccentColor(.primary) } .navigationTitle("Search") .navigationSearchBar { diff --git a/Ferrite/Views/MagnetChoiceView.swift b/Ferrite/Views/MagnetChoiceView.swift index a9701df..ae142b2 100644 --- a/Ferrite/Views/MagnetChoiceView.swift +++ b/Ferrite/Views/MagnetChoiceView.swift @@ -88,6 +88,7 @@ struct MagnetChoiceView: View { } } } + .dynamicAccentColor(.primary) .sheet(isPresented: $navModel.showLocalActivitySheet) { if #available(iOS 16, *) { AppActivityView(activityItems: navModel.activityItems) -- 2.45.2 From 79052195f8c778821411f56f21ccbbbe108500e5 Mon Sep 17 00:00:00 2001 From: Skitty Date: Sun, 4 Sep 2022 15:01:10 -0500 Subject: [PATCH 04/11] Title Case for settings pages --- Ferrite/Views/SettingsViews/SettingsAppVersionView.swift | 2 +- Ferrite/Views/SettingsViews/SettingsSourceListView.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift b/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift index 1a31c91..78aaba9 100644 --- a/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift +++ b/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift @@ -47,7 +47,7 @@ struct SettingsAppVersionView: View { .onDisappear { viewTask?.cancel() } - .navigationTitle("Version history") + .navigationTitle("Version History") .navigationBarTitleDisplayMode(.inline) } } diff --git a/Ferrite/Views/SettingsViews/SettingsSourceListView.swift b/Ferrite/Views/SettingsViews/SettingsSourceListView.swift index 42f7995..f691f49 100644 --- a/Ferrite/Views/SettingsViews/SettingsSourceListView.swift +++ b/Ferrite/Views/SettingsViews/SettingsSourceListView.swift @@ -73,7 +73,7 @@ struct SettingsSourceListView: View { SourceListEditorView(sourceUrl: navModel.selectedSourceList?.urlString ?? "") } } - .navigationTitle("Source lists") + .navigationTitle("Source Lists") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { -- 2.45.2 From 6eb19c67b79f2ec691a3c772729c698c8dde857e Mon Sep 17 00:00:00 2001 From: Skitty Date: Sun, 4 Sep 2022 15:03:57 -0500 Subject: [PATCH 05/11] Fix infinite loading indicator with versions --- .../SettingsViews/SettingsAppVersionView.swift | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift b/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift index 78aaba9..a9a180d 100644 --- a/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift +++ b/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift @@ -14,11 +14,13 @@ struct SettingsAppVersionView: View { @State private var viewTask: Task? @State private var releases: [GithubRelease] = [] + @State private var loadedReleases = false + var body: some View { ZStack { - if releases.isEmpty { + if !loadedReleases { ActivityIndicator() - } else { + } else if !releases.isEmpty { List { Section(header: Text("GitHub links")) { ForEach(releases, id: \.self) { release in @@ -33,15 +35,16 @@ struct SettingsAppVersionView: View { viewTask = Task { do { if let fetchedReleases = try await Github().fetchReleases() { - withAnimation { - releases = fetchedReleases - } + releases = fetchedReleases } else { toastModel.updateToastDescription("Github error: No releases found") } } catch { toastModel.updateToastDescription("Github error: \(error)") } + withAnimation { + loadedReleases = true + } } } .onDisappear { -- 2.45.2 From f0a6c2d7088d99a6a459dcc0262be40e789027b2 Mon Sep 17 00:00:00 2001 From: Skitty Date: Sun, 4 Sep 2022 15:05:38 -0500 Subject: [PATCH 06/11] Fix about header top inset --- Ferrite/Views/AboutView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Ferrite/Views/AboutView.swift b/Ferrite/Views/AboutView.swift index 70e8934..6ee081a 100644 --- a/Ferrite/Views/AboutView.swift +++ b/Ferrite/Views/AboutView.swift @@ -22,6 +22,7 @@ struct AboutView: View { .resizable() .frame(width: 100, height: 100) .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.") .textCase(.none) -- 2.45.2 From 33e5b4e518fe3124606c72385e041a975c71bc8b Mon Sep 17 00:00:00 2001 From: Skitty Date: Sun, 4 Sep 2022 15:10:47 -0500 Subject: [PATCH 07/11] Slightly increase cell height --- .../SettingsSourceListView.swift | 1 + .../SourceViews/InstalledSourceView.swift | 20 ++++++++++++++----- .../Views/SourceViews/SourceCatalogView.swift | 1 + .../SourceViews/SourceSettingsView.swift | 1 + .../SourceViews/SourceUpdateButtonView.swift | 1 + 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Ferrite/Views/SettingsViews/SettingsSourceListView.swift b/Ferrite/Views/SettingsViews/SettingsSourceListView.swift index f691f49..c3649df 100644 --- a/Ferrite/Views/SettingsViews/SettingsSourceListView.swift +++ b/Ferrite/Views/SettingsViews/SettingsSourceListView.swift @@ -36,6 +36,7 @@ struct SettingsSourceListView: View { .font(.caption) .foregroundColor(.gray) } + .padding(.vertical, 2) .contextMenu { Button { navModel.selectedSourceList = sourceList diff --git a/Ferrite/Views/SourceViews/InstalledSourceView.swift b/Ferrite/Views/SourceViews/InstalledSourceView.swift index 429ab9e..88065f3 100644 --- a/Ferrite/Views/SourceViews/InstalledSourceView.swift +++ b/Ferrite/Views/SourceViews/InstalledSourceView.swift @@ -32,6 +32,7 @@ struct InstalledSourceView: View { Text("by \(installedSource.author)") .foregroundColor(.secondary) } + .padding(.vertical, 2) } .contextMenu { Button { @@ -42,11 +43,20 @@ struct InstalledSourceView: View { Image(systemName: "gear") } - Button { - PersistenceController.shared.delete(installedSource, context: backgroundContext) - } label: { - Text("Remove") - Image(systemName: "trash") + if #available(iOS 15.0, *) { + Button(role: .destructive) { + PersistenceController.shared.delete(installedSource, context: backgroundContext) + } label: { + Text("Remove") + Image(systemName: "trash") + } + } else { + Button { + PersistenceController.shared.delete(installedSource, context: backgroundContext) + } label: { + Text("Remove") + Image(systemName: "trash") + } } } } diff --git a/Ferrite/Views/SourceViews/SourceCatalogView.swift b/Ferrite/Views/SourceViews/SourceCatalogView.swift index 9566df8..cdfa129 100644 --- a/Ferrite/Views/SourceViews/SourceCatalogView.swift +++ b/Ferrite/Views/SourceViews/SourceCatalogView.swift @@ -33,5 +33,6 @@ struct SourceCatalogButtonView: View { } } } + .padding(.vertical, 2) } } diff --git a/Ferrite/Views/SourceViews/SourceSettingsView.swift b/Ferrite/Views/SourceViews/SourceSettingsView.swift index 7f8f926..f195a16 100644 --- a/Ferrite/Views/SourceViews/SourceSettingsView.swift +++ b/Ferrite/Views/SourceViews/SourceSettingsView.swift @@ -40,6 +40,7 @@ struct SourceSettingsView: View { .foregroundColor(.secondary) .font(.caption) } + .padding(.vertical, 2) } if selectedSource.dynamicBaseUrl { diff --git a/Ferrite/Views/SourceViews/SourceUpdateButtonView.swift b/Ferrite/Views/SourceViews/SourceUpdateButtonView.swift index 029788b..2b0f1f2 100644 --- a/Ferrite/Views/SourceViews/SourceUpdateButtonView.swift +++ b/Ferrite/Views/SourceViews/SourceUpdateButtonView.swift @@ -24,6 +24,7 @@ struct SourceUpdateButtonView: View { Text("by \(updatedSource.author ?? "Unknown")") .foregroundColor(.secondary) } + .padding(.vertical, 2) Spacer() -- 2.45.2 From b4fe3807d6334c678047380b6a64ef3a69155582 Mon Sep 17 00:00:00 2001 From: Skitty Date: Sun, 4 Sep 2022 18:07:31 -0500 Subject: [PATCH 08/11] Use ProgressView instead of ActivityIndicator --- Ferrite/Views/SettingsViews/SettingsAppVersionView.swift | 3 +-- Ferrite/Views/SourcesView.swift | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift b/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift index a9a180d..3553c64 100644 --- a/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift +++ b/Ferrite/Views/SettingsViews/SettingsAppVersionView.swift @@ -6,7 +6,6 @@ // import SwiftUI -import SwiftUIX struct SettingsAppVersionView: View { @EnvironmentObject var toastModel: ToastViewModel @@ -19,7 +18,7 @@ struct SettingsAppVersionView: View { var body: some View { ZStack { if !loadedReleases { - ActivityIndicator() + ProgressView() } else if !releases.isEmpty { List { Section(header: Text("GitHub links")) { diff --git a/Ferrite/Views/SourcesView.swift b/Ferrite/Views/SourcesView.swift index 3f3f175..7e11757 100644 --- a/Ferrite/Views/SourcesView.swift +++ b/Ferrite/Views/SourcesView.swift @@ -52,7 +52,7 @@ struct SourcesView: View { NavView { ZStack { if !checkedForSources { - ActivityIndicator() + ProgressView() } else if sources.isEmpty && sourceManager.availableSources.isEmpty { VStack { Text("No Sources") -- 2.45.2 From 16a39c3a58499a74bd82727750aa73c18e596f73 Mon Sep 17 00:00:00 2001 From: kingbri Date: Mon, 5 Sep 2022 12:23:05 -0400 Subject: [PATCH 09/11] 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 --- Ferrite.xcodeproj/project.pbxproj | 29 ++++++++++++++++++ Ferrite/Extensions/View.swift | 23 ++++++++++++++ Ferrite/Views/CommonViews/ConditionalId.swift | 24 +++++++++++++++ Ferrite/Views/CommonViews/InlineHeader.swift | 30 +++++++++++++++++++ Ferrite/Views/CommonViews/InlinedList.swift | 28 +++++++++++++++++ Ferrite/Views/SearchResultsView.swift | 1 + Ferrite/Views/SettingsView.swift | 11 +++---- .../DefaultActionsPickerViews.swift | 2 ++ .../SettingsAppVersionView.swift | 2 +- .../SettingsSourceListView.swift | 1 + .../SettingsViews/SourceListEditorView.swift | 10 +++---- .../SourceViews/SourceSettingsView.swift | 8 ++--- Ferrite/Views/SourcesView.swift | 8 +++-- 13 files changed, 158 insertions(+), 19 deletions(-) create mode 100644 Ferrite/Views/CommonViews/ConditionalId.swift create mode 100644 Ferrite/Views/CommonViews/InlineHeader.swift create mode 100644 Ferrite/Views/CommonViews/InlinedList.swift 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) } } -- 2.45.2 From 541264d297443431af0d764183ea01efbbbfc215 Mon Sep 17 00:00:00 2001 From: kingbri Date: Mon, 5 Sep 2022 12:42:01 -0400 Subject: [PATCH 10/11] Sources: Fix searchbar behavior Cancelling the search now actually cancels the search. Signed-off-by: kingbri --- Ferrite/Views/SourcesView.swift | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Ferrite/Views/SourcesView.swift b/Ferrite/Views/SourcesView.swift index faf7388..6a99021 100644 --- a/Ferrite/Views/SourcesView.swift +++ b/Ferrite/Views/SourcesView.swift @@ -40,14 +40,13 @@ struct SourcesView: View { return tempSources } - @State private var viewTask: Task? = nil @State private var checkedForSources = false - - @State private var searchText: String = "" @State private var isEditing = false - @State var filteredUpdatedSources: [SourceJson] = [] - @State var filteredAvailableSources: [SourceJson] = [] + @State private var viewTask: Task? = nil + @State private var searchText: String = "" + @State private var filteredUpdatedSources: [SourceJson] = [] + @State private var filteredAvailableSources: [SourceJson] = [] var body: some View { NavView { @@ -128,6 +127,9 @@ struct SourcesView: View { .navigationSearchBar { SearchBar("Search", text: $searchText, isEditing: $isEditing) .showsCancelButton(isEditing) + .onCancel { + searchText = "" + } } .onChange(of: searchText) { newValue in filteredAvailableSources = sourceManager.availableSources.filter { searchText.isEmpty ? true : $0.name.contains(searchText) } -- 2.45.2 From 1600f17312dff57cd0b3476f71cc35cd3888977d Mon Sep 17 00:00:00 2001 From: kingbri Date: Mon, 5 Sep 2022 14:58:02 -0400 Subject: [PATCH 11/11] 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 +- .../CommonViews/EmptyInstructionView.swift | 27 ++++++ Ferrite/Views/CommonViews/InlinedList.swift | 2 +- Ferrite/Views/SettingsView.swift | 2 +- .../SettingsAppVersionView.swift | 1 + .../SettingsSourceListView.swift | 84 ++++++++++--------- .../SettingsViews/SourceListEditorView.swift | 8 +- Ferrite/Views/SourcesView.swift | 25 ++---- 10 files changed, 94 insertions(+), 67 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/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/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..56f8f00 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 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..778d6d7 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() - .sheet(isPresented: $presentSourceSheet) { - if #available(iOS 16, *) { - SourceListEditorView(sourceUrl: navModel.selectedSourceList?.urlString ?? "") - .presentationDetents([.medium]) - } else { - SourceListEditorView(sourceUrl: navModel.selectedSourceList?.urlString ?? "") + .listStyle(.insetGrouped) + .inlinedList() + .sheet(isPresented: $presentSourceSheet) { + if #available(iOS 16, *) { + SourceListEditorView() + .presentationDetents([.medium]) + } else { + 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..7dab7f5 100644 --- a/Ferrite/Views/SourcesView.swift +++ b/Ferrite/Views/SourcesView.swift @@ -5,9 +5,9 @@ // Created by Brian Dashore on 7/24/22. // +import Introspect import SwiftUI import SwiftUIX -import Introspect struct SourcesView: View { @Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass? @@ -53,15 +53,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 +73,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 +87,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 +124,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, *) { -- 2.45.2