mirror of
https://github.com/Ferrite-iOS/Ferrite.git
synced 2026-04-21 00:42:07 +00:00
Library: Add history functionality
Action history is logged and displayed to the user's library. These are triggered whenever the magnet choice sheet is displayed. Also redo alerts and action sheets to avoid deprecation notices for >iOS 14. These will be removed when iOS 14 support is dropped. There was also a problem with sheet presentation not working after a sheet was dismissed. Disable the appropriate view when a sheet is being presented. Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
parent
2f870b9410
commit
4d3a16f77e
25 changed files with 823 additions and 172 deletions
|
|
@ -10,19 +10,24 @@
|
||||||
0C0D50E5288DFE7F0035ECC8 /* SourceModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D50E4288DFE7F0035ECC8 /* SourceModels.swift */; };
|
0C0D50E5288DFE7F0035ECC8 /* SourceModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D50E4288DFE7F0035ECC8 /* SourceModels.swift */; };
|
||||||
0C0D50E7288DFF850035ECC8 /* SourcesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D50E6288DFF850035ECC8 /* SourcesView.swift */; };
|
0C0D50E7288DFF850035ECC8 /* SourcesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D50E6288DFF850035ECC8 /* SourcesView.swift */; };
|
||||||
0C10848B28BD9A38008F0BA6 /* SettingsAppVersionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C10848A28BD9A38008F0BA6 /* SettingsAppVersionView.swift */; };
|
0C10848B28BD9A38008F0BA6 /* SettingsAppVersionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C10848A28BD9A38008F0BA6 /* SettingsAppVersionView.swift */; };
|
||||||
|
0C12D43D28CC332A000195BF /* HistoryButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C12D43C28CC332A000195BF /* HistoryButtonView.swift */; };
|
||||||
0C31133C28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C31133A28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift */; };
|
0C31133C28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C31133A28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift */; };
|
||||||
0C31133D28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C31133B28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift */; };
|
0C31133D28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C31133B28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift */; };
|
||||||
0C32FB532890D19D002BD219 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32FB522890D19D002BD219 /* AboutView.swift */; };
|
0C32FB532890D19D002BD219 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32FB522890D19D002BD219 /* AboutView.swift */; };
|
||||||
0C32FB552890D1BF002BD219 /* UIApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32FB542890D1BF002BD219 /* UIApplication.swift */; };
|
0C32FB552890D1BF002BD219 /* UIApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32FB542890D1BF002BD219 /* UIApplication.swift */; };
|
||||||
0C32FB572890D1F2002BD219 /* ListRowViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32FB562890D1F2002BD219 /* ListRowViews.swift */; };
|
0C32FB572890D1F2002BD219 /* ListRowViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32FB562890D1F2002BD219 /* ListRowViews.swift */; };
|
||||||
0C360C5C28C7DF1400884ED3 /* DynamicFetchRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C360C5B28C7DF1400884ED3 /* DynamicFetchRequest.swift */; };
|
0C360C5C28C7DF1400884ED3 /* DynamicFetchRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C360C5B28C7DF1400884ED3 /* DynamicFetchRequest.swift */; };
|
||||||
|
0C391EC928CA63F0009F1CA1 /* DynamicActionSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C391EC828CA63F0009F1CA1 /* DynamicActionSheet.swift */; };
|
||||||
|
0C391ECB28CAA44B009F1CA1 /* AlertButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C391ECA28CAA44B009F1CA1 /* AlertButton.swift */; };
|
||||||
0C41BC6328C2AD0F00B47DD6 /* SearchResultButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C41BC6228C2AD0F00B47DD6 /* SearchResultButtonView.swift */; };
|
0C41BC6328C2AD0F00B47DD6 /* SearchResultButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C41BC6228C2AD0F00B47DD6 /* SearchResultButtonView.swift */; };
|
||||||
0C41BC6528C2AEB900B47DD6 /* SearchModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C41BC6428C2AEB900B47DD6 /* SearchModels.swift */; };
|
0C41BC6528C2AEB900B47DD6 /* SearchModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C41BC6428C2AEB900B47DD6 /* SearchModels.swift */; };
|
||||||
0C4CFC462897030D00AD9FAD /* Regex in Frameworks */ = {isa = PBXBuildFile; productRef = 0C4CFC452897030D00AD9FAD /* Regex */; };
|
0C4CFC462897030D00AD9FAD /* Regex in Frameworks */ = {isa = PBXBuildFile; productRef = 0C4CFC452897030D00AD9FAD /* Regex */; };
|
||||||
0C4CFC4D28970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C4CFC4728970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift */; };
|
0C4CFC4D28970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C4CFC4728970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift */; };
|
||||||
0C4CFC4E28970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C4CFC4828970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift */; };
|
0C4CFC4E28970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C4CFC4828970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift */; };
|
||||||
|
0C54D36328C5086E00BFEEE2 /* History+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C54D36128C5086E00BFEEE2 /* History+CoreDataClass.swift */; };
|
||||||
|
0C54D36428C5086E00BFEEE2 /* History+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C54D36228C5086E00BFEEE2 /* History+CoreDataProperties.swift */; };
|
||||||
0C57D4CC289032ED008534E8 /* SearchResultRDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C57D4CB289032ED008534E8 /* SearchResultRDView.swift */; };
|
0C57D4CC289032ED008534E8 /* SearchResultRDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C57D4CB289032ED008534E8 /* SearchResultRDView.swift */; };
|
||||||
0C60B1EF28A1A00000E3FD7E /* SearchProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C60B1EE28A1A00000E3FD7E /* SearchProgressView.swift */; };
|
0C626A9528CADB25003C7129 /* DynamicAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C626A9428CADB25003C7129 /* DynamicAlert.swift */; };
|
||||||
0C64A4B4288903680079976D /* Base32 in Frameworks */ = {isa = PBXBuildFile; productRef = 0C64A4B3288903680079976D /* Base32 */; };
|
0C64A4B4288903680079976D /* Base32 in Frameworks */ = {isa = PBXBuildFile; productRef = 0C64A4B3288903680079976D /* Base32 */; };
|
||||||
0C64A4B7288903880079976D /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 0C64A4B6288903880079976D /* KeychainSwift */; };
|
0C64A4B7288903880079976D /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 0C64A4B6288903880079976D /* KeychainSwift */; };
|
||||||
0C68135028BC1A2D00FAD890 /* GithubWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C68134F28BC1A2D00FAD890 /* GithubWrapper.swift */; };
|
0C68135028BC1A2D00FAD890 /* GithubWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C68134F28BC1A2D00FAD890 /* GithubWrapper.swift */; };
|
||||||
|
|
@ -80,14 +85,18 @@
|
||||||
0CA3B23C28C2AA5600616D3A /* Bookmark+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA3B23A28C2AA5600616D3A /* Bookmark+CoreDataClass.swift */; };
|
0CA3B23C28C2AA5600616D3A /* Bookmark+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA3B23A28C2AA5600616D3A /* Bookmark+CoreDataClass.swift */; };
|
||||||
0CA3B23D28C2AA5600616D3A /* Bookmark+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA3B23B28C2AA5600616D3A /* Bookmark+CoreDataProperties.swift */; };
|
0CA3B23D28C2AA5600616D3A /* Bookmark+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA3B23B28C2AA5600616D3A /* Bookmark+CoreDataProperties.swift */; };
|
||||||
0CA3FB2028B91D9500FA10A8 /* IndeterminateProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA3FB1F28B91D9500FA10A8 /* IndeterminateProgressView.swift */; };
|
0CA3FB2028B91D9500FA10A8 /* IndeterminateProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA3FB1F28B91D9500FA10A8 /* IndeterminateProgressView.swift */; };
|
||||||
|
0CA429F828C5098D000D0610 /* DateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA429F728C5098D000D0610 /* DateFormatter.swift */; };
|
||||||
0CAF1C7B286F5C8600296F86 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = 0CAF1C7A286F5C8600296F86 /* SwiftSoup */; };
|
0CAF1C7B286F5C8600296F86 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = 0CAF1C7A286F5C8600296F86 /* SwiftSoup */; };
|
||||||
0CB6516328C5A57300DCA721 /* ConditionalId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB6516228C5A57300DCA721 /* ConditionalId.swift */; };
|
0CB6516328C5A57300DCA721 /* ConditionalId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB6516228C5A57300DCA721 /* ConditionalId.swift */; };
|
||||||
0CB6516528C5A5D700DCA721 /* InlinedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB6516428C5A5D700DCA721 /* InlinedList.swift */; };
|
0CB6516528C5A5D700DCA721 /* InlinedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB6516428C5A5D700DCA721 /* InlinedList.swift */; };
|
||||||
0CB6516828C5A5EC00DCA721 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 0CB6516728C5A5EC00DCA721 /* Introspect */; };
|
0CB6516828C5A5EC00DCA721 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 0CB6516728C5A5EC00DCA721 /* Introspect */; };
|
||||||
0CB6516A28C5B4A600DCA721 /* InlineHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB6516928C5B4A600DCA721 /* InlineHeader.swift */; };
|
0CB6516A28C5B4A600DCA721 /* InlineHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB6516928C5B4A600DCA721 /* InlineHeader.swift */; };
|
||||||
|
0CBAB83628D12ED500AC903E /* DisableInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBAB83528D12ED500AC903E /* DisableInteraction.swift */; };
|
||||||
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 */; };
|
||||||
|
0CD4CAC628C980EB0046E1DC /* HistoryActionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD4CAC528C980EB0046E1DC /* HistoryActionsView.swift */; };
|
||||||
|
0CD5E78928CD932B001BF684 /* DisabledAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD5E78828CD932B001BF684 /* DisabledAppearance.swift */; };
|
||||||
0CDCB91828C662640098B513 /* EmptyInstructionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CDCB91728C662640098B513 /* EmptyInstructionView.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 */
|
||||||
|
|
@ -96,18 +105,23 @@
|
||||||
0C0D50E4288DFE7F0035ECC8 /* SourceModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceModels.swift; sourceTree = "<group>"; };
|
0C0D50E4288DFE7F0035ECC8 /* SourceModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceModels.swift; sourceTree = "<group>"; };
|
||||||
0C0D50E6288DFF850035ECC8 /* SourcesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourcesView.swift; sourceTree = "<group>"; };
|
0C0D50E6288DFF850035ECC8 /* SourcesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourcesView.swift; sourceTree = "<group>"; };
|
||||||
0C10848A28BD9A38008F0BA6 /* SettingsAppVersionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAppVersionView.swift; sourceTree = "<group>"; };
|
0C10848A28BD9A38008F0BA6 /* SettingsAppVersionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAppVersionView.swift; sourceTree = "<group>"; };
|
||||||
|
0C12D43C28CC332A000195BF /* HistoryButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryButtonView.swift; sourceTree = "<group>"; };
|
||||||
0C31133A28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceJsonParser+CoreDataClass.swift"; sourceTree = "<group>"; };
|
0C31133A28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceJsonParser+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||||
0C31133B28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceJsonParser+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
0C31133B28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceJsonParser+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||||
0C32FB522890D19D002BD219 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
|
0C32FB522890D19D002BD219 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
|
||||||
0C32FB542890D1BF002BD219 /* UIApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplication.swift; sourceTree = "<group>"; };
|
0C32FB542890D1BF002BD219 /* UIApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplication.swift; sourceTree = "<group>"; };
|
||||||
0C32FB562890D1F2002BD219 /* ListRowViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRowViews.swift; sourceTree = "<group>"; };
|
0C32FB562890D1F2002BD219 /* ListRowViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRowViews.swift; sourceTree = "<group>"; };
|
||||||
0C360C5B28C7DF1400884ED3 /* DynamicFetchRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicFetchRequest.swift; sourceTree = "<group>"; };
|
0C360C5B28C7DF1400884ED3 /* DynamicFetchRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicFetchRequest.swift; sourceTree = "<group>"; };
|
||||||
|
0C391EC828CA63F0009F1CA1 /* DynamicActionSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicActionSheet.swift; sourceTree = "<group>"; };
|
||||||
|
0C391ECA28CAA44B009F1CA1 /* AlertButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertButton.swift; sourceTree = "<group>"; };
|
||||||
0C41BC6228C2AD0F00B47DD6 /* SearchResultButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultButtonView.swift; sourceTree = "<group>"; };
|
0C41BC6228C2AD0F00B47DD6 /* SearchResultButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultButtonView.swift; sourceTree = "<group>"; };
|
||||||
0C41BC6428C2AEB900B47DD6 /* SearchModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchModels.swift; sourceTree = "<group>"; };
|
0C41BC6428C2AEB900B47DD6 /* SearchModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchModels.swift; sourceTree = "<group>"; };
|
||||||
0C4CFC4728970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceComplexQuery+CoreDataClass.swift"; sourceTree = "<group>"; };
|
0C4CFC4728970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceComplexQuery+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||||
0C4CFC4828970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceComplexQuery+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
0C4CFC4828970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceComplexQuery+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||||
|
0C54D36128C5086E00BFEEE2 /* History+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "History+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||||
|
0C54D36228C5086E00BFEEE2 /* History+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "History+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||||
0C57D4CB289032ED008534E8 /* SearchResultRDView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultRDView.swift; sourceTree = "<group>"; };
|
0C57D4CB289032ED008534E8 /* SearchResultRDView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultRDView.swift; sourceTree = "<group>"; };
|
||||||
0C60B1EE28A1A00000E3FD7E /* SearchProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchProgressView.swift; sourceTree = "<group>"; };
|
0C626A9428CADB25003C7129 /* DynamicAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicAlert.swift; sourceTree = "<group>"; };
|
||||||
0C68134F28BC1A2D00FAD890 /* GithubWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubWrapper.swift; sourceTree = "<group>"; };
|
0C68134F28BC1A2D00FAD890 /* GithubWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubWrapper.swift; sourceTree = "<group>"; };
|
||||||
0C68135128BC1A7C00FAD890 /* GithubModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubModels.swift; sourceTree = "<group>"; };
|
0C68135128BC1A7C00FAD890 /* GithubModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubModels.swift; sourceTree = "<group>"; };
|
||||||
0C70E40128C3CE9C00A5C72D /* ConditionalContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalContextMenu.swift; sourceTree = "<group>"; };
|
0C70E40128C3CE9C00A5C72D /* ConditionalContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalContextMenu.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -161,14 +175,18 @@
|
||||||
0CA3B23A28C2AA5600616D3A /* Bookmark+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bookmark+CoreDataClass.swift"; sourceTree = "<group>"; };
|
0CA3B23A28C2AA5600616D3A /* Bookmark+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bookmark+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||||
0CA3B23B28C2AA5600616D3A /* Bookmark+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bookmark+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
0CA3B23B28C2AA5600616D3A /* Bookmark+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bookmark+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||||
0CA3FB1F28B91D9500FA10A8 /* IndeterminateProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndeterminateProgressView.swift; sourceTree = "<group>"; };
|
0CA3FB1F28B91D9500FA10A8 /* IndeterminateProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndeterminateProgressView.swift; sourceTree = "<group>"; };
|
||||||
|
0CA429F728C5098D000D0610 /* DateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateFormatter.swift; sourceTree = "<group>"; };
|
||||||
0CAF1C68286F5C0E00296F86 /* Ferrite.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ferrite.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
0CAF1C68286F5C0E00296F86 /* Ferrite.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ferrite.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
0CB6516228C5A57300DCA721 /* ConditionalId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalId.swift; sourceTree = "<group>"; };
|
0CB6516228C5A57300DCA721 /* ConditionalId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalId.swift; sourceTree = "<group>"; };
|
||||||
0CB6516428C5A5D700DCA721 /* InlinedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlinedList.swift; sourceTree = "<group>"; };
|
0CB6516428C5A5D700DCA721 /* InlinedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlinedList.swift; sourceTree = "<group>"; };
|
||||||
0CB6516928C5B4A600DCA721 /* InlineHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineHeader.swift; sourceTree = "<group>"; };
|
0CB6516928C5B4A600DCA721 /* InlineHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineHeader.swift; sourceTree = "<group>"; };
|
||||||
|
0CBAB83528D12ED500AC903E /* DisableInteraction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisableInteraction.swift; sourceTree = "<group>"; };
|
||||||
0CBC76FC288D914F0054BE44 /* BatchChoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchChoiceView.swift; sourceTree = "<group>"; };
|
0CBC76FC288D914F0054BE44 /* BatchChoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchChoiceView.swift; sourceTree = "<group>"; };
|
||||||
0CBC76FE288DAAD00054BE44 /* NavigationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationViewModel.swift; sourceTree = "<group>"; };
|
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>"; };
|
||||||
|
0CD4CAC528C980EB0046E1DC /* HistoryActionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryActionsView.swift; sourceTree = "<group>"; };
|
||||||
|
0CD5E78828CD932B001BF684 /* DisabledAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisabledAppearance.swift; sourceTree = "<group>"; };
|
||||||
0CDCB91728C662640098B513 /* EmptyInstructionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyInstructionView.swift; 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 */
|
||||||
|
|
@ -194,6 +212,8 @@
|
||||||
0C0D50DE288DF72D0035ECC8 /* Classes */ = {
|
0C0D50DE288DF72D0035ECC8 /* Classes */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
0C54D36128C5086E00BFEEE2 /* History+CoreDataClass.swift */,
|
||||||
|
0C54D36228C5086E00BFEEE2 /* History+CoreDataProperties.swift */,
|
||||||
0CA3B23A28C2AA5600616D3A /* Bookmark+CoreDataClass.swift */,
|
0CA3B23A28C2AA5600616D3A /* Bookmark+CoreDataClass.swift */,
|
||||||
0CA3B23B28C2AA5600616D3A /* Bookmark+CoreDataProperties.swift */,
|
0CA3B23B28C2AA5600616D3A /* Bookmark+CoreDataProperties.swift */,
|
||||||
0C31133A28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift */,
|
0C31133A28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift */,
|
||||||
|
|
@ -280,6 +300,11 @@
|
||||||
0CDCB91728C662640098B513 /* EmptyInstructionView.swift */,
|
0CDCB91728C662640098B513 /* EmptyInstructionView.swift */,
|
||||||
0C360C5B28C7DF1400884ED3 /* DynamicFetchRequest.swift */,
|
0C360C5B28C7DF1400884ED3 /* DynamicFetchRequest.swift */,
|
||||||
0C70E40128C3CE9C00A5C72D /* ConditionalContextMenu.swift */,
|
0C70E40128C3CE9C00A5C72D /* ConditionalContextMenu.swift */,
|
||||||
|
0C391EC828CA63F0009F1CA1 /* DynamicActionSheet.swift */,
|
||||||
|
0C391ECA28CAA44B009F1CA1 /* AlertButton.swift */,
|
||||||
|
0C626A9428CADB25003C7129 /* DynamicAlert.swift */,
|
||||||
|
0CD5E78828CD932B001BF684 /* DisabledAppearance.swift */,
|
||||||
|
0CBAB83528D12ED500AC903E /* DisableInteraction.swift */,
|
||||||
);
|
);
|
||||||
path = CommonViews;
|
path = CommonViews;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -302,6 +327,7 @@
|
||||||
0C7D11FD28AA03FE00ED92DB /* View.swift */,
|
0C7D11FD28AA03FE00ED92DB /* View.swift */,
|
||||||
0C78041C28BFB3EA001E8CA3 /* String.swift */,
|
0C78041C28BFB3EA001E8CA3 /* String.swift */,
|
||||||
0C70E40528C40C4E00A5C72D /* NotificationCenter.swift */,
|
0C70E40528C40C4E00A5C72D /* NotificationCenter.swift */,
|
||||||
|
0CA429F728C5098D000D0610 /* DateFormatter.swift */,
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -315,6 +341,7 @@
|
||||||
0CA148C0288903F000DE2211 /* CommonViews */,
|
0CA148C0288903F000DE2211 /* CommonViews */,
|
||||||
0CA0545C288F7CB200850554 /* SettingsViews */,
|
0CA0545C288F7CB200850554 /* SettingsViews */,
|
||||||
0CA148D3288903F000DE2211 /* SearchResultsView.swift */,
|
0CA148D3288903F000DE2211 /* SearchResultsView.swift */,
|
||||||
|
0C41BC6228C2AD0F00B47DD6 /* SearchResultButtonView.swift */,
|
||||||
0C57D4CB289032ED008534E8 /* SearchResultRDView.swift */,
|
0C57D4CB289032ED008534E8 /* SearchResultRDView.swift */,
|
||||||
0CA148D4288903F000DE2211 /* ContentView.swift */,
|
0CA148D4288903F000DE2211 /* ContentView.swift */,
|
||||||
0CA148D1288903F000DE2211 /* MainView.swift */,
|
0CA148D1288903F000DE2211 /* MainView.swift */,
|
||||||
|
|
@ -324,9 +351,7 @@
|
||||||
0CA148BD288903F000DE2211 /* MagnetChoiceView.swift */,
|
0CA148BD288903F000DE2211 /* MagnetChoiceView.swift */,
|
||||||
0C0D50E6288DFF850035ECC8 /* SourcesView.swift */,
|
0C0D50E6288DFF850035ECC8 /* SourcesView.swift */,
|
||||||
0C32FB522890D19D002BD219 /* AboutView.swift */,
|
0C32FB522890D19D002BD219 /* AboutView.swift */,
|
||||||
0C60B1EE28A1A00000E3FD7E /* SearchProgressView.swift */,
|
|
||||||
0CA3B23328C2658700616D3A /* LibraryView.swift */,
|
0CA3B23328C2658700616D3A /* LibraryView.swift */,
|
||||||
0C41BC6228C2AD0F00B47DD6 /* SearchResultButtonView.swift */,
|
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -365,6 +390,8 @@
|
||||||
children = (
|
children = (
|
||||||
0CA3B23828C2660D00616D3A /* BookmarksView.swift */,
|
0CA3B23828C2660D00616D3A /* BookmarksView.swift */,
|
||||||
0CA3B23628C2660700616D3A /* HistoryView.swift */,
|
0CA3B23628C2660700616D3A /* HistoryView.swift */,
|
||||||
|
0CD4CAC528C980EB0046E1DC /* HistoryActionsView.swift */,
|
||||||
|
0C12D43C28CC332A000195BF /* HistoryButtonView.swift */,
|
||||||
);
|
);
|
||||||
path = LibraryViews;
|
path = LibraryViews;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -486,11 +513,11 @@
|
||||||
files = (
|
files = (
|
||||||
0C0D50E5288DFE7F0035ECC8 /* SourceModels.swift in Sources */,
|
0C0D50E5288DFE7F0035ECC8 /* SourceModels.swift in Sources */,
|
||||||
0CB6516528C5A5D700DCA721 /* InlinedList.swift in Sources */,
|
0CB6516528C5A5D700DCA721 /* InlinedList.swift in Sources */,
|
||||||
0C60B1EF28A1A00000E3FD7E /* SearchProgressView.swift in Sources */,
|
|
||||||
0C32FB532890D19D002BD219 /* AboutView.swift in Sources */,
|
0C32FB532890D19D002BD219 /* AboutView.swift in Sources */,
|
||||||
0CB6516328C5A57300DCA721 /* ConditionalId.swift in Sources */,
|
0CB6516328C5A57300DCA721 /* ConditionalId.swift in Sources */,
|
||||||
0C70E40628C40C4E00A5C72D /* NotificationCenter.swift in Sources */,
|
0C70E40628C40C4E00A5C72D /* NotificationCenter.swift in Sources */,
|
||||||
0C84F4832895BFED0074B7C9 /* Source+CoreDataProperties.swift in Sources */,
|
0C84F4832895BFED0074B7C9 /* Source+CoreDataProperties.swift in Sources */,
|
||||||
|
0CD5E78928CD932B001BF684 /* DisabledAppearance.swift in Sources */,
|
||||||
0CA148DB288903F000DE2211 /* NavView.swift in Sources */,
|
0CA148DB288903F000DE2211 /* NavView.swift in Sources */,
|
||||||
0CA3B23C28C2AA5600616D3A /* Bookmark+CoreDataClass.swift in Sources */,
|
0CA3B23C28C2AA5600616D3A /* Bookmark+CoreDataClass.swift in Sources */,
|
||||||
0C750745289B003E004B3906 /* SourceRssParser+CoreDataProperties.swift in Sources */,
|
0C750745289B003E004B3906 /* SourceRssParser+CoreDataProperties.swift in Sources */,
|
||||||
|
|
@ -499,8 +526,10 @@
|
||||||
0C31133D28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift in Sources */,
|
0C31133D28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift in Sources */,
|
||||||
0CA0545B288EEA4E00850554 /* SourceListEditorView.swift in Sources */,
|
0CA0545B288EEA4E00850554 /* SourceListEditorView.swift in Sources */,
|
||||||
0C360C5C28C7DF1400884ED3 /* DynamicFetchRequest.swift in Sources */,
|
0C360C5C28C7DF1400884ED3 /* DynamicFetchRequest.swift in Sources */,
|
||||||
|
0CA429F828C5098D000D0610 /* DateFormatter.swift in Sources */,
|
||||||
0C84F4872895BFED0074B7C9 /* SourceList+CoreDataProperties.swift in Sources */,
|
0C84F4872895BFED0074B7C9 /* SourceList+CoreDataProperties.swift in Sources */,
|
||||||
0C794B6B289DACF100DD1CC8 /* SourceCatalogView.swift in Sources */,
|
0C794B6B289DACF100DD1CC8 /* SourceCatalogView.swift in Sources */,
|
||||||
|
0C54D36428C5086E00BFEEE2 /* History+CoreDataProperties.swift in Sources */,
|
||||||
0CA148E9288903F000DE2211 /* MainView.swift in Sources */,
|
0CA148E9288903F000DE2211 /* MainView.swift in Sources */,
|
||||||
0CBC76FD288D914F0054BE44 /* BatchChoiceView.swift in Sources */,
|
0CBC76FD288D914F0054BE44 /* BatchChoiceView.swift in Sources */,
|
||||||
0C32FB552890D1BF002BD219 /* UIApplication.swift in Sources */,
|
0C32FB552890D1BF002BD219 /* UIApplication.swift in Sources */,
|
||||||
|
|
@ -515,6 +544,7 @@
|
||||||
0C750744289B003E004B3906 /* SourceRssParser+CoreDataClass.swift in Sources */,
|
0C750744289B003E004B3906 /* SourceRssParser+CoreDataClass.swift in Sources */,
|
||||||
0C794B69289DACC800DD1CC8 /* InstalledSourceView.swift in Sources */,
|
0C794B69289DACC800DD1CC8 /* InstalledSourceView.swift in Sources */,
|
||||||
0C79DC082899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift in Sources */,
|
0C79DC082899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift in Sources */,
|
||||||
|
0CD4CAC628C980EB0046E1DC /* HistoryActionsView.swift in Sources */,
|
||||||
0CA148DD288903F000DE2211 /* ScrapingViewModel.swift in Sources */,
|
0CA148DD288903F000DE2211 /* ScrapingViewModel.swift in Sources */,
|
||||||
0CB6516A28C5B4A600DCA721 /* InlineHeader.swift in Sources */,
|
0CB6516A28C5B4A600DCA721 /* InlineHeader.swift in Sources */,
|
||||||
0CA148D8288903F000DE2211 /* MagnetChoiceView.swift in Sources */,
|
0CA148D8288903F000DE2211 /* MagnetChoiceView.swift in Sources */,
|
||||||
|
|
@ -523,9 +553,11 @@
|
||||||
0C68135028BC1A2D00FAD890 /* GithubWrapper.swift in Sources */,
|
0C68135028BC1A2D00FAD890 /* GithubWrapper.swift in Sources */,
|
||||||
0C95D8DA28A55BB6005E22B3 /* SettingsModels.swift in Sources */,
|
0C95D8DA28A55BB6005E22B3 /* SettingsModels.swift in Sources */,
|
||||||
0CA148E3288903F000DE2211 /* Task.swift in Sources */,
|
0CA148E3288903F000DE2211 /* Task.swift in Sources */,
|
||||||
|
0C626A9528CADB25003C7129 /* DynamicAlert.swift in Sources */,
|
||||||
0CA148E7288903F000DE2211 /* ToastViewModel.swift in Sources */,
|
0CA148E7288903F000DE2211 /* ToastViewModel.swift in Sources */,
|
||||||
0C68135228BC1A7C00FAD890 /* GithubModels.swift in Sources */,
|
0C68135228BC1A7C00FAD890 /* GithubModels.swift in Sources */,
|
||||||
0CFEFCFD288A006200B3F490 /* GroupBoxStyle.swift in Sources */,
|
0CFEFCFD288A006200B3F490 /* GroupBoxStyle.swift in Sources */,
|
||||||
|
0C391EC928CA63F0009F1CA1 /* DynamicActionSheet.swift in Sources */,
|
||||||
0CA3B23928C2660D00616D3A /* BookmarksView.swift in Sources */,
|
0CA3B23928C2660D00616D3A /* BookmarksView.swift in Sources */,
|
||||||
0C79DC072899AF3C003F1C5A /* SourceSeedLeech+CoreDataClass.swift in Sources */,
|
0C79DC072899AF3C003F1C5A /* SourceSeedLeech+CoreDataClass.swift in Sources */,
|
||||||
0C794B67289DACB600DD1CC8 /* SourceUpdateButtonView.swift in Sources */,
|
0C794B67289DACB600DD1CC8 /* SourceUpdateButtonView.swift in Sources */,
|
||||||
|
|
@ -539,6 +571,7 @@
|
||||||
0C41BC6328C2AD0F00B47DD6 /* SearchResultButtonView.swift in Sources */,
|
0C41BC6328C2AD0F00B47DD6 /* SearchResultButtonView.swift in Sources */,
|
||||||
0CA05459288EE9E600850554 /* SourceManager.swift in Sources */,
|
0CA05459288EE9E600850554 /* SourceManager.swift in Sources */,
|
||||||
0C84F4772895BE680074B7C9 /* FerriteDB.xcdatamodeld in Sources */,
|
0C84F4772895BE680074B7C9 /* FerriteDB.xcdatamodeld in Sources */,
|
||||||
|
0C12D43D28CC332A000195BF /* HistoryButtonView.swift in Sources */,
|
||||||
0C733287289C4C820058D1FE /* SourceSettingsView.swift in Sources */,
|
0C733287289C4C820058D1FE /* SourceSettingsView.swift in Sources */,
|
||||||
0C10848B28BD9A38008F0BA6 /* SettingsAppVersionView.swift in Sources */,
|
0C10848B28BD9A38008F0BA6 /* SettingsAppVersionView.swift in Sources */,
|
||||||
0CA05457288EE58200850554 /* SettingsSourceListView.swift in Sources */,
|
0CA05457288EE58200850554 /* SettingsSourceListView.swift in Sources */,
|
||||||
|
|
@ -550,9 +583,12 @@
|
||||||
0CA148E8288903F000DE2211 /* RealDebridWrapper.swift in Sources */,
|
0CA148E8288903F000DE2211 /* RealDebridWrapper.swift in Sources */,
|
||||||
0CA148D6288903F000DE2211 /* SettingsView.swift in Sources */,
|
0CA148D6288903F000DE2211 /* SettingsView.swift in Sources */,
|
||||||
0CA148E5288903F000DE2211 /* DebridManager.swift in Sources */,
|
0CA148E5288903F000DE2211 /* DebridManager.swift in Sources */,
|
||||||
|
0C54D36328C5086E00BFEEE2 /* History+CoreDataClass.swift in Sources */,
|
||||||
|
0CBAB83628D12ED500AC903E /* DisableInteraction.swift in Sources */,
|
||||||
0C84F4842895BFED0074B7C9 /* SourceHtmlParser+CoreDataClass.swift in Sources */,
|
0C84F4842895BFED0074B7C9 /* SourceHtmlParser+CoreDataClass.swift in Sources */,
|
||||||
0C32FB572890D1F2002BD219 /* ListRowViews.swift in Sources */,
|
0C32FB572890D1F2002BD219 /* ListRowViews.swift in Sources */,
|
||||||
0C84F4822895BFED0074B7C9 /* Source+CoreDataClass.swift in Sources */,
|
0C84F4822895BFED0074B7C9 /* Source+CoreDataClass.swift in Sources */,
|
||||||
|
0C391ECB28CAA44B009F1CA1 /* AlertButton.swift in Sources */,
|
||||||
0C84F4852895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift in Sources */,
|
0C84F4852895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift in Sources */,
|
||||||
0CA148EB288903F000DE2211 /* SearchResultsView.swift in Sources */,
|
0CA148EB288903F000DE2211 /* SearchResultsView.swift in Sources */,
|
||||||
0CA148D7288903F000DE2211 /* LoginWebView.swift in Sources */,
|
0CA148D7288903F000DE2211 /* LoginWebView.swift in Sources */,
|
||||||
|
|
@ -705,6 +741,7 @@
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = me.kingbri.Ferrite;
|
PRODUCT_BUNDLE_IDENTIFIER = me.kingbri.Ferrite;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_STRICT_CONCURRENCY = minimal;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
|
|
@ -738,6 +775,7 @@
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = me.kingbri.Ferrite;
|
PRODUCT_BUNDLE_IDENTIFIER = me.kingbri.Ferrite;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_STRICT_CONCURRENCY = minimal;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
|
|
|
||||||
15
Ferrite/DataManagement/Classes/History+CoreDataClass.swift
Normal file
15
Ferrite/DataManagement/Classes/History+CoreDataClass.swift
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
//
|
||||||
|
// History+CoreDataClass.swift
|
||||||
|
// Ferrite
|
||||||
|
//
|
||||||
|
// Created by Brian Dashore on 9/4/22.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreData
|
||||||
|
|
||||||
|
@objc(History)
|
||||||
|
public class History: NSManagedObject {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
//
|
||||||
|
// History+CoreDataProperties.swift
|
||||||
|
// Ferrite
|
||||||
|
//
|
||||||
|
// Created by Brian Dashore on 9/4/22.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreData
|
||||||
|
|
||||||
|
|
||||||
|
extension History {
|
||||||
|
|
||||||
|
@nonobjc public class func fetchRequest() -> NSFetchRequest<History> {
|
||||||
|
return NSFetchRequest<History>(entityName: "History")
|
||||||
|
}
|
||||||
|
|
||||||
|
@NSManaged public var date: Date?
|
||||||
|
@NSManaged public var dateString: String?
|
||||||
|
@NSManaged public var entries: NSSet?
|
||||||
|
|
||||||
|
var entryArray: [HistoryEntry] {
|
||||||
|
let entrySet = entries as? Set<HistoryEntry> ?? []
|
||||||
|
|
||||||
|
return entrySet.sorted {
|
||||||
|
$0.timeStamp > $1.timeStamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Generated accessors for entries
|
||||||
|
extension History {
|
||||||
|
|
||||||
|
@objc(addEntriesObject:)
|
||||||
|
@NSManaged public func addToEntries(_ value: HistoryEntry)
|
||||||
|
|
||||||
|
@objc(removeEntriesObject:)
|
||||||
|
@NSManaged public func removeFromEntries(_ value: HistoryEntry)
|
||||||
|
|
||||||
|
@objc(addEntries:)
|
||||||
|
@NSManaged public func addToEntries(_ values: NSSet)
|
||||||
|
|
||||||
|
@objc(removeEntries:)
|
||||||
|
@NSManaged public func removeFromEntries(_ values: NSSet)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension History : Identifiable {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21277" systemVersion="21F79" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21279" systemVersion="21G83" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||||
<entity name="Bookmark" representedClassName="Bookmark" syncable="YES">
|
<entity name="Bookmark" representedClassName="Bookmark" syncable="YES">
|
||||||
<attribute name="leechers" optional="YES" attributeType="String"/>
|
<attribute name="leechers" optional="YES" attributeType="String"/>
|
||||||
<attribute name="magnetHash" optional="YES" attributeType="String"/>
|
<attribute name="magnetHash" optional="YES" attributeType="String"/>
|
||||||
|
|
@ -10,13 +10,15 @@
|
||||||
<attribute name="source" attributeType="String" defaultValueString=""/>
|
<attribute name="source" attributeType="String" defaultValueString=""/>
|
||||||
<attribute name="title" optional="YES" attributeType="String"/>
|
<attribute name="title" optional="YES" attributeType="String"/>
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="History" representedClassName="History" syncable="YES" codeGenerationType="class">
|
<entity name="History" representedClassName="History" syncable="YES">
|
||||||
<attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
<attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
<attribute name="dateString" optional="YES" attributeType="String"/>
|
<attribute name="dateString" optional="YES" attributeType="String"/>
|
||||||
<relationship name="entries" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="HistoryEntry" inverseName="parentHistory" inverseEntity="HistoryEntry"/>
|
<relationship name="entries" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="HistoryEntry" inverseName="parentHistory" inverseEntity="HistoryEntry"/>
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="HistoryEntry" representedClassName="HistoryEntry" syncable="YES" codeGenerationType="class">
|
<entity name="HistoryEntry" representedClassName="HistoryEntry" syncable="YES" codeGenerationType="class">
|
||||||
<attribute name="name" optional="YES" attributeType="String"/>
|
<attribute name="name" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="source" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="subName" optional="YES" attributeType="String"/>
|
||||||
<attribute name="timeStamp" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
|
<attribute name="timeStamp" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||||
<attribute name="url" optional="YES" attributeType="String"/>
|
<attribute name="url" optional="YES" attributeType="String"/>
|
||||||
<relationship name="parentHistory" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="History" inverseName="entries" inverseEntity="History"/>
|
<relationship name="parentHistory" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="History" inverseName="entries" inverseEntity="History"/>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,18 @@
|
||||||
|
|
||||||
import CoreData
|
import CoreData
|
||||||
|
|
||||||
|
enum HistoryDeleteRange {
|
||||||
|
case day
|
||||||
|
case week
|
||||||
|
case month
|
||||||
|
case allTime
|
||||||
|
}
|
||||||
|
|
||||||
|
enum HistoryDeleteError: Error {
|
||||||
|
case noDate(String)
|
||||||
|
case unknown(String)
|
||||||
|
}
|
||||||
|
|
||||||
// No iCloud until finalized sources
|
// No iCloud until finalized sources
|
||||||
struct PersistenceController {
|
struct PersistenceController {
|
||||||
static var shared = PersistenceController()
|
static var shared = PersistenceController()
|
||||||
|
|
@ -78,4 +90,67 @@ struct PersistenceController {
|
||||||
container.viewContext.delete(object)
|
container.viewContext.delete(object)
|
||||||
save()
|
save()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func getHistoryPredicate(range: HistoryDeleteRange) -> NSPredicate? {
|
||||||
|
if range == .allTime {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var components = Calendar.current.dateComponents([.day, .month, .year], from: Date())
|
||||||
|
components.hour = 0
|
||||||
|
components.minute = 0
|
||||||
|
components.second = 0
|
||||||
|
|
||||||
|
guard let today = Calendar.current.date(from: components) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var offsetComponents = DateComponents(day: 1)
|
||||||
|
guard let tomorrow = Calendar.current.date(byAdding: offsetComponents, to: today) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch range {
|
||||||
|
case .week:
|
||||||
|
offsetComponents.day = -7
|
||||||
|
case .month:
|
||||||
|
offsetComponents.day = -28
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
guard var offsetDate = Calendar.current.date(byAdding: offsetComponents, to: today) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if TimeZone.current.isDaylightSavingTime(for: offsetDate) {
|
||||||
|
offsetDate = offsetDate.addingTimeInterval(3600)
|
||||||
|
}
|
||||||
|
|
||||||
|
let predicate = NSPredicate(format: "date >= %@ && date < %@", range == .day ? today as NSDate : offsetDate as NSDate, tomorrow as NSDate)
|
||||||
|
|
||||||
|
return predicate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always use the background context to batch delete
|
||||||
|
// Merge changes into both contexts to update views
|
||||||
|
func batchDeleteHistory(range: HistoryDeleteRange) throws {
|
||||||
|
let predicate = getHistoryPredicate(range: range)
|
||||||
|
|
||||||
|
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "History")
|
||||||
|
|
||||||
|
if let predicate = predicate {
|
||||||
|
fetchRequest.predicate = predicate
|
||||||
|
} else if range != .allTime {
|
||||||
|
throw HistoryDeleteError.noDate("No history date range was provided and you weren't trying to clear everything! Try relaunching the app?")
|
||||||
|
}
|
||||||
|
|
||||||
|
let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
|
||||||
|
|
||||||
|
batchDeleteRequest.resultType = .resultTypeObjectIDs
|
||||||
|
let result = try backgroundContext.execute(batchDeleteRequest) as? NSBatchDeleteResult
|
||||||
|
let changes: [AnyHashable: Any] = [NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? []]
|
||||||
|
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [container.viewContext, backgroundContext])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
17
Ferrite/Extensions/DateFormatter.swift
Normal file
17
Ferrite/Extensions/DateFormatter.swift
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
//
|
||||||
|
// DateFormatter.swift
|
||||||
|
// Ferrite
|
||||||
|
//
|
||||||
|
// Created by Brian Dashore on 9/4/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension DateFormatter {
|
||||||
|
static let historyDateFormatter: DateFormatter = {
|
||||||
|
let df = DateFormatter()
|
||||||
|
df.dateFormat = "ddMMyyyy"
|
||||||
|
|
||||||
|
return df
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
@ -33,6 +33,10 @@ extension View {
|
||||||
modifier(ConditionalId(id: id))
|
modifier(ConditionalId(id: id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func disabledAppearance(_ disabled: Bool, dimmedOpacity: Double? = nil, animation: Animation? = nil) -> some View {
|
||||||
|
modifier(DisabledAppearance(disabled: disabled, dimmedOpacity: dimmedOpacity, animation: animation))
|
||||||
|
}
|
||||||
|
|
||||||
func inlinedList() -> some View {
|
func inlinedList() -> some View {
|
||||||
modifier(InlinedList())
|
modifier(InlinedList())
|
||||||
}
|
}
|
||||||
|
|
@ -43,4 +47,26 @@ extension View {
|
||||||
) -> some View {
|
) -> some View {
|
||||||
modifier(ConditionalContextMenu(internalContent, id: id))
|
modifier(ConditionalContextMenu(internalContent, id: id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dynamicActionSheet(
|
||||||
|
isPresented: Binding<Bool>,
|
||||||
|
title: String,
|
||||||
|
message: String? = nil,
|
||||||
|
buttons: [AlertButton]) -> some View
|
||||||
|
{
|
||||||
|
modifier(DynamicActionSheet(isPresented: isPresented, title: title, message: message, buttons: buttons))
|
||||||
|
}
|
||||||
|
|
||||||
|
func dynamicAlert(
|
||||||
|
isPresented: Binding<Bool>,
|
||||||
|
title: String,
|
||||||
|
message: String? = nil,
|
||||||
|
buttons: [AlertButton]) -> some View
|
||||||
|
{
|
||||||
|
modifier(DynamicAlert(isPresented: isPresented, title: title, message: message, buttons: buttons))
|
||||||
|
}
|
||||||
|
|
||||||
|
func disableInteraction(_ disabled: Bool) -> some View {
|
||||||
|
modifier(DisableInteraction(disabled: disabled))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ class NavigationViewModel: ObservableObject {
|
||||||
@AppStorage("Actions.DefaultDebrid") var defaultDebridAction: DefaultDebridActionType = .none
|
@AppStorage("Actions.DefaultDebrid") var defaultDebridAction: DefaultDebridActionType = .none
|
||||||
@AppStorage("Actions.DefaultMagnet") var defaultMagnetAction: DefaultMagnetActionType = .none
|
@AppStorage("Actions.DefaultMagnet") var defaultMagnetAction: DefaultMagnetActionType = .none
|
||||||
|
|
||||||
public func runDebridAction(action: DefaultDebridActionType?, urlString: String) {
|
public func runDebridAction(urlString: String, _ action: DefaultDebridActionType? = nil) {
|
||||||
let selectedAction = action ?? defaultDebridAction
|
let selectedAction = action ?? defaultDebridAction
|
||||||
|
|
||||||
switch selectedAction {
|
switch selectedAction {
|
||||||
|
|
@ -89,17 +89,10 @@ class NavigationViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func runMagnetAction(_ action: DefaultMagnetActionType? = nil) {
|
public func runMagnetAction(magnetString: String?, _ action: DefaultMagnetActionType? = nil) {
|
||||||
guard let searchResult = selectedSearchResult else {
|
|
||||||
toastModel?.updateToastDescription("Magnet action error: A search result was not selected.")
|
|
||||||
print("Magnet action error: A search result was not selected.")
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let selectedAction = action ?? defaultMagnetAction
|
let selectedAction = action ?? defaultMagnetAction
|
||||||
|
|
||||||
guard let magnetLink = searchResult.magnetLink else {
|
guard let magnetLink = magnetString else {
|
||||||
toastModel?.updateToastDescription("Could not run your action because the magnet link is invalid.")
|
toastModel?.updateToastDescription("Could not run your action because the magnet link is invalid.")
|
||||||
print("Magnet action error: The magnet link is invalid.")
|
print("Magnet action error: The magnet link is invalid.")
|
||||||
|
|
||||||
|
|
@ -126,4 +119,48 @@ class NavigationViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func addToHistory(name: String?, source: String?, url: String?, subName: String? = nil) {
|
||||||
|
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||||
|
|
||||||
|
let newHistoryEntry = HistoryEntry(context: backgroundContext)
|
||||||
|
newHistoryEntry.name = name
|
||||||
|
newHistoryEntry.source = source
|
||||||
|
newHistoryEntry.url = url
|
||||||
|
newHistoryEntry.subName = subName
|
||||||
|
|
||||||
|
let now = Date()
|
||||||
|
newHistoryEntry.timeStamp = now.timeIntervalSince1970
|
||||||
|
|
||||||
|
let dateString = DateFormatter.historyDateFormatter.string(from: now)
|
||||||
|
|
||||||
|
let historyRequest = History.fetchRequest()
|
||||||
|
historyRequest.predicate = NSPredicate(format: "dateString = %@", dateString)
|
||||||
|
|
||||||
|
if var histories = try? backgroundContext.fetch(historyRequest) {
|
||||||
|
for (i, history) in histories.enumerated() {
|
||||||
|
let existingEntries = history.entryArray.filter { $0.url == newHistoryEntry.url && $0.name == newHistoryEntry.name }
|
||||||
|
|
||||||
|
if !existingEntries.isEmpty {
|
||||||
|
for entry in existingEntries {
|
||||||
|
PersistenceController.shared.delete(entry, context: backgroundContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if history.entryArray.isEmpty {
|
||||||
|
PersistenceController.shared.delete(history, context: backgroundContext)
|
||||||
|
histories.remove(at: i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newHistoryEntry.parentHistory = histories.first ?? History(context: backgroundContext)
|
||||||
|
} else {
|
||||||
|
newHistoryEntry.parentHistory = History(context: backgroundContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
newHistoryEntry.parentHistory?.dateString = dateString
|
||||||
|
newHistoryEntry.parentHistory?.date = now
|
||||||
|
|
||||||
|
PersistenceController.shared.save(backgroundContext)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,12 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct BatchChoiceView: View {
|
struct BatchChoiceView: View {
|
||||||
@Environment(\.presentationMode) var presentationMode
|
|
||||||
|
|
||||||
@EnvironmentObject var debridManager: DebridManager
|
@EnvironmentObject var debridManager: DebridManager
|
||||||
@EnvironmentObject var scrapingModel: ScrapingViewModel
|
@EnvironmentObject var scrapingModel: ScrapingViewModel
|
||||||
@EnvironmentObject var navModel: NavigationViewModel
|
@EnvironmentObject var navModel: NavigationViewModel
|
||||||
|
|
||||||
|
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavView {
|
NavView {
|
||||||
List {
|
List {
|
||||||
|
|
@ -28,7 +28,8 @@ struct BatchChoiceView: View {
|
||||||
if !debridManager.realDebridDownloadUrl.isEmpty {
|
if !debridManager.realDebridDownloadUrl.isEmpty {
|
||||||
// The download may complete before this sheet dismisses
|
// The download may complete before this sheet dismisses
|
||||||
try? await Task.sleep(seconds: 1)
|
try? await Task.sleep(seconds: 1)
|
||||||
navModel.runDebridAction(action: nil, urlString: debridManager.realDebridDownloadUrl)
|
navModel.addToHistory(name: searchResult.title, source: searchResult.source, url: debridManager.realDebridDownloadUrl, subName: file.name)
|
||||||
|
navModel.runDebridAction(urlString: debridManager.realDebridDownloadUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
debridManager.selectedRealDebridFile = nil
|
debridManager.selectedRealDebridFile = nil
|
||||||
|
|
@ -36,7 +37,7 @@ struct BatchChoiceView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
presentationMode.wrappedValue.dismiss()
|
navModel.currentChoiceSheet = nil
|
||||||
}
|
}
|
||||||
.dynamicAccentColor(.primary)
|
.dynamicAccentColor(.primary)
|
||||||
}
|
}
|
||||||
|
|
@ -47,9 +48,12 @@ struct BatchChoiceView: View {
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
Button("Done") {
|
Button("Done") {
|
||||||
debridManager.selectedRealDebridItem = nil
|
navModel.currentChoiceSheet = nil
|
||||||
|
|
||||||
presentationMode.wrappedValue.dismiss()
|
Task {
|
||||||
|
try? await Task.sleep(seconds: 1)
|
||||||
|
debridManager.selectedRealDebridItem = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
69
Ferrite/Views/CommonViews/AlertButton.swift
Normal file
69
Ferrite/Views/CommonViews/AlertButton.swift
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
//
|
||||||
|
// AlertButton.swift
|
||||||
|
// Ferrite
|
||||||
|
//
|
||||||
|
// Created by Brian Dashore on 9/8/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct AlertButton: Identifiable {
|
||||||
|
enum Role {
|
||||||
|
case destructive
|
||||||
|
case cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
let id: UUID
|
||||||
|
let label: String
|
||||||
|
let action: () -> Void
|
||||||
|
let role: Role?
|
||||||
|
|
||||||
|
// Used for all buttons
|
||||||
|
init(_ label: String, role: Role? = nil, action: @escaping () -> Void) {
|
||||||
|
self.id = UUID()
|
||||||
|
self.label = label
|
||||||
|
self.action = action
|
||||||
|
self.role = role
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used for buttons with no action
|
||||||
|
init(_ label: String = "Cancel", role: Role? = nil) {
|
||||||
|
self.id = UUID()
|
||||||
|
self.label = label
|
||||||
|
self.action = { }
|
||||||
|
self.role = role
|
||||||
|
}
|
||||||
|
|
||||||
|
func toActionButton() -> Alert.Button {
|
||||||
|
if let role = role {
|
||||||
|
switch role {
|
||||||
|
case .cancel:
|
||||||
|
return .cancel(Text(label))
|
||||||
|
case .destructive:
|
||||||
|
return .destructive(Text(label), action: action)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return .default(Text(label), action: action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 15.0, *)
|
||||||
|
@ViewBuilder
|
||||||
|
func toButtonView() -> some View {
|
||||||
|
Button(label, role: toButtonRole(role), action: action)
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 15.0, *)
|
||||||
|
func toButtonRole(_ role: Role?) -> ButtonRole? {
|
||||||
|
if let role = role {
|
||||||
|
switch role {
|
||||||
|
case .destructive:
|
||||||
|
return .destructive
|
||||||
|
case .cancel:
|
||||||
|
return .cancel
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
Ferrite/Views/CommonViews/DisableInteraction.swift
Normal file
25
Ferrite/Views/CommonViews/DisableInteraction.swift
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
//
|
||||||
|
// DisableInteraction.swift
|
||||||
|
// Ferrite
|
||||||
|
//
|
||||||
|
// Created by Brian Dashore on 9/13/22.
|
||||||
|
//
|
||||||
|
// Disables interaction without applying the appearance
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct DisableInteraction: ViewModifier {
|
||||||
|
let disabled: Bool
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
.overlay {
|
||||||
|
if disabled {
|
||||||
|
Color.clear
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.gesture(TapGesture())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
Ferrite/Views/CommonViews/DisabledAppearance.swift
Normal file
21
Ferrite/Views/CommonViews/DisabledAppearance.swift
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
//
|
||||||
|
// DisabledAppearance.swift
|
||||||
|
// Ferrite
|
||||||
|
//
|
||||||
|
// Created by Brian Dashore on 9/10/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct DisabledAppearance: ViewModifier {
|
||||||
|
let disabled: Bool
|
||||||
|
let dimmedOpacity: Double?
|
||||||
|
let animation: Animation?
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
.disabled(disabled)
|
||||||
|
.opacity(disabled ? dimmedOpacity.map { $0 } ?? 0.5 : 1)
|
||||||
|
.animation(animation.map { $0 } ?? .none, value: disabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
44
Ferrite/Views/CommonViews/DynamicActionSheet.swift
Normal file
44
Ferrite/Views/CommonViews/DynamicActionSheet.swift
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
//
|
||||||
|
// DynamicActionSheet.swift
|
||||||
|
// Ferrite
|
||||||
|
//
|
||||||
|
// Created by Brian Dashore on 9/8/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct DynamicActionSheet: ViewModifier {
|
||||||
|
@Binding var isPresented: Bool
|
||||||
|
|
||||||
|
let title: String
|
||||||
|
let message: String?
|
||||||
|
let buttons: [AlertButton]
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
if #available(iOS 15, *) {
|
||||||
|
content
|
||||||
|
.confirmationDialog(
|
||||||
|
title,
|
||||||
|
isPresented: $isPresented,
|
||||||
|
titleVisibility: .visible
|
||||||
|
) {
|
||||||
|
ForEach(buttons) { button in
|
||||||
|
button.toButtonView()
|
||||||
|
}
|
||||||
|
} message: {
|
||||||
|
if let message = message {
|
||||||
|
Text(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
content
|
||||||
|
.actionSheet(isPresented: $isPresented) {
|
||||||
|
ActionSheet(
|
||||||
|
title: Text(title),
|
||||||
|
message: message.map { Text($0) } ?? nil,
|
||||||
|
buttons: [buttons.map { $0.toActionButton() }, [.cancel()]].flatMap{ $0 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
Ferrite/Views/CommonViews/DynamicAlert.swift
Normal file
56
Ferrite/Views/CommonViews/DynamicAlert.swift
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
//
|
||||||
|
// DynamicAlert.swift
|
||||||
|
// Ferrite
|
||||||
|
//
|
||||||
|
// Created by Brian Dashore on 9/8/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct DynamicAlert: ViewModifier {
|
||||||
|
@Binding var isPresented: Bool
|
||||||
|
|
||||||
|
let title: String
|
||||||
|
let message: String?
|
||||||
|
let buttons: [AlertButton]
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
if #available(iOS 15, *) {
|
||||||
|
content
|
||||||
|
.alert(
|
||||||
|
title,
|
||||||
|
isPresented: $isPresented,
|
||||||
|
actions: {
|
||||||
|
ForEach(buttons) { button in
|
||||||
|
button.toButtonView()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
if let message = message {
|
||||||
|
Text(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
content
|
||||||
|
.alert(isPresented: $isPresented) {
|
||||||
|
if let primaryButton = buttons[safe: 0],
|
||||||
|
let secondaryButton = buttons[safe: 1]
|
||||||
|
{
|
||||||
|
return Alert(
|
||||||
|
title: Text(title),
|
||||||
|
message: message.map { Text($0) } ?? nil,
|
||||||
|
primaryButton: primaryButton.toActionButton(),
|
||||||
|
secondaryButton: secondaryButton.toActionButton()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return Alert(
|
||||||
|
title: Text(title),
|
||||||
|
message: message.map { Text($0) } ?? nil,
|
||||||
|
dismissButton: buttons[0].toActionButton()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -73,25 +73,23 @@ struct ContentView: View {
|
||||||
SearchResultsView()
|
SearchResultsView()
|
||||||
}
|
}
|
||||||
.sheet(item: $navModel.currentChoiceSheet) { item in
|
.sheet(item: $navModel.currentChoiceSheet) { item in
|
||||||
Group {
|
switch item {
|
||||||
switch item {
|
case .magnet:
|
||||||
case .magnet:
|
MagnetChoiceView()
|
||||||
MagnetChoiceView()
|
.environmentObject(debridManager)
|
||||||
.environmentObject(debridManager)
|
.environmentObject(scrapingModel)
|
||||||
.environmentObject(scrapingModel)
|
.environmentObject(navModel)
|
||||||
.environmentObject(navModel)
|
case .batch:
|
||||||
case .batch:
|
BatchChoiceView()
|
||||||
BatchChoiceView()
|
.environmentObject(debridManager)
|
||||||
.environmentObject(debridManager)
|
.environmentObject(scrapingModel)
|
||||||
.environmentObject(scrapingModel)
|
.environmentObject(navModel)
|
||||||
.environmentObject(navModel)
|
case .activity:
|
||||||
case .activity:
|
if #available(iOS 16, *) {
|
||||||
if #available(iOS 16, *) {
|
AppActivityView(activityItems: navModel.activityItems)
|
||||||
AppActivityView(activityItems: navModel.activityItems)
|
.presentationDetents([.medium, .large])
|
||||||
.presentationDetents([.medium, .large])
|
} else {
|
||||||
} else {
|
AppActivityView(activityItems: navModel.activityItems)
|
||||||
AppActivityView(activityItems: navModel.activityItems)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,13 @@ struct LibraryView: View {
|
||||||
]
|
]
|
||||||
) var bookmarks: FetchedResults<Bookmark>
|
) var bookmarks: FetchedResults<Bookmark>
|
||||||
|
|
||||||
|
@FetchRequest(
|
||||||
|
entity: History.entity(),
|
||||||
|
sortDescriptors: [
|
||||||
|
NSSortDescriptor(keyPath: \History.date, ascending: false)
|
||||||
|
]
|
||||||
|
) var history: FetchedResults<History>
|
||||||
|
|
||||||
@State private var historyEmpty = true
|
@State private var historyEmpty = true
|
||||||
|
|
||||||
@State private var selectedSegment: LibraryPickerSegment = .bookmarks
|
@State private var selectedSegment: LibraryPickerSegment = .bookmarks
|
||||||
|
|
@ -42,7 +49,7 @@ struct LibraryView: View {
|
||||||
case .bookmarks:
|
case .bookmarks:
|
||||||
BookmarksView(bookmarks: bookmarks)
|
BookmarksView(bookmarks: bookmarks)
|
||||||
case .history:
|
case .history:
|
||||||
HistoryView()
|
HistoryView(history: history)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
@ -54,7 +61,7 @@ struct LibraryView: View {
|
||||||
EmptyInstructionView(title: "No Bookmarks", message: "Add a bookmark from search results")
|
EmptyInstructionView(title: "No Bookmarks", message: "Add a bookmark from search results")
|
||||||
}
|
}
|
||||||
case .history:
|
case .history:
|
||||||
if historyEmpty {
|
if history.isEmpty {
|
||||||
EmptyInstructionView(title: "No History", message: "Start watching to build history")
|
EmptyInstructionView(title: "No History", message: "Start watching to build history")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +69,13 @@ struct LibraryView: View {
|
||||||
.navigationTitle("Library")
|
.navigationTitle("Library")
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
EditButton()
|
HStack {
|
||||||
|
EditButton()
|
||||||
|
|
||||||
|
if selectedSegment == .history {
|
||||||
|
HistoryActionsView()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.environment(\.editMode, $editMode)
|
.environment(\.editMode, $editMode)
|
||||||
|
|
|
||||||
54
Ferrite/Views/LibraryViews/HistoryActionsView.swift
Normal file
54
Ferrite/Views/LibraryViews/HistoryActionsView.swift
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
//
|
||||||
|
// HistoryActionsView.swift
|
||||||
|
// Ferrite
|
||||||
|
//
|
||||||
|
// Created by Brian Dashore on 9/7/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct HistoryActionsView: View {
|
||||||
|
@EnvironmentObject var toastModel: ToastViewModel
|
||||||
|
|
||||||
|
@State private var showActionSheet = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button("Clear") {
|
||||||
|
showActionSheet.toggle()
|
||||||
|
}
|
||||||
|
.dynamicAccentColor(.red)
|
||||||
|
.dynamicActionSheet(
|
||||||
|
isPresented: $showActionSheet,
|
||||||
|
title: "Clear watch history",
|
||||||
|
message: "This is an irreversible action!",
|
||||||
|
buttons: [
|
||||||
|
AlertButton("Past day", role: .destructive) {
|
||||||
|
deleteHistory(.day)
|
||||||
|
},
|
||||||
|
AlertButton("Past week", role: .destructive) {
|
||||||
|
deleteHistory(.week)
|
||||||
|
},
|
||||||
|
AlertButton("Past month", role: .destructive) {
|
||||||
|
deleteHistory(.month)
|
||||||
|
},
|
||||||
|
AlertButton("All time", role: .destructive) {
|
||||||
|
deleteHistory(.allTime)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteHistory(_ deleteRange: HistoryDeleteRange) {
|
||||||
|
do {
|
||||||
|
try PersistenceController.shared.batchDeleteHistory(range: deleteRange)
|
||||||
|
} catch {
|
||||||
|
toastModel.updateToastDescription("History delete error: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HistoryActionsView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
HistoryActionsView()
|
||||||
|
}
|
||||||
|
}
|
||||||
76
Ferrite/Views/LibraryViews/HistoryButtonView.swift
Normal file
76
Ferrite/Views/LibraryViews/HistoryButtonView.swift
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
//
|
||||||
|
// HistoryButtonView.swift
|
||||||
|
// Ferrite
|
||||||
|
//
|
||||||
|
// Created by Brian Dashore on 9/9/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct HistoryButtonView: View {
|
||||||
|
@EnvironmentObject var toastModel: ToastViewModel
|
||||||
|
@EnvironmentObject var navModel: NavigationViewModel
|
||||||
|
@EnvironmentObject var debridManager: DebridManager
|
||||||
|
|
||||||
|
let entry: HistoryEntry
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button {
|
||||||
|
if let url = entry.url {
|
||||||
|
if url.starts(with: "https://") {
|
||||||
|
Task {
|
||||||
|
debridManager.realDebridDownloadUrl = url
|
||||||
|
navModel.runDebridAction(urlString: url)
|
||||||
|
|
||||||
|
if navModel.currentChoiceSheet != .magnet {
|
||||||
|
debridManager.realDebridDownloadUrl = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
navModel.runMagnetAction(magnetString: url)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toastModel.updateToastDescription("URL invalid. Cannot load this history entry. Please delete it.")
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
VStack(alignment: .leading, spacing: 3) {
|
||||||
|
Text(entry.name ?? "Unknown title")
|
||||||
|
.font(entry.subName == nil ? .body : .subheadline)
|
||||||
|
|
||||||
|
if let subName = entry.subName {
|
||||||
|
Text(subName)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.font(.subheadline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Text(entry.source ?? "Unknown source")
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Text("DEBRID")
|
||||||
|
.fontWeight(.bold)
|
||||||
|
.padding(3)
|
||||||
|
.background {
|
||||||
|
Group {
|
||||||
|
if let url = entry.url, url.starts(with: "https://") {
|
||||||
|
Color.green
|
||||||
|
} else {
|
||||||
|
Color.red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.cornerRadius(4)
|
||||||
|
.opacity(0.5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.font(.caption)
|
||||||
|
}
|
||||||
|
.lineLimit(1)
|
||||||
|
.disabledAppearance(navModel.currentChoiceSheet != nil, dimmedOpacity: 0.7, animation: .easeOut(duration: 0.2))
|
||||||
|
}
|
||||||
|
.dynamicAccentColor(.white)
|
||||||
|
.disableInteraction(navModel.currentChoiceSheet != nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,15 +8,58 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct HistoryView: View {
|
struct HistoryView: View {
|
||||||
|
@EnvironmentObject var navModel: NavigationViewModel
|
||||||
|
|
||||||
|
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||||
|
|
||||||
|
var history: FetchedResults<History>
|
||||||
|
var formatter: DateFormatter = .init()
|
||||||
|
|
||||||
|
@State private var historyIndex = 0
|
||||||
|
|
||||||
|
init(history: FetchedResults<History>) {
|
||||||
|
self.history = history
|
||||||
|
|
||||||
|
formatter.dateStyle = .medium
|
||||||
|
formatter.timeStyle = .none
|
||||||
|
}
|
||||||
|
|
||||||
|
func groupedEntries(_ result: FetchedResults<History>) -> [[History]] {
|
||||||
|
Dictionary(grouping: result) { (element: History) in
|
||||||
|
element.dateString ?? ""
|
||||||
|
}.values.sorted { $0[0].date ?? Date() > $1[0].date ?? Date() }
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
if !history.isEmpty {
|
||||||
EmptyView()
|
List {
|
||||||
|
ForEach(groupedEntries(history), id: \.self) { (section: [History]) in
|
||||||
|
Section(header: Text(formatter.string(from: section[0].date ?? Date()))) {
|
||||||
|
ForEach(section, id: \.self) { history in
|
||||||
|
ForEach(history.entryArray) { entry in
|
||||||
|
HistoryButtonView(entry: entry)
|
||||||
|
}
|
||||||
|
.onDelete { offsets in
|
||||||
|
removeEntry(at: offsets, from: history)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.listStyle(.insetGrouped)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeEntry(at offsets: IndexSet, from history: History) {
|
||||||
|
for index in offsets {
|
||||||
|
if let entry = history.entryArray[safe: index] {
|
||||||
|
history.removeFromEntries(entry)
|
||||||
|
PersistenceController.shared.delete(entry, context: backgroundContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
if history.entryArray.isEmpty {
|
||||||
|
PersistenceController.shared.delete(history, context: backgroundContext)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HistoryView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
HistoryView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -23,31 +23,30 @@ struct MagnetChoiceView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavView {
|
NavView {
|
||||||
Form {
|
Form {
|
||||||
if realDebridEnabled, debridManager.matchSearchResult(result: navModel.selectedSearchResult) != .none {
|
if !debridManager.realDebridDownloadUrl.isEmpty {
|
||||||
Section(header: "Real Debrid options") {
|
Section(header: "Real Debrid options") {
|
||||||
ListRowButtonView("Play on Outplayer", systemImage: "arrow.up.forward.app.fill") {
|
ListRowButtonView("Play on Outplayer", systemImage: "arrow.up.forward.app.fill") {
|
||||||
navModel.runDebridAction(action: .outplayer, urlString: debridManager.realDebridDownloadUrl)
|
navModel.runDebridAction(urlString: debridManager.realDebridDownloadUrl, .outplayer)
|
||||||
}
|
}
|
||||||
|
|
||||||
ListRowButtonView("Play on VLC", systemImage: "arrow.up.forward.app.fill") {
|
ListRowButtonView("Play on VLC", systemImage: "arrow.up.forward.app.fill") {
|
||||||
navModel.runDebridAction(action: .vlc, urlString: debridManager.realDebridDownloadUrl)
|
navModel.runDebridAction(urlString: debridManager.realDebridDownloadUrl, .vlc)
|
||||||
}
|
}
|
||||||
|
|
||||||
ListRowButtonView("Play on Infuse", systemImage: "arrow.up.forward.app.fill") {
|
ListRowButtonView("Play on Infuse", systemImage: "arrow.up.forward.app.fill") {
|
||||||
navModel.runDebridAction(action: .infuse, urlString: debridManager.realDebridDownloadUrl)
|
navModel.runDebridAction(urlString: debridManager.realDebridDownloadUrl, .infuse)
|
||||||
}
|
}
|
||||||
|
|
||||||
ListRowButtonView("Copy download URL", systemImage: "doc.on.doc.fill") {
|
ListRowButtonView("Copy download URL", systemImage: "doc.on.doc.fill") {
|
||||||
UIPasteboard.general.string = debridManager.realDebridDownloadUrl
|
UIPasteboard.general.string = debridManager.realDebridDownloadUrl
|
||||||
showLinkCopyAlert.toggle()
|
showLinkCopyAlert.toggle()
|
||||||
}
|
}
|
||||||
.alert(isPresented: $showLinkCopyAlert) {
|
.dynamicAlert(
|
||||||
Alert(
|
isPresented: $showLinkCopyAlert ,
|
||||||
title: Text("Copied"),
|
title: "Copied",
|
||||||
message: Text("Download link copied successfully"),
|
message: "Download link copied successfully",
|
||||||
dismissButton: .cancel(Text("OK"))
|
buttons: [AlertButton("OK")]
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
ListRowButtonView("Share download URL", systemImage: "square.and.arrow.up.fill") {
|
ListRowButtonView("Share download URL", systemImage: "square.and.arrow.up.fill") {
|
||||||
if let url = URL(string: debridManager.realDebridDownloadUrl) {
|
if let url = URL(string: debridManager.realDebridDownloadUrl) {
|
||||||
|
|
@ -63,13 +62,12 @@ struct MagnetChoiceView: View {
|
||||||
UIPasteboard.general.string = navModel.selectedSearchResult?.magnetLink
|
UIPasteboard.general.string = navModel.selectedSearchResult?.magnetLink
|
||||||
showMagnetCopyAlert.toggle()
|
showMagnetCopyAlert.toggle()
|
||||||
}
|
}
|
||||||
.alert(isPresented: $showMagnetCopyAlert) {
|
.dynamicAlert(
|
||||||
Alert(
|
isPresented: $showMagnetCopyAlert,
|
||||||
title: Text("Copied"),
|
title: "Copied",
|
||||||
message: Text("Magnet link copied successfully"),
|
message: "Magnet link copied successfully",
|
||||||
dismissButton: .cancel(Text("OK"))
|
buttons: [AlertButton("OK")]
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
ListRowButtonView("Share magnet", systemImage: "square.and.arrow.up.fill") {
|
ListRowButtonView("Share magnet", systemImage: "square.and.arrow.up.fill") {
|
||||||
if let result = navModel.selectedSearchResult,
|
if let result = navModel.selectedSearchResult,
|
||||||
|
|
@ -82,7 +80,7 @@ struct MagnetChoiceView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
ListRowButtonView("Open in WebTor", systemImage: "arrow.up.forward.app.fill") {
|
ListRowButtonView("Open in WebTor", systemImage: "arrow.up.forward.app.fill") {
|
||||||
navModel.runMagnetAction(.webtor)
|
navModel.runMagnetAction(magnetString: navModel.selectedSearchResult?.magnetLink, .webtor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -95,6 +93,9 @@ struct MagnetChoiceView: View {
|
||||||
AppActivityView(activityItems: navModel.activityItems)
|
AppActivityView(activityItems: navModel.activityItems)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onDisappear {
|
||||||
|
debridManager.realDebridDownloadUrl = ""
|
||||||
|
}
|
||||||
.navigationTitle("Link actions")
|
.navigationTitle("Link actions")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
|
|
||||||
|
|
@ -46,20 +46,21 @@ struct MainView: View {
|
||||||
}
|
}
|
||||||
.tag(ViewTab.settings)
|
.tag(ViewTab.settings)
|
||||||
}
|
}
|
||||||
.alert(isPresented: $showUpdateAlert) {
|
.dynamicAlert(
|
||||||
Alert(
|
isPresented: $showUpdateAlert,
|
||||||
title: Text("Update available"),
|
title: "Update available",
|
||||||
message: Text("Ferrite \(releaseVersionString) can be downloaded. \n\n This alert can be disabled in Settings."),
|
message: "Ferrite \(releaseVersionString) can be downloaded. \n\n This alert can be disabled in Settings.",
|
||||||
primaryButton: .default(Text("Download")) {
|
buttons: [
|
||||||
|
AlertButton("Download") {
|
||||||
guard let releaseUrl = URL(string: releaseUrlString) else {
|
guard let releaseUrl = URL(string: releaseUrlString) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
UIApplication.shared.open(releaseUrl)
|
UIApplication.shared.open(releaseUrl)
|
||||||
},
|
},
|
||||||
secondaryButton: .cancel()
|
AlertButton(role: .cancel)
|
||||||
)
|
]
|
||||||
}
|
)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if autoUpdateNotifs {
|
if autoUpdateNotifs {
|
||||||
viewTask = Task {
|
viewTask = Task {
|
||||||
|
|
@ -71,7 +72,6 @@ struct MainView: View {
|
||||||
|
|
||||||
let releaseVersion = String(latestRelease.tagName.dropFirst())
|
let releaseVersion = String(latestRelease.tagName.dropFirst())
|
||||||
if releaseVersion > UIApplication.shared.appVersion {
|
if releaseVersion > UIApplication.shared.appVersion {
|
||||||
print("Greater")
|
|
||||||
releaseVersionString = latestRelease.tagName
|
releaseVersionString = latestRelease.tagName
|
||||||
releaseUrlString = latestRelease.htmlUrl
|
releaseUrlString = latestRelease.htmlUrl
|
||||||
showUpdateAlert.toggle()
|
showUpdateAlert.toggle()
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
//
|
|
||||||
// SearchProgressView.swift
|
|
||||||
// Ferrite
|
|
||||||
//
|
|
||||||
// Created by Brian Dashore on 8/8/22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct SearchProgressView: View {
|
|
||||||
var body: some View {
|
|
||||||
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SearchProgressView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
SearchProgressView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -14,73 +14,82 @@ struct SearchResultButtonView: View {
|
||||||
@EnvironmentObject var navModel: NavigationViewModel
|
@EnvironmentObject var navModel: NavigationViewModel
|
||||||
@EnvironmentObject var debridManager: DebridManager
|
@EnvironmentObject var debridManager: DebridManager
|
||||||
|
|
||||||
|
@AppStorage("RealDebrid.Enabled") var realDebridEnabled = false
|
||||||
|
|
||||||
var result: SearchResult
|
var result: SearchResult
|
||||||
|
|
||||||
@State private var runOnce = false
|
@State private var runOnce = false
|
||||||
@State var existingBookmark: Bookmark? = nil
|
@State var existingBookmark: Bookmark? = nil
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading) {
|
Button {
|
||||||
Button {
|
if debridManager.currentDebridTask == nil {
|
||||||
if debridManager.currentDebridTask == nil {
|
navModel.selectedSearchResult = result
|
||||||
navModel.selectedSearchResult = result
|
|
||||||
|
switch debridManager.matchSearchResult(result: result) {
|
||||||
switch debridManager.matchSearchResult(result: result) {
|
case .full:
|
||||||
case .full:
|
debridManager.currentDebridTask = Task {
|
||||||
debridManager.currentDebridTask = Task {
|
await debridManager.fetchRdDownload(searchResult: result)
|
||||||
await debridManager.fetchRdDownload(searchResult: result)
|
|
||||||
|
if !debridManager.realDebridDownloadUrl.isEmpty {
|
||||||
if !debridManager.realDebridDownloadUrl.isEmpty {
|
navModel.addToHistory(name: result.title, source: result.source, url: debridManager.realDebridDownloadUrl)
|
||||||
navModel.runDebridAction(action: nil, urlString: debridManager.realDebridDownloadUrl)
|
navModel.runDebridAction(urlString: debridManager.realDebridDownloadUrl)
|
||||||
|
|
||||||
|
if navModel.currentChoiceSheet != .magnet {
|
||||||
|
debridManager.realDebridDownloadUrl = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .partial:
|
|
||||||
if debridManager.setSelectedRdResult(result: result) {
|
|
||||||
navModel.currentChoiceSheet = .batch
|
|
||||||
}
|
|
||||||
case .none:
|
|
||||||
navModel.runMagnetAction()
|
|
||||||
}
|
}
|
||||||
|
case .partial:
|
||||||
|
if debridManager.setSelectedRdResult(result: result) {
|
||||||
|
navModel.currentChoiceSheet = .batch
|
||||||
|
}
|
||||||
|
case .none:
|
||||||
|
navModel.addToHistory(name: result.title, source: result.source, url: result.magnetLink)
|
||||||
|
navModel.runMagnetAction(magnetString: result.magnetLink)
|
||||||
}
|
}
|
||||||
} label: {
|
}
|
||||||
|
} label: {
|
||||||
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
Text(result.title ?? "No title")
|
Text(result.title ?? "No title")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
}
|
|
||||||
.dynamicAccentColor(.primary)
|
|
||||||
.padding(.bottom, 5)
|
|
||||||
.conditionalContextMenu(id: existingBookmark) {
|
|
||||||
if let bookmark = existingBookmark {
|
|
||||||
Button {
|
|
||||||
PersistenceController.shared.delete(bookmark, context: backgroundContext)
|
|
||||||
|
|
||||||
// When the entity is deleted, let other instances know to remove that reference
|
SearchResultRDView(result: result)
|
||||||
NotificationCenter.default.post(name: .didDeleteBookmark, object: nil)
|
}
|
||||||
} label: {
|
.disabledAppearance(navModel.currentChoiceSheet != nil, dimmedOpacity: 0.7, animation: .easeOut(duration: 0.2))
|
||||||
Text("Remove bookmark")
|
}
|
||||||
Image(systemName: "bookmark.slash.fill")
|
.disableInteraction(navModel.currentChoiceSheet != nil)
|
||||||
}
|
.dynamicAccentColor(.primary)
|
||||||
} else {
|
.conditionalContextMenu(id: existingBookmark) {
|
||||||
Button {
|
if let bookmark = existingBookmark {
|
||||||
let newBookmark = Bookmark(context: backgroundContext)
|
Button {
|
||||||
newBookmark.title = result.title
|
PersistenceController.shared.delete(bookmark, context: backgroundContext)
|
||||||
newBookmark.source = result.source
|
|
||||||
newBookmark.magnetHash = result.magnetHash
|
// When the entity is deleted, let other instances know to remove that reference
|
||||||
newBookmark.magnetLink = result.magnetLink
|
NotificationCenter.default.post(name: .didDeleteBookmark, object: nil)
|
||||||
newBookmark.seeders = result.seeders
|
} label: {
|
||||||
newBookmark.leechers = result.leechers
|
Text("Remove bookmark")
|
||||||
|
Image(systemName: "bookmark.slash.fill")
|
||||||
existingBookmark = newBookmark
|
}
|
||||||
|
} else {
|
||||||
PersistenceController.shared.save(backgroundContext)
|
Button {
|
||||||
} label: {
|
let newBookmark = Bookmark(context: backgroundContext)
|
||||||
Text("Bookmark")
|
newBookmark.title = result.title
|
||||||
Image(systemName: "bookmark")
|
newBookmark.source = result.source
|
||||||
}
|
newBookmark.magnetHash = result.magnetHash
|
||||||
|
newBookmark.magnetLink = result.magnetLink
|
||||||
|
newBookmark.seeders = result.seeders
|
||||||
|
newBookmark.leechers = result.leechers
|
||||||
|
|
||||||
|
existingBookmark = newBookmark
|
||||||
|
|
||||||
|
PersistenceController.shared.save(backgroundContext)
|
||||||
|
} label: {
|
||||||
|
Text("Bookmark")
|
||||||
|
Image(systemName: "bookmark")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchResultRDView(result: result)
|
|
||||||
}
|
}
|
||||||
.onReceive(NotificationCenter.default.publisher(for: .didDeleteBookmark)) { _ in
|
.onReceive(NotificationCenter.default.publisher(for: .didDeleteBookmark)) { _ in
|
||||||
existingBookmark = nil
|
existingBookmark = nil
|
||||||
|
|
|
||||||
|
|
@ -37,20 +37,18 @@ struct SearchResultRDView: View {
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
.padding(2)
|
.padding(2)
|
||||||
.background {
|
.background {
|
||||||
switch debridManager.matchSearchResult(result: result) {
|
Group {
|
||||||
case .full:
|
switch debridManager.matchSearchResult(result: result) {
|
||||||
Color.green
|
case .full:
|
||||||
.cornerRadius(4)
|
Color.green
|
||||||
.opacity(0.5)
|
case .partial:
|
||||||
case .partial:
|
Color.orange
|
||||||
Color.orange
|
case .none:
|
||||||
.cornerRadius(4)
|
Color.red
|
||||||
.opacity(0.5)
|
}
|
||||||
case .none:
|
|
||||||
Color.red
|
|
||||||
.cornerRadius(4)
|
|
||||||
.opacity(0.5)
|
|
||||||
}
|
}
|
||||||
|
.cornerRadius(4)
|
||||||
|
.opacity(0.5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,13 +32,12 @@ struct SourceListEditorView: View {
|
||||||
sourceUrl = navModel.selectedSourceList?.urlString ?? ""
|
sourceUrl = navModel.selectedSourceList?.urlString ?? ""
|
||||||
sourceUrlSet = true
|
sourceUrlSet = true
|
||||||
}
|
}
|
||||||
.alert(isPresented: $sourceManager.showUrlErrorAlert) {
|
.dynamicAlert(
|
||||||
Alert(
|
isPresented: $sourceManager.showUrlErrorAlert,
|
||||||
title: Text("Error"),
|
title: "Error",
|
||||||
message: Text(sourceManager.urlErrorAlertText),
|
message: sourceManager.urlErrorAlertText,
|
||||||
dismissButton: .default(Text("OK"))
|
buttons: [AlertButton("OK")]
|
||||||
)
|
)
|
||||||
}
|
|
||||||
.navigationTitle("Editing source list")
|
.navigationTitle("Editing source list")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue