Ios 14 #5
20 changed files with 232 additions and 268 deletions
|
|
@ -20,6 +20,7 @@
|
|||
0C64A4B4288903680079976D /* Base32 in Frameworks */ = {isa = PBXBuildFile; productRef = 0C64A4B3288903680079976D /* Base32 */; };
|
||||
0C64A4B7288903880079976D /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 0C64A4B6288903880079976D /* KeychainSwift */; };
|
||||
0C733287289C4C820058D1FE /* SourceSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C733286289C4C820058D1FE /* SourceSettingsView.swift */; };
|
||||
0C7376F028A97D1400D60918 /* SwiftUIX in Frameworks */ = {isa = PBXBuildFile; productRef = 0C7376EF28A97D1400D60918 /* SwiftUIX */; };
|
||||
0C750744289B003E004B3906 /* SourceRssParser+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C750742289B003E004B3906 /* SourceRssParser+CoreDataClass.swift */; };
|
||||
0C750745289B003E004B3906 /* SourceRssParser+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C750743289B003E004B3906 /* SourceRssParser+CoreDataProperties.swift */; };
|
||||
0C794B67289DACB600DD1CC8 /* SourceUpdateButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C794B66289DACB600DD1CC8 /* SourceUpdateButtonView.swift */; };
|
||||
|
|
@ -28,6 +29,8 @@
|
|||
0C794B6D289EFA2E00DD1CC8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0C794B6C289EFA2E00DD1CC8 /* LaunchScreen.storyboard */; };
|
||||
0C79DC072899AF3C003F1C5A /* SourceSeedLeech+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C79DC052899AF3C003F1C5A /* SourceSeedLeech+CoreDataClass.swift */; };
|
||||
0C79DC082899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C79DC062899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift */; };
|
||||
0C7D11FC28AA01E900ED92DB /* DynamicAccentColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7D11FB28AA01E900ED92DB /* DynamicAccentColor.swift */; };
|
||||
0C7D11FE28AA03FE00ED92DB /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7D11FD28AA03FE00ED92DB /* View.swift */; };
|
||||
0C84F4772895BE680074B7C9 /* FerriteDB.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 0C84F4752895BE680074B7C9 /* FerriteDB.xcdatamodeld */; };
|
||||
0C84F4822895BFED0074B7C9 /* Source+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84F47A2895BFED0074B7C9 /* Source+CoreDataClass.swift */; };
|
||||
0C84F4832895BFED0074B7C9 /* Source+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84F47B2895BFED0074B7C9 /* Source+CoreDataProperties.swift */; };
|
||||
|
|
@ -35,7 +38,6 @@
|
|||
0C84F4852895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84F47D2895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift */; };
|
||||
0C84F4862895BFED0074B7C9 /* SourceList+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84F47E2895BFED0074B7C9 /* SourceList+CoreDataClass.swift */; };
|
||||
0C84F4872895BFED0074B7C9 /* SourceList+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84F47F2895BFED0074B7C9 /* SourceList+CoreDataProperties.swift */; };
|
||||
0C90E32C2888E5D000C0BC89 /* ActivityView in Frameworks */ = {isa = PBXBuildFile; productRef = 0C90E32B2888E5D000C0BC89 /* ActivityView */; };
|
||||
0C95D8D828A55B03005E22B3 /* DefaultActionsPickerViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C95D8D728A55B03005E22B3 /* DefaultActionsPickerViews.swift */; };
|
||||
0C95D8DA28A55BB6005E22B3 /* SettingsModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C95D8D928A55BB6005E22B3 /* SettingsModels.swift */; };
|
||||
0CA05457288EE58200850554 /* SettingsSourceListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA05456288EE58200850554 /* SettingsSourceListView.swift */; };
|
||||
|
|
@ -88,6 +90,8 @@
|
|||
0C794B6C289EFA2E00DD1CC8 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
0C79DC052899AF3C003F1C5A /* SourceSeedLeech+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceSeedLeech+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
0C79DC062899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceSeedLeech+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
0C7D11FB28AA01E900ED92DB /* DynamicAccentColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicAccentColor.swift; sourceTree = "<group>"; };
|
||||
0C7D11FD28AA03FE00ED92DB /* View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = "<group>"; };
|
||||
0C84F4762895BE680074B7C9 /* FerriteDB.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = FerriteDB.xcdatamodel; sourceTree = "<group>"; };
|
||||
0C84F47A2895BFED0074B7C9 /* Source+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Source+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
0C84F47B2895BFED0074B7C9 /* Source+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Source+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
|
|
@ -134,9 +138,9 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0C90E32C2888E5D000C0BC89 /* ActivityView in Frameworks */,
|
||||
0C64A4B4288903680079976D /* Base32 in Frameworks */,
|
||||
0C4CFC462897030D00AD9FAD /* Regex in Frameworks */,
|
||||
0C7376F028A97D1400D60918 /* SwiftUIX in Frameworks */,
|
||||
0C64A4B7288903880079976D /* KeychainSwift in Frameworks */,
|
||||
0CAF1C7B286F5C8600296F86 /* SwiftSoup in Frameworks */,
|
||||
);
|
||||
|
|
@ -221,6 +225,7 @@
|
|||
0CA148C1288903F000DE2211 /* NavView.swift */,
|
||||
0CFEFCFC288A006200B3F490 /* GroupBoxStyle.swift */,
|
||||
0C32FB562890D1F2002BD219 /* ListRowViews.swift */,
|
||||
0C7D11FB28AA01E900ED92DB /* DynamicAccentColor.swift */,
|
||||
);
|
||||
path = CommonViews;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -240,6 +245,7 @@
|
|||
0CA148CA288903F000DE2211 /* Data.swift */,
|
||||
0CA148CB288903F000DE2211 /* Task.swift */,
|
||||
0C32FB542890D1BF002BD219 /* UIApplication.swift */,
|
||||
0C7D11FD28AA03FE00ED92DB /* View.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -338,10 +344,10 @@
|
|||
name = Ferrite;
|
||||
packageProductDependencies = (
|
||||
0CAF1C7A286F5C8600296F86 /* SwiftSoup */,
|
||||
0C90E32B2888E5D000C0BC89 /* ActivityView */,
|
||||
0C64A4B3288903680079976D /* Base32 */,
|
||||
0C64A4B6288903880079976D /* KeychainSwift */,
|
||||
0C4CFC452897030D00AD9FAD /* Regex */,
|
||||
0C7376EF28A97D1400D60918 /* SwiftUIX */,
|
||||
);
|
||||
productName = Torrenter;
|
||||
productReference = 0CAF1C68286F5C0E00296F86 /* Ferrite.app */;
|
||||
|
|
@ -373,10 +379,10 @@
|
|||
mainGroup = 0CAF1C5F286F5C0D00296F86;
|
||||
packageReferences = (
|
||||
0CAF1C79286F5C8600296F86 /* XCRemoteSwiftPackageReference "SwiftSoup" */,
|
||||
0C90E32A2888E5D000C0BC89 /* XCRemoteSwiftPackageReference "ActivityView" */,
|
||||
0C64A4B2288903680079976D /* XCRemoteSwiftPackageReference "Base32" */,
|
||||
0C64A4B5288903880079976D /* XCRemoteSwiftPackageReference "keychain-swift" */,
|
||||
0C4CFC442897030D00AD9FAD /* XCRemoteSwiftPackageReference "Regex" */,
|
||||
0C7376EE28A97D1400D60918 /* XCRemoteSwiftPackageReference "SwiftUIX" */,
|
||||
);
|
||||
productRefGroup = 0CAF1C69286F5C0E00296F86 /* Products */;
|
||||
projectDirPath = "";
|
||||
|
|
@ -419,6 +425,7 @@
|
|||
0CBC76FD288D914F0054BE44 /* BatchChoiceView.swift in Sources */,
|
||||
0CF501F2289AE06A0099C785 /* SourceTracker+CoreDataClass.swift in Sources */,
|
||||
0C32FB552890D1BF002BD219 /* UIApplication.swift in Sources */,
|
||||
0C7D11FE28AA03FE00ED92DB /* View.swift in Sources */,
|
||||
0C0D50E7288DFF850035ECC8 /* SourcesView.swift in Sources */,
|
||||
0CA148EC288903F000DE2211 /* ContentView.swift in Sources */,
|
||||
0C95D8D828A55B03005E22B3 /* DefaultActionsPickerViews.swift in Sources */,
|
||||
|
|
@ -439,6 +446,7 @@
|
|||
0C4CFC4E28970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift in Sources */,
|
||||
0CA148E2288903F000DE2211 /* Data.swift in Sources */,
|
||||
0C57D4CC289032ED008534E8 /* SearchResultRDView.swift in Sources */,
|
||||
0C7D11FC28AA01E900ED92DB /* DynamicAccentColor.swift in Sources */,
|
||||
0CA05459288EE9E600850554 /* SourceManager.swift in Sources */,
|
||||
0C84F4772895BE680074B7C9 /* FerriteDB.xcdatamodeld in Sources */,
|
||||
0C733287289C4C820058D1FE /* SourceSettingsView.swift in Sources */,
|
||||
|
|
@ -513,7 +521,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
|
|
@ -567,7 +575,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
|
|
@ -583,7 +591,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
CURRENT_PROJECT_VERSION = 8;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Ferrite/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = 8A74DBQ6S3;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
|
@ -596,11 +604,12 @@
|
|||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.3.2;
|
||||
MARKETING_VERSION = 0.3.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.kingbri.Ferrite;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
|
|
@ -615,7 +624,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
CURRENT_PROJECT_VERSION = 8;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Ferrite/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = 8A74DBQ6S3;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
|
@ -628,11 +637,12 @@
|
|||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.3.2;
|
||||
MARKETING_VERSION = 0.3.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.kingbri.Ferrite;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
|
|
@ -689,12 +699,12 @@
|
|||
kind = branch;
|
||||
};
|
||||
};
|
||||
0C90E32A2888E5D000C0BC89 /* XCRemoteSwiftPackageReference "ActivityView" */ = {
|
||||
0C7376EE28A97D1400D60918 /* XCRemoteSwiftPackageReference "SwiftUIX" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/SwiftUI-Plus/ActivityView.git";
|
||||
repositoryURL = "https://github.com/SwiftUIX/SwiftUIX";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 1.0.0;
|
||||
branch = master;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
0CAF1C79286F5C8600296F86 /* XCRemoteSwiftPackageReference "SwiftSoup" */ = {
|
||||
|
|
@ -723,10 +733,10 @@
|
|||
package = 0C64A4B5288903880079976D /* XCRemoteSwiftPackageReference "keychain-swift" */;
|
||||
productName = KeychainSwift;
|
||||
};
|
||||
0C90E32B2888E5D000C0BC89 /* ActivityView */ = {
|
||||
0C7376EF28A97D1400D60918 /* SwiftUIX */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 0C90E32A2888E5D000C0BC89 /* XCRemoteSwiftPackageReference "ActivityView" */;
|
||||
productName = ActivityView;
|
||||
package = 0C7376EE28A97D1400D60918 /* XCRemoteSwiftPackageReference "SwiftUIX" */;
|
||||
productName = SwiftUIX;
|
||||
};
|
||||
0CAF1C7A286F5C8600296F86 /* SwiftSoup */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@ struct PersistenceController {
|
|||
backgroundContext.automaticallyMergesChangesFromParent = true
|
||||
backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
|
||||
try? backgroundContext.setQueryGenerationFrom(.current)
|
||||
|
||||
container.viewContext.automaticallyMergesChangesFromParent = true
|
||||
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
|
||||
try? container.viewContext.setQueryGenerationFrom(.current)
|
||||
}
|
||||
|
||||
func save(_ context: NSManagedObjectContext? = nil) {
|
||||
|
|
|
|||
16
Ferrite/Extensions/View.swift
Normal file
16
Ferrite/Extensions/View.swift
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
//
|
||||
// View.swift
|
||||
// Ferrite
|
||||
//
|
||||
// Created by Brian Dashore on 8/15/22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension View {
|
||||
// MARK: Modifiers
|
||||
|
||||
func dynamicAccentColor(_ color: Color) -> some View {
|
||||
modifier(DynamicAccentColor(color: color))
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@
|
|||
// Created by Brian Dashore on 7/24/22.
|
||||
//
|
||||
|
||||
import ActivityView
|
||||
import SwiftUI
|
||||
|
||||
enum ViewTab {
|
||||
|
|
@ -28,8 +27,14 @@ class NavigationViewModel: ObservableObject {
|
|||
case batch
|
||||
}
|
||||
|
||||
@Published var isEditingSearch: Bool = false
|
||||
@Published var isSearching: Bool = false
|
||||
|
||||
@Published var hideNavigationBar = false
|
||||
|
||||
@Published var currentChoiceSheet: ChoiceSheetType?
|
||||
@Published var currentActivityItem: ActivityItem?
|
||||
@Published var activityItems: [Any] = []
|
||||
@Published var showActivityView: Bool = false
|
||||
|
||||
@Published var selectedTab: ViewTab = .search
|
||||
@Published var showSearchProgress: Bool = false
|
||||
|
|
@ -70,7 +75,8 @@ class NavigationViewModel: ObservableObject {
|
|||
}
|
||||
case .shareDownload:
|
||||
if let downloadUrl = URL(string: urlString), currentChoiceSheet == nil {
|
||||
currentActivityItem = ActivityItem(items: downloadUrl)
|
||||
activityItems = [downloadUrl]
|
||||
showActivityView.toggle()
|
||||
} else {
|
||||
toastModel?.toastDescription = "Could not create object for sharing"
|
||||
}
|
||||
|
|
@ -91,7 +97,8 @@ class NavigationViewModel: ObservableObject {
|
|||
}
|
||||
case .shareMagnet:
|
||||
if let magnetUrl = URL(string: searchResult.magnetLink), currentChoiceSheet == nil {
|
||||
currentActivityItem = ActivityItem(items: magnetUrl)
|
||||
activityItems = [magnetUrl]
|
||||
showActivityView.toggle()
|
||||
} else {
|
||||
toastModel?.toastDescription = "Could not create object for sharing"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -274,6 +274,8 @@ public class SourceManager: ObservableObject {
|
|||
existingSourceList.urlString = sourceUrl
|
||||
existingSourceList.name = rawResponse.name
|
||||
existingSourceList.author = rawResponse.author
|
||||
|
||||
try PersistenceController.shared.container.viewContext.save()
|
||||
} else {
|
||||
let sourceListRequest = SourceList.fetchRequest()
|
||||
let urlPredicate = NSPredicate(format: "urlString == %@", sourceUrl)
|
||||
|
|
@ -293,9 +295,9 @@ public class SourceManager: ObservableObject {
|
|||
newSourceUrl.urlString = sourceUrl
|
||||
newSourceUrl.name = rawResponse.name
|
||||
newSourceUrl.author = rawResponse.author
|
||||
}
|
||||
|
||||
try backgroundContext.save()
|
||||
try backgroundContext.save()
|
||||
}
|
||||
|
||||
return true
|
||||
} catch {
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@
|
|||
import SwiftUI
|
||||
|
||||
struct AboutView: View {
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Image("AppImage")
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
import SwiftUI
|
||||
|
||||
struct BatchChoiceView: View {
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
@EnvironmentObject var debridManager: DebridManager
|
||||
@EnvironmentObject var scrapingModel: ScrapingViewModel
|
||||
|
|
@ -25,19 +25,22 @@ struct BatchChoiceView: View {
|
|||
Task {
|
||||
await debridManager.fetchRdDownload(searchResult: searchResult, iaFile: file)
|
||||
|
||||
// The download may complete before this sheet dismisses
|
||||
try? await Task.sleep(seconds: 1)
|
||||
navModel.runDebridAction(action: nil, urlString: debridManager.realDebridDownloadUrl)
|
||||
if !debridManager.realDebridDownloadUrl.isEmpty {
|
||||
// The download may complete before this sheet dismisses
|
||||
try? await Task.sleep(seconds: 1)
|
||||
navModel.runDebridAction(action: nil, urlString: debridManager.realDebridDownloadUrl)
|
||||
}
|
||||
|
||||
debridManager.selectedRealDebridFile = nil
|
||||
debridManager.selectedRealDebridItem = nil
|
||||
}
|
||||
}
|
||||
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.insetGrouped)
|
||||
.navigationTitle("Select a file")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
|
|
@ -45,7 +48,7 @@ struct BatchChoiceView: View {
|
|||
Button("Done") {
|
||||
debridManager.selectedRealDebridItem = nil
|
||||
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
22
Ferrite/Views/CommonViews/DynamicAccentColor.swift
Normal file
22
Ferrite/Views/CommonViews/DynamicAccentColor.swift
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// dynamicAccentColor.swift
|
||||
// Ferrite
|
||||
//
|
||||
// Created by Brian Dashore on 8/15/22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct DynamicAccentColor: ViewModifier {
|
||||
let color: Color
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
if #available(iOS 15, *) {
|
||||
content
|
||||
.tint(color)
|
||||
} else {
|
||||
content
|
||||
.accentColor(color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ struct ErrorGroupBoxStyle: GroupBoxStyle {
|
|||
configuration.content
|
||||
}
|
||||
.padding(10)
|
||||
.background(Color(uiColor: .secondarySystemGroupedBackground))
|
||||
.background(Color(UIColor.secondarySystemGroupedBackground))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
// Created by Brian Dashore on 7/1/22.
|
||||
//
|
||||
|
||||
import ActivityView
|
||||
import SwiftUI
|
||||
import SwiftUIX
|
||||
|
||||
struct ContentView: View {
|
||||
@EnvironmentObject var scrapingModel: ScrapingViewModel
|
||||
|
|
@ -66,38 +66,63 @@ struct ContentView: View {
|
|||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 5)
|
||||
.padding(.horizontal, 20)
|
||||
|
||||
SearchResultsView()
|
||||
}
|
||||
.searchable(text: $scrapingModel.searchText)
|
||||
.onSubmit(of: .search) {
|
||||
scrapingModel.runningSearchTask = Task {
|
||||
navModel.showSearchProgress = true
|
||||
|
||||
await scrapingModel.scanSources(sources: sources.compactMap { $0 })
|
||||
|
||||
if realDebridEnabled, !scrapingModel.searchResults.isEmpty {
|
||||
await debridManager.populateDebridHashes(scrapingModel.searchResults)
|
||||
.sheet(item: $navModel.currentChoiceSheet) { item in
|
||||
Group {
|
||||
switch item {
|
||||
case .magnet:
|
||||
MagnetChoiceView()
|
||||
.environmentObject(debridManager)
|
||||
.environmentObject(scrapingModel)
|
||||
.environmentObject(navModel)
|
||||
case .batch:
|
||||
BatchChoiceView()
|
||||
.environmentObject(debridManager)
|
||||
.environmentObject(scrapingModel)
|
||||
.environmentObject(navModel)
|
||||
}
|
||||
|
||||
navModel.showSearchProgress = false
|
||||
}
|
||||
.dynamicAccentColor(.primary)
|
||||
}
|
||||
.sheet(isPresented: $navModel.showActivityView) {
|
||||
if #available(iOS 16, *) {
|
||||
AppActivityView(activityItems: navModel.activityItems)
|
||||
.presentationDetents([.medium])
|
||||
} else {
|
||||
AppActivityView(activityItems: navModel.activityItems)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Search")
|
||||
}
|
||||
.sheet(item: $navModel.currentChoiceSheet) { item in
|
||||
Group {
|
||||
switch item {
|
||||
case .magnet:
|
||||
MagnetChoiceView()
|
||||
case .batch:
|
||||
BatchChoiceView()
|
||||
}
|
||||
.navigationSearchBar {
|
||||
SearchBar("Search", text: $scrapingModel.searchText, isEditing: $navModel.isEditingSearch,
|
||||
onCommit: {
|
||||
scrapingModel.runningSearchTask = Task {
|
||||
navModel.isSearching = true
|
||||
navModel.showSearchProgress = true
|
||||
|
||||
await scrapingModel.scanSources(sources: sources.compactMap { $0 })
|
||||
|
||||
if realDebridEnabled, !scrapingModel.searchResults.isEmpty {
|
||||
await debridManager.populateDebridHashes(scrapingModel.searchResults)
|
||||
}
|
||||
|
||||
navModel.showSearchProgress = false
|
||||
}
|
||||
})
|
||||
.showsCancelButton(navModel.isEditingSearch || navModel.isSearching)
|
||||
.onCancel {
|
||||
scrapingModel.searchResults = []
|
||||
scrapingModel.runningSearchTask?.cancel()
|
||||
scrapingModel.runningSearchTask = nil
|
||||
navModel.isSearching = false
|
||||
scrapingModel.searchText = ""
|
||||
}
|
||||
}
|
||||
.tint(.primary)
|
||||
}
|
||||
.activitySheet($navModel.currentActivityItem)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@
|
|||
import SwiftUI
|
||||
|
||||
struct LoginWebView: View {
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
var url: URL
|
||||
|
||||
var body: some View {
|
||||
NavView {
|
||||
WebView(url: url)
|
||||
|
|
@ -18,7 +19,7 @@ struct LoginWebView: View {
|
|||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("Done") {
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@
|
|||
// Created by Brian Dashore on 7/20/22.
|
||||
//
|
||||
|
||||
import ActivityView
|
||||
import SwiftUI
|
||||
import SwiftUIX
|
||||
|
||||
struct MagnetChoiceView: View {
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
@EnvironmentObject var scrapingModel: ScrapingViewModel
|
||||
@EnvironmentObject var debridManager: DebridManager
|
||||
|
|
@ -20,13 +20,13 @@ struct MagnetChoiceView: View {
|
|||
@State private var showActivityView = false
|
||||
@State private var showLinkCopyAlert = false
|
||||
@State private var showMagnetCopyAlert = false
|
||||
@State private var activityItem: ActivityItem?
|
||||
@State private var activityItems: [Any] = []
|
||||
|
||||
var body: some View {
|
||||
NavView {
|
||||
Form {
|
||||
if realDebridEnabled, debridManager.matchSearchResult(result: scrapingModel.selectedSearchResult) != .none {
|
||||
Section("Real Debrid options") {
|
||||
Section(header: "Real Debrid options") {
|
||||
ListRowButtonView("Play on Outplayer", systemImage: "arrow.up.forward.app.fill") {
|
||||
navModel.runDebridAction(action: .outplayer, urlString: debridManager.realDebridDownloadUrl)
|
||||
}
|
||||
|
|
@ -52,16 +52,15 @@ struct MagnetChoiceView: View {
|
|||
}
|
||||
|
||||
ListRowButtonView("Share download URL", systemImage: "square.and.arrow.up.fill") {
|
||||
guard let url = URL(string: debridManager.realDebridDownloadUrl) else {
|
||||
return
|
||||
if let url = URL(string: debridManager.realDebridDownloadUrl) {
|
||||
activityItems = [url]
|
||||
navModel.showActivityView.toggle()
|
||||
}
|
||||
|
||||
activityItem = ActivityItem(items: url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section("Magnet options") {
|
||||
Section(header: "Magnet options") {
|
||||
ListRowButtonView("Copy magnet", systemImage: "doc.on.doc.fill") {
|
||||
UIPasteboard.general.string = scrapingModel.selectedSearchResult?.magnetLink
|
||||
showMagnetCopyAlert.toggle()
|
||||
|
|
@ -76,7 +75,8 @@ struct MagnetChoiceView: View {
|
|||
|
||||
ListRowButtonView("Share magnet", systemImage: "square.and.arrow.up.fill") {
|
||||
if let result = scrapingModel.selectedSearchResult, let url = URL(string: result.magnetLink) {
|
||||
activityItem = ActivityItem(items: url)
|
||||
activityItems = [url]
|
||||
navModel.showActivityView.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +87,14 @@ struct MagnetChoiceView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.activitySheet($activityItem)
|
||||
.sheet(isPresented: $navModel.showActivityView) {
|
||||
if #available(iOS 16, *) {
|
||||
AppActivityView(activityItems: activityItems)
|
||||
.presentationDetents([.medium])
|
||||
} else {
|
||||
AppActivityView(activityItems: activityItems)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Link actions")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
|
|
@ -95,7 +102,7 @@ struct MagnetChoiceView: View {
|
|||
Button("Done") {
|
||||
debridManager.realDebridDownloadUrl = ""
|
||||
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ struct MainView: View {
|
|||
}
|
||||
}
|
||||
.font(.caption)
|
||||
.shadow(radius: 10)
|
||||
.animation(.easeInOut(duration: 0.3), value: toastModel.showToast)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,6 @@
|
|||
import SwiftUI
|
||||
|
||||
struct SearchResultsView: View {
|
||||
@Environment(\.isSearching) var isSearching
|
||||
@Environment(\.dismissSearch) var dismissSearch
|
||||
|
||||
@EnvironmentObject var scrapingModel: ScrapingViewModel
|
||||
@EnvironmentObject var debridManager: DebridManager
|
||||
@EnvironmentObject var navModel: NavigationViewModel
|
||||
|
|
@ -29,7 +26,10 @@ struct SearchResultsView: View {
|
|||
case .full:
|
||||
Task {
|
||||
await debridManager.fetchRdDownload(searchResult: result)
|
||||
navModel.runDebridAction(action: nil, urlString: debridManager.realDebridDownloadUrl)
|
||||
|
||||
if !debridManager.realDebridDownloadUrl.isEmpty {
|
||||
navModel.runDebridAction(action: nil, urlString: debridManager.realDebridDownloadUrl)
|
||||
}
|
||||
}
|
||||
case .partial:
|
||||
if debridManager.setSelectedRdResult(result: result) {
|
||||
|
|
@ -43,7 +43,7 @@ struct SearchResultsView: View {
|
|||
.font(.callout)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
.tint(.primary)
|
||||
.dynamicAccentColor(.primary)
|
||||
.padding(.bottom, 5)
|
||||
|
||||
SearchResultRDView(result: result)
|
||||
|
|
@ -51,6 +51,7 @@ struct SearchResultsView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.insetGrouped)
|
||||
.overlay {
|
||||
if scrapingModel.searchResults.isEmpty {
|
||||
if navModel.showSearchProgress {
|
||||
|
|
@ -58,7 +59,7 @@ struct SearchResultsView: View {
|
|||
ProgressView()
|
||||
Text("Loading \(scrapingModel.currentSourceName ?? "")")
|
||||
}
|
||||
} else if isSearching, scrapingModel.runningSearchTask != nil {
|
||||
} else if navModel.isSearching, scrapingModel.runningSearchTask != nil {
|
||||
Text("No results found")
|
||||
}
|
||||
}
|
||||
|
|
@ -66,25 +67,19 @@ struct SearchResultsView: View {
|
|||
.onChange(of: navModel.selectedTab) { tab in
|
||||
// Cancel the search if tab is switched while search is in progress
|
||||
if tab != .search, navModel.showSearchProgress {
|
||||
scrapingModel.searchResults = []
|
||||
scrapingModel.runningSearchTask?.cancel()
|
||||
scrapingModel.runningSearchTask = nil
|
||||
dismissSearch()
|
||||
navModel.isSearching = false
|
||||
scrapingModel.searchText = ""
|
||||
}
|
||||
}
|
||||
.onChange(of: scrapingModel.searchResults) { _ in
|
||||
// Cleans up any leftover search results in the event of an abrupt cancellation
|
||||
if !isSearching {
|
||||
if !navModel.isSearching {
|
||||
scrapingModel.searchResults = []
|
||||
}
|
||||
}
|
||||
.onChange(of: isSearching) { changed in
|
||||
// Clear the results array and cleans up search tasks on cancel
|
||||
if !changed {
|
||||
scrapingModel.searchResults = []
|
||||
scrapingModel.runningSearchTask?.cancel()
|
||||
scrapingModel.runningSearchTask = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ struct SettingsView: View {
|
|||
var body: some View {
|
||||
NavView {
|
||||
Form {
|
||||
Section("Debrid services") {
|
||||
Section(header: "Debrid services") {
|
||||
HStack {
|
||||
Text("Real Debrid")
|
||||
Spacer()
|
||||
|
|
@ -42,11 +42,11 @@ struct SettingsView: View {
|
|||
}
|
||||
}
|
||||
|
||||
Section("Source management") {
|
||||
Section(header: "Source management") {
|
||||
NavigationLink("Source lists", destination: SettingsSourceListView())
|
||||
}
|
||||
|
||||
Section("Default actions") {
|
||||
Section(header: "Default actions") {
|
||||
if realDebridEnabled {
|
||||
NavigationLink(
|
||||
destination: DebridActionPickerView(),
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
import SwiftUI
|
||||
|
||||
struct SourceListEditorView: View {
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
@EnvironmentObject var navModel: NavigationViewModel
|
||||
@EnvironmentObject var sourceManager: SourceManager
|
||||
|
|
@ -42,7 +42,7 @@ struct SourceListEditorView: View {
|
|||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Cancel") {
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ struct SourceListEditorView: View {
|
|||
sourceUrl: sourceUrl,
|
||||
existingSourceList: navModel.selectedSourceList
|
||||
) {
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,15 +8,15 @@
|
|||
import SwiftUI
|
||||
|
||||
struct SourceSettingsView: View {
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
@EnvironmentObject var navModel: NavigationViewModel
|
||||
|
||||
var body: some View {
|
||||
NavView {
|
||||
Form {
|
||||
List {
|
||||
if let selectedSource = navModel.selectedSource {
|
||||
Section("Info") {
|
||||
Section(header: "Info") {
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
HStack {
|
||||
Text(selectedSource.name)
|
||||
|
|
@ -53,6 +53,7 @@ struct SourceSettingsView: View {
|
|||
SourceSettingsMethodView(selectedSource: selectedSource)
|
||||
}
|
||||
}
|
||||
.listStyle(.insetGrouped)
|
||||
.onDisappear {
|
||||
PersistenceController.shared.save()
|
||||
}
|
||||
|
|
@ -60,7 +61,7 @@ struct SourceSettingsView: View {
|
|||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("Done") {
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -71,29 +72,25 @@ struct SourceSettingsView: View {
|
|||
struct SourceSettingsBaseUrlView: View {
|
||||
@ObservedObject var selectedSource: Source
|
||||
|
||||
@FocusState var baseUrlFocused: Bool
|
||||
|
||||
@State private var tempBaseUrl: String = ""
|
||||
var body: some View {
|
||||
Section(
|
||||
header: Text("Base URL"),
|
||||
footer: Text("Enter the base URL of your server.")
|
||||
) {
|
||||
TextField("https://...", text: $tempBaseUrl)
|
||||
.keyboardType(.URL)
|
||||
.focused($baseUrlFocused)
|
||||
.onChange(of: baseUrlFocused) { isFocused in
|
||||
if !isFocused {
|
||||
if tempBaseUrl.last == "/" {
|
||||
selectedSource.baseUrl = String(tempBaseUrl.dropLast())
|
||||
} else {
|
||||
selectedSource.baseUrl = tempBaseUrl
|
||||
}
|
||||
TextField("https://...", text: $tempBaseUrl, onEditingChanged: { isFocused in
|
||||
if !isFocused {
|
||||
if tempBaseUrl.last == "/" {
|
||||
selectedSource.baseUrl = String(tempBaseUrl.dropLast())
|
||||
} else {
|
||||
selectedSource.baseUrl = tempBaseUrl
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
tempBaseUrl = selectedSource.baseUrl ?? ""
|
||||
}
|
||||
})
|
||||
.keyboardType(.URL)
|
||||
.onAppear {
|
||||
tempBaseUrl = selectedSource.baseUrl ?? ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -101,9 +98,6 @@ struct SourceSettingsBaseUrlView: View {
|
|||
struct SourceSettingsApiView: View {
|
||||
@ObservedObject var selectedSourceApi: SourceApi
|
||||
|
||||
@FocusState var clientIdFieldFocused: Bool
|
||||
@FocusState var tokenFieldFocused: Bool
|
||||
|
||||
@State private var tempClientId: String = ""
|
||||
@State private var tempClientSecret: String = ""
|
||||
|
||||
|
|
@ -117,31 +111,27 @@ struct SourceSettingsApiView: View {
|
|||
footer: Text("Grab the required API credentials from the website. A client secret can be an API token.")
|
||||
) {
|
||||
if selectedSourceApi.dynamicClientId {
|
||||
TextField("Client ID", text: $tempClientId)
|
||||
.textInputAutocapitalization(.never)
|
||||
.focused($clientIdFieldFocused)
|
||||
.onChange(of: clientIdFieldFocused) { isFocused in
|
||||
if !isFocused {
|
||||
selectedSourceApi.clientId = tempClientId
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
tempClientId = selectedSourceApi.clientId ?? ""
|
||||
TextField("Client ID", text: $tempClientId, onEditingChanged: { isFocused in
|
||||
if !isFocused {
|
||||
selectedSourceApi.clientId = tempClientId
|
||||
}
|
||||
})
|
||||
.autocapitalization(.none)
|
||||
.onAppear {
|
||||
tempClientId = selectedSourceApi.clientId ?? ""
|
||||
}
|
||||
}
|
||||
|
||||
if selectedSourceApi.clientSecret != nil {
|
||||
TextField("Token", text: $tempClientSecret)
|
||||
.textInputAutocapitalization(.never)
|
||||
.focused($tokenFieldFocused)
|
||||
.onChange(of: clientIdFieldFocused) { isFocused in
|
||||
if !isFocused {
|
||||
selectedSourceApi.clientSecret = tempClientSecret
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
tempClientSecret = selectedSourceApi.clientSecret ?? ""
|
||||
TextField("Token", text: $tempClientSecret, onEditingChanged: { isFocused in
|
||||
if !isFocused {
|
||||
selectedSourceApi.clientSecret = tempClientSecret
|
||||
}
|
||||
})
|
||||
.autocapitalization(.none)
|
||||
.onAppear {
|
||||
tempClientSecret = selectedSourceApi.clientSecret ?? ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,11 +36,13 @@ struct SourcesView: View {
|
|||
return tempSources
|
||||
}
|
||||
|
||||
@State private var viewTask: Task<Void, Never>? = nil
|
||||
|
||||
var body: some View {
|
||||
NavView {
|
||||
List {
|
||||
if !updatedSources.isEmpty {
|
||||
Section("Updates") {
|
||||
Section(header: "Updates") {
|
||||
ForEach(updatedSources, id: \.self) { source in
|
||||
SourceUpdateButtonView(updatedSource: source)
|
||||
}
|
||||
|
|
@ -48,13 +50,10 @@ struct SourcesView: View {
|
|||
}
|
||||
|
||||
if !sources.isEmpty {
|
||||
Section("Installed") {
|
||||
Section(header: "Installed") {
|
||||
ForEach(sources, id: \.self) { source in
|
||||
InstalledSourceView(installedSource: source)
|
||||
}
|
||||
.sheet(isPresented: $navModel.showSourceSettings) {
|
||||
SourceSettingsView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +66,7 @@ struct SourcesView: View {
|
|||
}
|
||||
)
|
||||
}) {
|
||||
Section("Catalog") {
|
||||
Section(header: "Catalog") {
|
||||
ForEach(sourceManager.availableSources, id: \.self) { availableSource in
|
||||
if !sources.contains(
|
||||
where: {
|
||||
|
|
@ -82,8 +81,18 @@ struct SourcesView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.task {
|
||||
await sourceManager.fetchSourcesFromUrl()
|
||||
.listStyle(.insetGrouped)
|
||||
.sheet(isPresented: $navModel.showSourceSettings) {
|
||||
SourceSettingsView()
|
||||
.environmentObject(navModel)
|
||||
}
|
||||
.onAppear {
|
||||
viewTask = Task {
|
||||
await sourceManager.fetchSourcesFromUrl()
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
viewTask?.cancel()
|
||||
}
|
||||
.navigationTitle("Sources")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,7 @@ I also wanted to support the use of RealDebrid since there aren't any (free) opt
|
|||
|
||||
## What iOS versions are supported?
|
||||
|
||||
iOS 15 and up. This is because SwiftUI is very flexible with these versions. I have no intention of backporting Ferrite to iOS 14, so please update if you want to use it.
|
||||
|
||||
However, if you want an immersive browser for your websites with iOS 14 support, please look at my sister app Asobi.
|
||||
iOS 14 and up. I was able to successfully backport the app!
|
||||
|
||||
## Planned features
|
||||
|
||||
|
|
|
|||
124
Sources.md
124
Sources.md
|
|
@ -1,124 +0,0 @@
|
|||
# Sources
|
||||
|
||||
This is a tentative guide for Ferrite sources. The source format can change at any time without warning throughout the duration of the alpha so do not jump the gun if you don't want to.
|
||||
|
||||
## Source lists
|
||||
|
||||
Source lists must adhere to the following template. All of these fields are `required` to properly add a source to Ferrite.
|
||||
|
||||
(Note: Name and author fields are not enforced, but they will be in future versions of Ferrite)
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Repository name",
|
||||
"author": "Repository author",
|
||||
"sources": ["source objects go here"]
|
||||
}
|
||||
```
|
||||
|
||||
## Creating a source object
|
||||
|
||||
Here is a quick template to what a source entry looks like with all the possible parameters. You can copy/paste this template into your editor of choice. I will go through all the keys one-by-one.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Website (source) name",
|
||||
"version": "1",
|
||||
"baseUrl": "https://sourceurl.com",
|
||||
"htmlParser": {
|
||||
"searchUrl": "?q={query}",
|
||||
"rows": "row selector",
|
||||
"magnet": {
|
||||
"query": "magnet selector",
|
||||
"externalLinkQuery": "https://sourceurl.com/magnetUrl"
|
||||
"attribute": "href",
|
||||
"regex": "regex"
|
||||
},
|
||||
"title": {
|
||||
"query": "title selector",
|
||||
"attribute": "text",
|
||||
"regex": "regex"
|
||||
},
|
||||
"size": {
|
||||
"query": "size selector",
|
||||
"attribute": "text",
|
||||
"regex": "regex"
|
||||
},
|
||||
"sl": {
|
||||
"seeders": "seeder selector",
|
||||
"leechers": "leecher selector",
|
||||
"combined": "seeder/leecher ratio selector",
|
||||
"seederRegex": "regex",
|
||||
"leecherRegex": "regex"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### name
|
||||
|
||||
`Required`: This is the name that is shown when the user is looking at a source
|
||||
|
||||
### version
|
||||
|
||||
`Required`: This is the version number of the source. Each update to the source increments the version by 1. Only increment the version when you are sure that the source is ready to be published.
|
||||
|
||||
### baseUrl
|
||||
|
||||
`Required`: The base URL of the website. For example, `https://google.com` is the base URL of Google. DO NOT include the slash on the end of the base URL otherwise the source will break.
|
||||
|
||||
### htmlParser
|
||||
|
||||
`Optional`: The web scraping module for a source. Use this if a source does not have an API and allows scraping! (NOTE: API support will be added in a future build of Ferrite, use the htmlParser for now)
|
||||
|
||||
### searchUrl
|
||||
|
||||
`Required for htmlParser`: The URL given when searching content on a website. For example, when given a URL such as `https://www.google.com/search?q=hello`, the search URL is whatever comes after the base URL (in this case `/search?q=hello`). It is important to include the slash at the beginning otherwise the source will break.
|
||||
|
||||
### rows
|
||||
|
||||
`Required for htmlParser`: The CSS selector for selecting a table row. Most of these sites use HTML tables. Please consult this while web scraping.
|
||||
|
||||
### magnet
|
||||
|
||||
`Required for htmlParser`: A complex query. Please reference complex queries to understand the other keys. Unique keys are provided below:
|
||||
|
||||
- externalLinkQuery: If a magnet link is located on a different page, this fetches the URL required to navigate to that page and fetch the magnet link.
|
||||
|
||||
### title
|
||||
|
||||
`Optional for htmlParser`: This is a complex query. Please reference complex queries to understand the keys
|
||||
|
||||
### size
|
||||
|
||||
`Optional for htmlParser`: This is a complex query. Please reference complex queries to understand the keys
|
||||
|
||||
### sl (seeders and leechers)
|
||||
|
||||
`Optional for htmlParser`: Used to get seeder and leecher values on a website. All the below properties are optional.
|
||||
|
||||
- seeders: The seeder CSS selector
|
||||
|
||||
- leechers: The leecher CSS selector
|
||||
|
||||
- combined: A CSS selector used when seeders and leechers are in one string (ex. `Seeders: 100 / Leechers: 200`)
|
||||
|
||||
- seederRegex: Regex used to strip the seeder value from a string (follows the same rules as complex query regexes)
|
||||
|
||||
- leecherRegex: Regex used to strip the leecher value from a string (follows the same rules as complex query regexes)
|
||||
|
||||
## Complex queries
|
||||
|
||||
These are generic queries used by Ferrite for keys that require a little more information when parsing the contents. Any key that has a complex query disclaimer will always use these parameters:
|
||||
|
||||
- query `Required`: The CSS selector for selecting the element in question
|
||||
|
||||
- attribute `Required`: The attribute to look for after selecting the query (ex. href, title, span). If you want the textContent, use `text` in the attribute parameter.
|
||||
|
||||
- regex `Optional`: Runs regex on the query result before presentation to the user.
|
||||
|
||||
- Do not include the beginning and end slashes in this string (ex. `/regex/`)
|
||||
|
||||
- When using a `\` character, escape it using `\\`
|
||||
|
||||
- This regex must have only one capturing group. [Don't know what a capture group is?](https://www.regular-expressions.info/brackets.html)
|
||||
Loading…
Reference in a new issue