Skip to content

Commit

Permalink
Torrent list filters added
Browse files Browse the repository at this point in the history
  • Loading branch information
XITRIX committed Nov 16, 2024
1 parent 1235473 commit 9268d82
Show file tree
Hide file tree
Showing 10 changed files with 556 additions and 64 deletions.
2 changes: 1 addition & 1 deletion Submodules/LibTorrent-Swift
16 changes: 16 additions & 0 deletions iTorrent.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@
7CAD30202BC34BCE00592990 /* RssFeedProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CAD301F2BC34BCE00592990 /* RssFeedProvider.swift */; };
7CB2639C2C0671320083C052 /* Publisher+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CB2639B2C0671320083C052 /* Publisher+UI.swift */; };
7CB2639E2C0A5B420083C052 /* BaseSafariViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CB2639D2C0A5B420083C052 /* BaseSafariViewController.swift */; };
7CB58D842CE662BB00929205 /* TagsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CB58D832CE662BB00929205 /* TagsView.swift */; };
7CB58D872CE6AC7D00929205 /* NavigationItemPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CB58D862CE6AC7500929205 /* NavigationItemPalette.swift */; };
7CB6F6CE2BD82BAB00D0813B /* FileSharingPreferencesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CB6F6CD2BD82BAB00D0813B /* FileSharingPreferencesViewModel.swift */; };
7CBDBAAD2C31EF0C008C986B /* UserDefaults+AppGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CBDBAAC2C31EF0C008C986B /* UserDefaults+AppGroup.swift */; };
7CBDBAAE2C31EF52008C986B /* UserDefaults+AppGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CBDBAAC2C31EF0C008C986B /* UserDefaults+AppGroup.swift */; };
Expand Down Expand Up @@ -300,6 +302,8 @@
7CAD301F2BC34BCE00592990 /* RssFeedProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RssFeedProvider.swift; sourceTree = "<group>"; };
7CB2639B2C0671320083C052 /* Publisher+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+UI.swift"; sourceTree = "<group>"; };
7CB2639D2C0A5B420083C052 /* BaseSafariViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseSafariViewController.swift; sourceTree = "<group>"; };
7CB58D832CE662BB00929205 /* TagsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagsView.swift; sourceTree = "<group>"; };
7CB58D862CE6AC7500929205 /* NavigationItemPalette.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationItemPalette.swift; sourceTree = "<group>"; };
7CB6F6CD2BD82BAB00D0813B /* FileSharingPreferencesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSharingPreferencesViewModel.swift; sourceTree = "<group>"; };
7CBDBAAC2C31EF0C008C986B /* UserDefaults+AppGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+AppGroup.swift"; sourceTree = "<group>"; };
7CC411612BD319AE00CA8B13 /* AppDelegate+RemoteConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+RemoteConfig.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -542,6 +546,7 @@
7C4CF2FF2BDE7DF60078FEA1 /* AdView */,
7C5FBE6E2BC2EF8B0069E5A0 /* EditTextView */,
D173D9DD2BC024DD00E4F9EB /* UISwitchWithMenu.swift */,
7CB58D832CE662BB00929205 /* TagsView.swift */,
7C5FBE0E2BBB227D0069E5A0 /* UISegmentedProgressView.swift */,
7C4CF2FD2BDE7DE50078FEA1 /* BaseView.swift */,
7C4ED0932BE961A20034B62C /* UIModernBarButtonItem.swift */,
Expand Down Expand Up @@ -626,6 +631,7 @@
7C95B7B52C385B8E000EC50F /* UIKit */ = {
isa = PBXGroup;
children = (
7CB58D852CE6AC6F00929205 /* NavigationItemPalette */,
D173D9DF2BC0285800E4F9EB /* UIMenu */,
7C95B7B62C385B97000EC50F /* UICellAccessory+Image.swift */,
7C5FBE2B2BBDD6B40069E5A0 /* UIView+LayerColors.swift */,
Expand All @@ -643,6 +649,14 @@
path = RssFeed;
sourceTree = "<group>";
};
7CB58D852CE6AC6F00929205 /* NavigationItemPalette */ = {
isa = PBXGroup;
children = (
7CB58D862CE6AC7500929205 /* NavigationItemPalette.swift */,
);
path = NavigationItemPalette;
sourceTree = "<group>";
};
7CB6F6CC2BD82B8A00D0813B /* FileSharing */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1691,6 +1705,7 @@
D1352D352BBD721700104E7B /* PRStorageViewModel.swift in Sources */,
D1352D2C2BBD6E0E00104E7B /* MemorySpaceManager.swift in Sources */,
D1AA00D22AFAC95500B74629 /* String+Localized.swift in Sources */,
7CB58D872CE6AC7D00929205 /* NavigationItemPalette.swift in Sources */,
7CFEBE732BC3ED480013233F /* RssFeedCell.swift in Sources */,
7C5FBE552BC0B3550069E5A0 /* ProgressWidgetAttributes.swift in Sources */,
7CFEBEA92BC721D50013233F /* RssListPreferencesViewModel.swift in Sources */,
Expand All @@ -1709,6 +1724,7 @@
D1A226A02AEEEFCC00669D6D /* SceneDelegate.swift in Sources */,
D11BE5442AFB901E00780C1B /* PRSwitchView.swift in Sources */,
D173D9E32BC0327D00E4F9EB /* BackgroundService.swift in Sources */,
7CB58D842CE662BB00929205 /* TagsView.swift in Sources */,
D1352D382BBD73CD00104E7B /* ColoredProgressBarView.swift in Sources */,
D1A226FD2AEF04F900669D6D /* BaseViewController.swift in Sources */,
);
Expand Down
191 changes: 191 additions & 0 deletions iTorrent/Components/Views/TagsView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
//
// TagsView.swift
// Apple-Music-Search-Chips-Demo
//
// Created by Seb Vidal on 07/09/2024.
// Modified by XITRIX on 14/11/2024.
//

import Combine
import UIKit

class TagsView: UIScrollView {
private var lastUpdatedFrame: CGRect = .zero
private var bottomStackView: UIStackView!
private var topStackView: UIStackView!
private var backgroundView: UIView!
private var tagMaskView: UIView!

var titles: [String] = [] {
didSet { updateButtons(for: titles) }
}

@Published var selectedTagIndex: Int = 0 {
didSet {
updateSelection(for: selectedTagIndex, animated: true)
}
}

override init(frame: CGRect) {
super.init(frame: frame)
setupScrollView()
setupBottomStackView()
setupTopStackView()
setupBackgroundView()
setupTagMaskView()
}

@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

func scrollToSelectedItem() {
let button = bottomStackView.arrangedSubviews[selectedTagIndex]
scrollRectToVisible(button.frame, animated: true)
}

private func setupScrollView() {
alwaysBounceHorizontal = true
showsHorizontalScrollIndicator = false
contentInsetAdjustmentBehavior = .automatic
}

private func setupBottomStackView() {
bottomStackView = UIStackView()
bottomStackView.axis = .horizontal
bottomStackView.distribution = .fillProportionally
bottomStackView.isLayoutMarginsRelativeArrangement = true
bottomStackView.translatesAutoresizingMaskIntoConstraints = false

addSubview(bottomStackView)

NSLayoutConstraint.activate([
bottomStackView.topAnchor.constraint(equalTo: topAnchor),
bottomStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
bottomStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
bottomStackView.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}

private func setupTopStackView() {
topStackView = UIStackView()
topStackView.axis = .horizontal
topStackView.isUserInteractionEnabled = false
topStackView.distribution = .fillProportionally
topStackView.isLayoutMarginsRelativeArrangement = true
topStackView.translatesAutoresizingMaskIntoConstraints = false

addSubview(topStackView)

NSLayoutConstraint.activate([
topStackView.topAnchor.constraint(equalTo: topAnchor),
topStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
topStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
topStackView.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}

private func setupBackgroundView() {
backgroundView = UIView()
backgroundView.clipsToBounds = true
backgroundView.backgroundColor = .tintColor
backgroundView.layer.cornerCurve = .continuous

insertSubview(backgroundView, aboveSubview: bottomStackView)
}

private func setupTagMaskView() {
tagMaskView = UIView()
tagMaskView.clipsToBounds = true
tagMaskView.backgroundColor = .black
tagMaskView.layer.cornerCurve = .continuous

topStackView.mask = tagMaskView
}

private func updateButtons(for titles: [String]) {
bottomStackView.arrangedSubviews.forEach { subview in
subview.removeFromSuperview()
}

topStackView.arrangedSubviews.forEach { subview in
subview.removeFromSuperview()
}

for title in titles {
let bottomButton = button(with: title, foregroundColor: .label)
bottomStackView.addArrangedSubview(bottomButton)

let topButton = button(with: title, foregroundColor: .white)
topStackView.addArrangedSubview(topButton)
}

setNeedsLayout()
layoutIfNeeded()

updateSelection(for: selectedTagIndex, animated: false)
}

private func button(with title: String, foregroundColor: UIColor) -> UIButton {
let titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { container in
var container = container
container.font = UIFont.systemFont(ofSize: 13, weight: .semibold)

return container
}

let button = UIButton(type: .system)
button.configuration = .plain()
button.configuration?.title = title
button.configuration?.cornerStyle = .capsule
button.configuration?.baseForegroundColor = foregroundColor
button.configuration?.titleTextAttributesTransformer = titleTextAttributesTransformer
button.configuration?.contentInsets = NSDirectionalEdgeInsets(top: 8.33, leading: 12, bottom: 8, trailing: 12.66)
button.addTarget(self, action: #selector(tagButtonTapped), for: .touchUpInside)

return button
}

@objc private func tagButtonTapped(_ sender: UIButton) {
selectedTagIndex = bottomStackView.arrangedSubviews.firstIndex(of: sender)!
}

private func updateSelection(for selectedTagIndex: Int, animated: Bool) {
let update = { [self] in
if bottomStackView.arrangedSubviews.indices.contains(selectedTagIndex) {
let button = bottomStackView.arrangedSubviews[selectedTagIndex]

tagMaskView.layer.cornerRadius = button.frame.height / 2
tagMaskView.frame = button.frame

backgroundView.layer.cornerRadius = button.frame.height / 2
backgroundView.frame = button.frame

scrollRectToVisible(button.frame, animated: true)
}
}

guard animated
else { return update() }

if #available(iOS 17.0, *) {
UIView.animate(springDuration: 0.25, bounce: 0.25) {
update()
}
} else {
UIView.animate(withDuration: 0.25) {
update()
}
}
}

override func layoutSubviews() {
super.layoutSubviews()

guard backgroundView.frame == .zero
else { return }

updateSelection(for: selectedTagIndex, animated: false)
}
}
56 changes: 56 additions & 0 deletions iTorrent/Core/Assets/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,34 @@
}
}
},
"common.all" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "All"
}
},
"es" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "All"
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "Все"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "All"
}
}
}
},
"common.cancel" : {
"localizations" : {
"en" : {
Expand Down Expand Up @@ -3231,6 +3259,34 @@
}
}
},
"noContent.search.%@" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "No Results for “%@”"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "No hay resultados para “%@”"
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "Нет результатов по запросу «%@»"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "未找到“%@”的相关结果"
}
}
}
},
"notification.done.message_%@" : {
"localizations" : {
"en" : {
Expand Down
8 changes: 6 additions & 2 deletions iTorrent/Screens/Rss/Search/RssSearchViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ class RssSearchViewController<VM: RssSearchViewModel>: BaseCollectionViewControl
config.text = %"rssSearch.empty.title"
config.secondaryText = %"rssSearch.empty.subtitle"
contentUnavailableConfiguration = config
case .badSearch:
contentUnavailableConfiguration = UIContentUnavailableConfiguration.search()
case .badSearch(let search):
var configuration = UIContentUnavailableConfiguration.search()
configuration.text = %"noContent.search.\(search)" //"No Results for “\(search)”"
contentUnavailableConfiguration = configuration
case .badFilter:
contentUnavailableConfiguration = UIContentUnavailableConfiguration.empty()
case nil:
contentUnavailableConfiguration = nil
}
Expand Down
2 changes: 1 addition & 1 deletion iTorrent/Screens/Rss/Search/RssSearchViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ extension RssSearchViewModel {
var emptyContentType: AnyPublisher<EmptyType?, Never> {
Publishers.combineLatest($sections, $searchQuery) { sections, searchQuery in
if sections.isEmpty || sections.allSatisfy({ $0.items.isEmpty }) {
if !searchQuery.isEmpty { return EmptyType.badSearch }
if !searchQuery.isEmpty { return EmptyType.badSearch(searchQuery) }
return EmptyType.noData
}
return nil
Expand Down
Loading

0 comments on commit 9268d82

Please sign in to comment.