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 <bdashore3@proton.me>
This commit is contained in:
kingbri 2022-09-05 14:58:02 -04:00 committed by Brian Dashore
parent a9d2604fb3
commit ff35ab7cfa
10 changed files with 94 additions and 67 deletions

View file

@ -78,6 +78,7 @@
0CBC76FD288D914F0054BE44 /* BatchChoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBC76FC288D914F0054BE44 /* BatchChoiceView.swift */; }; 0CBC76FD288D914F0054BE44 /* BatchChoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBC76FC288D914F0054BE44 /* BatchChoiceView.swift */; };
0CBC76FF288DAAD00054BE44 /* NavigationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBC76FE288DAAD00054BE44 /* NavigationViewModel.swift */; }; 0CBC76FF288DAAD00054BE44 /* NavigationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBC76FE288DAAD00054BE44 /* NavigationViewModel.swift */; };
0CBC7705288DE7F40054BE44 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBC7704288DE7F40054BE44 /* PersistenceController.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 */; }; 0CFEFCFD288A006200B3F490 /* GroupBoxStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CFEFCFC288A006200B3F490 /* GroupBoxStyle.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -148,6 +149,7 @@
0CBC76FE288DAAD00054BE44 /* NavigationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationViewModel.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>"; }; 0CBC7704288DE7F40054BE44 /* PersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceController.swift; sourceTree = "<group>"; };
0CC6E4D428A45BA000AF2BCC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; }; 0CC6E4D428A45BA000AF2BCC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
0CDCB91728C662640098B513 /* EmptyInstructionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyInstructionView.swift; sourceTree = "<group>"; };
0CFEFCFC288A006200B3F490 /* GroupBoxStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupBoxStyle.swift; sourceTree = "<group>"; }; 0CFEFCFC288A006200B3F490 /* GroupBoxStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupBoxStyle.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@ -252,6 +254,7 @@
0CB6516228C5A57300DCA721 /* ConditionalId.swift */, 0CB6516228C5A57300DCA721 /* ConditionalId.swift */,
0CB6516428C5A5D700DCA721 /* InlinedList.swift */, 0CB6516428C5A5D700DCA721 /* InlinedList.swift */,
0CB6516928C5B4A600DCA721 /* InlineHeader.swift */, 0CB6516928C5B4A600DCA721 /* InlineHeader.swift */,
0CDCB91728C662640098B513 /* EmptyInstructionView.swift */,
); );
path = CommonViews; path = CommonViews;
sourceTree = "<group>"; sourceTree = "<group>";
@ -482,6 +485,7 @@
0C794B67289DACB600DD1CC8 /* SourceUpdateButtonView.swift in Sources */, 0C794B67289DACB600DD1CC8 /* SourceUpdateButtonView.swift in Sources */,
0CA148E6288903F000DE2211 /* WebView.swift in Sources */, 0CA148E6288903F000DE2211 /* WebView.swift in Sources */,
0C4CFC4E28970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift in Sources */, 0C4CFC4E28970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift in Sources */,
0CDCB91828C662640098B513 /* EmptyInstructionView.swift in Sources */,
0CA148E2288903F000DE2211 /* Data.swift in Sources */, 0CA148E2288903F000DE2211 /* Data.swift in Sources */,
0C57D4CC289032ED008534E8 /* SearchResultRDView.swift in Sources */, 0C57D4CC289032ED008534E8 /* SearchResultRDView.swift in Sources */,
0C7D11FC28AA01E900ED92DB /* DynamicAccentColor.swift in Sources */, 0C7D11FC28AA01E900ED92DB /* DynamicAccentColor.swift in Sources */,

View file

@ -5,14 +5,14 @@
// Created by Brian Dashore on 8/15/22. // Created by Brian Dashore on 8/15/22.
// //
import SwiftUI
import Introspect import Introspect
import SwiftUI
extension View { extension View {
// MARK: Custom introspect functions // MARK: Custom introspect functions
func introspectCollectionView(customize: @escaping (UICollectionView) -> ()) -> some View { func introspectCollectionView(customize: @escaping (UICollectionView) -> Void) -> some View {
return inject(UIKitIntrospectionView( inject(UIKitIntrospectionView(
selector: { introspectionView in selector: { introspectionView in
guard let viewHost = Introspect.findViewHost(from: introspectionView) else { guard let viewHost = Introspect.findViewHost(from: introspectionView) else {
return nil return nil

View file

@ -21,7 +21,7 @@ struct AboutView: View {
Image("AppImage") Image("AppImage")
.resizable() .resizable()
.frame(width: 100, height: 100) .frame(width: 100, height: 100)
.clipShape(RoundedRectangle(cornerRadius: 100*0.225, style: .continuous)) .clipShape(RoundedRectangle(cornerRadius: 100 * 0.225, style: .continuous))
.padding(.top, 24) .padding(.top, 24)
Text("Ferrite is a free and open source application developed by kingbri under the GNU-GPLv3 license.") Text("Ferrite is a free and open source application developed by kingbri under the GNU-GPLv3 license.")

View file

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

View file

@ -8,8 +8,8 @@
// Use UITableView.appearance().contentInset.top = -20 for iOS 15 and below in the App file // Use UITableView.appearance().contentInset.top = -20 for iOS 15 and below in the App file
// //
import SwiftUI
import Introspect import Introspect
import SwiftUI
struct InlinedList: ViewModifier { struct InlinedList: ViewModifier {
func body(content: Content) -> some View { func body(content: Content) -> some View {

View file

@ -5,8 +5,8 @@
// Created by Brian Dashore on 7/11/22. // Created by Brian Dashore on 7/11/22.
// //
import SwiftUI
import Introspect import Introspect
import SwiftUI
struct SettingsView: View { struct SettingsView: View {
@EnvironmentObject var debridManager: DebridManager @EnvironmentObject var debridManager: DebridManager

View file

@ -41,6 +41,7 @@ struct SettingsAppVersionView: View {
} catch { } catch {
toastModel.updateToastDescription("Github error: \(error)") toastModel.updateToastDescription("Github error: \(error)")
} }
withAnimation { withAnimation {
loadedReleases = true loadedReleases = true
} }

View file

@ -21,58 +21,60 @@ struct SettingsSourceListView: View {
@State private var selectedSourceList: SourceList? @State private var selectedSourceList: SourceList?
var body: some View { var body: some View {
List { ZStack {
if sourceLists.isEmpty { if sourceLists.isEmpty {
Text("No source lists") EmptyInstructionView(title: "No Lists", message: "Add a source list using the + button in the top-right")
} else { } else {
ForEach(sourceLists, id: \.self) { sourceList in List {
VStack(alignment: .leading, spacing: 5) { ForEach(sourceLists, id: \.self) { sourceList in
Text(sourceList.name) VStack(alignment: .leading, spacing: 5) {
Text(sourceList.name)
Text(sourceList.author) Text(sourceList.author)
.foregroundColor(.gray) .foregroundColor(.gray)
Text("ID: \(sourceList.id)") Text("ID: \(sourceList.id)")
.font(.caption) .font(.caption)
.foregroundColor(.gray) .foregroundColor(.gray)
}
.padding(.vertical, 2)
.contextMenu {
Button {
navModel.selectedSourceList = sourceList
presentSourceSheet.toggle()
} label: {
Text("Edit")
Image(systemName: "pencil")
} }
.padding(.vertical, 2)
if #available(iOS 15.0, *) { .contextMenu {
Button(role: .destructive) {
PersistenceController.shared.delete(sourceList, context: backgroundContext)
} label: {
Text("Remove")
Image(systemName: "trash")
}
} else {
Button { Button {
PersistenceController.shared.delete(sourceList, context: backgroundContext) navModel.selectedSourceList = sourceList
presentSourceSheet.toggle()
} label: { } label: {
Text("Remove") Text("Edit")
Image(systemName: "trash") Image(systemName: "pencil")
}
if #available(iOS 15.0, *) {
Button(role: .destructive) {
PersistenceController.shared.delete(sourceList, context: backgroundContext)
} label: {
Text("Remove")
Image(systemName: "trash")
}
} else {
Button {
PersistenceController.shared.delete(sourceList, context: backgroundContext)
} label: {
Text("Remove")
Image(systemName: "trash")
}
} }
} }
} }
} }
} .listStyle(.insetGrouped)
} .inlinedList()
.listStyle(.insetGrouped) .sheet(isPresented: $presentSourceSheet) {
.inlinedList() if #available(iOS 16, *) {
.sheet(isPresented: $presentSourceSheet) { SourceListEditorView()
if #available(iOS 16, *) { .presentationDetents([.medium])
SourceListEditorView(sourceUrl: navModel.selectedSourceList?.urlString ?? "") } else {
.presentationDetents([.medium]) SourceListEditorView()
} else { }
SourceListEditorView(sourceUrl: navModel.selectedSourceList?.urlString ?? "") }
} }
} }
.navigationTitle("Source Lists") .navigationTitle("Source Lists")

View file

@ -15,11 +15,9 @@ struct SourceListEditorView: View {
let backgroundContext = PersistenceController.shared.backgroundContext let backgroundContext = PersistenceController.shared.backgroundContext
@State private var sourceUrl: String @State private var sourceUrlSet = false
init(sourceUrl: String = "") { @State private var sourceUrl: String = ""
_sourceUrl = State(initialValue: sourceUrl)
}
var body: some View { var body: some View {
NavView { NavView {
@ -28,9 +26,11 @@ struct SourceListEditorView: View {
.disableAutocorrection(true) .disableAutocorrection(true)
.keyboardType(.URL) .keyboardType(.URL)
.autocapitalization(.none) .autocapitalization(.none)
.conditionalId(sourceUrlSet)
} }
.onAppear { .onAppear {
sourceUrl = navModel.selectedSourceList?.urlString ?? "" sourceUrl = navModel.selectedSourceList?.urlString ?? ""
sourceUrlSet = true
} }
.alert(isPresented: $sourceManager.showUrlErrorAlert) { .alert(isPresented: $sourceManager.showUrlErrorAlert) {
Alert( Alert(

View file

@ -5,9 +5,9 @@
// Created by Brian Dashore on 7/24/22. // Created by Brian Dashore on 7/24/22.
// //
import Introspect
import SwiftUI import SwiftUI
import SwiftUIX import SwiftUIX
import Introspect
struct SourcesView: View { struct SourcesView: View {
@Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass? @Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass?
@ -53,15 +53,8 @@ struct SourcesView: View {
ZStack { ZStack {
if !checkedForSources { if !checkedForSources {
ProgressView() ProgressView()
} else if sources.isEmpty && sourceManager.availableSources.isEmpty { } else if sources.isEmpty, sourceManager.availableSources.isEmpty {
VStack { EmptyInstructionView(title: "No Sources", message: "Add a source list in Settings")
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 { } else {
List { List {
if !filteredUpdatedSources.isEmpty { 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( !sources.contains(
where: { where: {
availableSource.name == $0.name && availableSource.name == $0.name &&
availableSource.listId == $0.listId && availableSource.listId == $0.listId &&
availableSource.author == $0.author availableSource.author == $0.author
} }
) )
}) { }) {
@ -94,8 +87,8 @@ struct SourcesView: View {
if !sources.contains( if !sources.contains(
where: { where: {
availableSource.name == $0.name && availableSource.name == $0.name &&
availableSource.listId == $0.listId && availableSource.listId == $0.listId &&
availableSource.author == $0.author availableSource.author == $0.author
} }
) { ) {
SourceCatalogButtonView(availableSource: availableSource) SourceCatalogButtonView(availableSource: availableSource)
@ -131,7 +124,7 @@ struct SourcesView: View {
searchText = "" searchText = ""
} }
} }
.onChange(of: searchText) { newValue in .onChange(of: searchText) { _ in
filteredAvailableSources = sourceManager.availableSources.filter { searchText.isEmpty ? true : $0.name.contains(searchText) } filteredAvailableSources = sourceManager.availableSources.filter { searchText.isEmpty ? true : $0.name.contains(searchText) }
filteredUpdatedSources = updatedSources.filter { searchText.isEmpty ? true : $0.name.contains(searchText) } filteredUpdatedSources = updatedSources.filter { searchText.isEmpty ? true : $0.name.contains(searchText) }
if #available(iOS 15.0, *) { if #available(iOS 15.0, *) {