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:
kingbri 2022-09-06 00:05:34 -04:00
parent 2f870b9410
commit 4d3a16f77e
25 changed files with 823 additions and 172 deletions

View file

@ -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";
}; };

View 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 {
}

View file

@ -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 {
}

View file

@ -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"/>

View file

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

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

View file

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

View file

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

View file

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

View 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
}
}
}

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

View 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)
}
}

View 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 }
)
}
}
}
}

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

View file

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

View file

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

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

View 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)
}
}

View file

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

View file

@ -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 {

View file

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

View file

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

View file

@ -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

View file

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

View file

@ -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 {