diff --git a/Localization/StringsConvertor/input/Base.lproj/app.json b/Localization/StringsConvertor/input/Base.lproj/app.json index fc0e84c8b9..a81eeec6f3 100644 --- a/Localization/StringsConvertor/input/Base.lproj/app.json +++ b/Localization/StringsConvertor/input/Base.lproj/app.json @@ -346,6 +346,7 @@ "search": { "placeholder": "Search name or URL" }, + "category_ignored_message": "No servers found in the “%s” category. Here are results from other categories:", "no_server_selected_hint": "We’ll pick a server based on your language if you continue without making a selection." }, "privacy": { diff --git a/Localization/app.json b/Localization/app.json index fc0e84c8b9..a81eeec6f3 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -346,6 +346,7 @@ "search": { "placeholder": "Search name or URL" }, + "category_ignored_message": "No servers found in the “%s” category. Here are results from other categories:", "no_server_selected_hint": "We’ll pick a server based on your language if you continue without making a selection." }, "privacy": { diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 78682c7d73..6a061685f7 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -124,6 +124,7 @@ 62FD27D12893707600B205C5 /* BookmarkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FD27D02893707600B205C5 /* BookmarkViewController.swift */; }; 62FD27D32893707B00B205C5 /* BookmarkViewController+DataSourceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FD27D22893707B00B205C5 /* BookmarkViewController+DataSourceProvider.swift */; }; 62FD27D52893708A00B205C5 /* BookmarkViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FD27D42893708A00B205C5 /* BookmarkViewModel+Diffable.swift */; }; + 852198A82A4F304500D137AC /* PickServerMessageTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 852198A72A4F304500D137AC /* PickServerMessageTableViewCell.swift */; }; 855149C8295F1C5F00943D96 /* UIInterfaceOrientationMask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 855149C7295F1C5F00943D96 /* UIInterfaceOrientationMask.swift */; }; 855149CA29606D6400943D96 /* PortraitAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 855149C929606D6400943D96 /* PortraitAlertController.swift */; }; 85904C02293BC0EB0011C817 /* ImageProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85904C01293BC0EB0011C817 /* ImageProvider.swift */; }; @@ -749,6 +750,7 @@ 7CB58D292DA7ACEF179A9050 /* Pods-Mastodon.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.profile.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.profile.xcconfig"; sourceTree = ""; }; 7CEFFAE9AF9284B13C0A758D /* Pods-MastodonTests.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.asdk - debug.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.asdk - debug.xcconfig"; sourceTree = ""; }; 819CEC9DCAD8E8E7BD85A7BB /* Pods-Mastodon.asdk.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.asdk.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.asdk.xcconfig"; sourceTree = ""; }; + 852198A72A4F304500D137AC /* PickServerMessageTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerMessageTableViewCell.swift; sourceTree = ""; }; 855149C7295F1C5F00943D96 /* UIInterfaceOrientationMask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIInterfaceOrientationMask.swift; sourceTree = ""; }; 855149C929606D6400943D96 /* PortraitAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortraitAlertController.swift; sourceTree = ""; }; 85904C01293BC0EB0011C817 /* ImageProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageProvider.swift; sourceTree = ""; }; @@ -1357,6 +1359,7 @@ children = ( 0FB3D33725E6401400AAD544 /* PickServerCell.swift */, DB0F814F264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift */, + 852198A72A4F304500D137AC /* PickServerMessageTableViewCell.swift */, ); path = TableViewCell; sourceTree = ""; @@ -3648,6 +3651,7 @@ DB5B549F2833A72500DEF8B2 /* FamiliarFollowersViewModel+Diffable.swift in Sources */, DB6B351E2601FAEE00DC1E11 /* ComposeStatusAttachmentCollectionViewCell.swift in Sources */, DB8F7076279E954700E1225B /* DataSourceFacade+Follow.swift in Sources */, + 852198A82A4F304500D137AC /* PickServerMessageTableViewCell.swift in Sources */, DB63F7542799491600455B82 /* DataSourceFacade+SearchHistory.swift in Sources */, DB7A9F912818EAF10016AF98 /* MastodonRegisterView.swift in Sources */, DBF1572F27046F1A00EC00B7 /* SecondaryPlaceholderViewController.swift in Sources */, diff --git a/Mastodon/Scene/Onboarding/PickServer/CategoryPickerItem.swift b/Mastodon/Scene/Onboarding/PickServer/CategoryPickerItem.swift index 759b9606e2..4d7ffd92e0 100644 --- a/Mastodon/Scene/Onboarding/PickServer/CategoryPickerItem.swift +++ b/Mastodon/Scene/Onboarding/PickServer/CategoryPickerItem.swift @@ -17,6 +17,39 @@ enum CategoryPickerItem { case category(category: Mastodon.Entity.Category) } +extension Mastodon.Entity.Category.Kind { + var label: String { + switch self { + case .academia: + return L10n.Scene.ServerPicker.Button.Category.academia + case .activism: + return L10n.Scene.ServerPicker.Button.Category.activism + case .food: + return L10n.Scene.ServerPicker.Button.Category.food + case .furry: + return L10n.Scene.ServerPicker.Button.Category.furry + case .games: + return L10n.Scene.ServerPicker.Button.Category.games + case .general: + return L10n.Scene.ServerPicker.Button.Category.general + case .journalism: + return L10n.Scene.ServerPicker.Button.Category.journalism + case .lgbt: + return L10n.Scene.ServerPicker.Button.Category.lgbt + case .regional: + return L10n.Scene.ServerPicker.Button.Category.regional + case .art: + return L10n.Scene.ServerPicker.Button.Category.art + case .music: + return L10n.Scene.ServerPicker.Button.Category.music + case .tech: + return L10n.Scene.ServerPicker.Button.Category.tech + case ._other: + return "-" // FIXME: + } + } +} + extension CategoryPickerItem { var title: String { @@ -38,34 +71,7 @@ extension CategoryPickerItem { return L10n.Scene.ServerPicker.Button.signupSpeed } case .category(let category): - switch category.category { - case .academia: - return L10n.Scene.ServerPicker.Button.Category.academia - case .activism: - return L10n.Scene.ServerPicker.Button.Category.activism - case .food: - return L10n.Scene.ServerPicker.Button.Category.food - case .furry: - return L10n.Scene.ServerPicker.Button.Category.furry - case .games: - return L10n.Scene.ServerPicker.Button.Category.games - case .general: - return L10n.Scene.ServerPicker.Button.Category.general - case .journalism: - return L10n.Scene.ServerPicker.Button.Category.journalism - case .lgbt: - return L10n.Scene.ServerPicker.Button.Category.lgbt - case .regional: - return L10n.Scene.ServerPicker.Button.Category.regional - case .art: - return L10n.Scene.ServerPicker.Button.Category.art - case .music: - return L10n.Scene.ServerPicker.Button.Category.music - case .tech: - return L10n.Scene.ServerPicker.Button.Category.tech - case ._other: - return "-" // FIXME: - } + return category.category.label } } diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift index f8a2937fb4..a8777d1d7a 100644 --- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift +++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift @@ -48,12 +48,13 @@ extension MastodonPickServerViewModel { loadIndexedServerStateMachine.enter(LoadIndexedServerState.Loading.self) - Publishers.CombineLatest( + Publishers.CombineLatest3( filteredIndexedServers, + selectCategoryItem, unindexedServers ) .receive(on: DispatchQueue.main) - .sink(receiveValue: { [weak self] indexedServers, unindexedServers in + .sink(receiveValue: { [weak self] indexedServers, selectCategoryItem, unindexedServers in guard let self = self else { return } guard let diffableDataSource = self.diffableDataSource else { return } @@ -69,14 +70,21 @@ extension MastodonPickServerViewModel { // TODO: handle filter var serverItems: [PickServerItem] = [] - for server in indexedServers { - let attribute = oldSnapshotServerItemAttributeDict[server.domain] ?? PickServerItem.ServerItemAttribute(isLast: false, isExpand: false) - attribute.isLast.value = false - let item = PickServerItem.server(server: server, attribute: attribute) - guard !serverItems.contains(item) else { continue } - serverItems.append(item) + if let indexedServers { + if indexedServers.didIgnoreCategory, + indexedServers.servers.isNotEmpty, + case .category(let category) = selectCategoryItem { + serverItems.append(.message(attribute: .categoryIgnored(category: category.category))) + } + for server in indexedServers.servers { + let attribute = oldSnapshotServerItemAttributeDict[server.domain] ?? PickServerItem.ServerItemAttribute(isLast: false, isExpand: false) + attribute.isLast.value = false + let item = PickServerItem.server(server: server, attribute: attribute) + guard !serverItems.contains(item) else { continue } + serverItems.append(item) + } } - + if let unindexedServers = unindexedServers { if !unindexedServers.isEmpty { for server in unindexedServers { @@ -87,7 +95,8 @@ extension MastodonPickServerViewModel { serverItems.append(item) } } else { - if indexedServers.isEmpty && !self.isLoadingIndexedServers.value { + if (indexedServers?.servers.isEmpty ?? true), + !self.isLoadingIndexedServers.value { serverItems.append(.loader(attribute: PickServerItem.LoaderItemAttribute(isLast: false, isEmptyResult: true))) } } diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift index 19e9bab3cf..dc10ba54cf 100644 --- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift +++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift @@ -63,7 +63,7 @@ class MastodonPickServerViewModel: NSObject { stateMachine.enter(LoadIndexedServerState.Initial.self) return stateMachine }() - let filteredIndexedServers = CurrentValueSubject<[Mastodon.Entity.Server], Never>([]) + let filteredIndexedServers = CurrentValueSubject(nil) let servers = CurrentValueSubject<[Mastodon.Entity.Server], Error>([]) let selectedServer = CurrentValueSubject(nil) let error = CurrentValueSubject(nil) @@ -126,7 +126,7 @@ extension MastodonPickServerViewModel { (selectedLanguage, manualApprovalRequired) } ) - .map { [weak self] indexedServers, selectCategoryItem, searchText, filters -> [Mastodon.Entity.Server] in + .map { [weak self] indexedServers, selectCategoryItem, searchText, filters in var indexedServers = indexedServers var _indexedServers: [Mastodon.Entity.Server] = [] @@ -249,7 +249,11 @@ extension MastodonPickServerViewModel { } extension MastodonPickServerViewModel { - private static func filterServers(servers: [Mastodon.Entity.Server], language: String? = nil, manualApprovalRequired: Bool? = nil, category: String?, searchText: String) -> [Mastodon.Entity.Server] { + struct FilteredServers { + let servers: [Mastodon.Entity.Server] + let didIgnoreCategory: Bool + } + private static func filterServers(servers: [Mastodon.Entity.Server], language: String? = nil, manualApprovalRequired: Bool? = nil, category: String?, searchText: String) -> FilteredServers { let filteredServers = servers // 1. Filter the category .filter { @@ -275,7 +279,14 @@ extension MastodonPickServerViewModel { print("\($0.domain) \($0.approvalRequired) < \(manualApprovalRequired)") return $0.approvalRequired == manualApprovalRequired } - return filteredServers + + // if there are no results when filtering by category, drop the category filter + if category != nil, filteredServers.isEmpty { + let result = filterServers(servers: servers, language: language, manualApprovalRequired: manualApprovalRequired, category: nil, searchText: searchText) + return FilteredServers(servers: result.servers, didIgnoreCategory: true) + } + + return FilteredServers(servers: filteredServers, didIgnoreCategory: false) } } diff --git a/Mastodon/Scene/Onboarding/PickServer/PickServerItem.swift b/Mastodon/Scene/Onboarding/PickServer/PickServerItem.swift index 8b43969657..e8ec65fa05 100644 --- a/Mastodon/Scene/Onboarding/PickServer/PickServerItem.swift +++ b/Mastodon/Scene/Onboarding/PickServer/PickServerItem.swift @@ -13,6 +13,7 @@ import MastodonSDK enum PickServerItem { case server(server: Mastodon.Entity.Server, attribute: ServerItemAttribute) case loader(attribute: LoaderItemAttribute) + case message(attribute: MessageItemAttribute) } extension PickServerItem { @@ -53,6 +54,10 @@ extension PickServerItem { hasher.combine(id) } } + + enum MessageItemAttribute: Equatable, Hashable { + case categoryIgnored(category: Mastodon.Entity.Category.Kind) + } } extension PickServerItem: Equatable { @@ -75,6 +80,8 @@ extension PickServerItem: Hashable { hasher.combine(server.domain) case .loader(let attribute): hasher.combine(attribute) + case .message(let attribute): + hasher.combine(attribute) } } } diff --git a/Mastodon/Scene/Onboarding/PickServer/PickServerSection.swift b/Mastodon/Scene/Onboarding/PickServer/PickServerSection.swift index 9f17816572..c25cd85e80 100644 --- a/Mastodon/Scene/Onboarding/PickServer/PickServerSection.swift +++ b/Mastodon/Scene/Onboarding/PickServer/PickServerSection.swift @@ -7,6 +7,7 @@ import UIKit import MastodonSDK +import MastodonLocalization import Kanna import AlamofireImage @@ -22,7 +23,8 @@ extension PickServerSection { ) -> UITableViewDiffableDataSource { tableView.register(PickServerCell.self, forCellReuseIdentifier: String(describing: PickServerCell.self)) tableView.register(PickServerLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: PickServerLoaderTableViewCell.self)) - + tableView.register(PickServerMessageTableViewCell.self, forCellReuseIdentifier: String(describing: PickServerMessageTableViewCell.self)) + return UITableViewDiffableDataSource(tableView: tableView) { [ weak dependency ] tableView, indexPath, item -> UITableViewCell? in @@ -36,6 +38,10 @@ extension PickServerSection { let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerLoaderTableViewCell.self), for: indexPath) as! PickServerLoaderTableViewCell PickServerSection.configure(cell: cell, attribute: attribute) return cell + case .message(let message): + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerMessageTableViewCell.self), for: indexPath) as! PickServerMessageTableViewCell + PickServerSection.configure(cell: cell, attribute: message) + return cell } } } @@ -112,3 +118,14 @@ extension PickServerSection { } } + +extension PickServerSection { + + static func configure(cell: PickServerMessageTableViewCell, attribute: PickServerItem.MessageItemAttribute) { + switch attribute { + case .categoryIgnored(let category): + cell.messageLabel.text = L10n.Scene.ServerPicker.categoryIgnoredMessage(category.label) + } + } + +} diff --git a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerMessageTableViewCell.swift b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerMessageTableViewCell.swift new file mode 100644 index 0000000000..fad359eb09 --- /dev/null +++ b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerMessageTableViewCell.swift @@ -0,0 +1,73 @@ +// +// PickServerMessageTableViewCell.swift +// Mastodon +// +// Created by MainasuK Cirno on 2021-5-13. +// + +import UIKit +import Combine +import MastodonAsset +import MastodonCore +import MastodonUI +import MastodonLocalization + +public final class PickServerMessageTableViewCell: UITableViewCell { + + let messageLabel: UILabel = { + let label = UILabel() + label.textColor = Asset.Colors.Label.secondary.color + label.textAlignment = .center + label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 14, weight: .semibold)) + label.numberOfLines = 0 + return label + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + _init() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + +} + +extension PickServerMessageTableViewCell { + + public func _init() { + selectionStyle = .none + backgroundColor = .clear + + messageLabel.translatesAutoresizingMaskIntoConstraints = false + messageLabel.setContentCompressionResistancePriority(.required, for: .vertical) + contentView.addSubview(messageLabel) + NSLayoutConstraint.activate([ + messageLabel.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor), + contentView.readableContentGuide.trailingAnchor.constraint(equalTo: messageLabel.trailingAnchor), + messageLabel.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor), + messageLabel.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor), + ]) + } + +} + +#if canImport(SwiftUI) && DEBUG +import SwiftUI + +struct PickServerMessageTableViewCell_Previews: PreviewProvider { + + static var previews: some View { + UIViewPreview(width: 375) { + let view = PickServerMessageTableViewCell() + view.messageLabel.text = "Hello, world!" + return view + } + .previewLayout(.fixed(width: 375, height: 100)) + } + +} + +#endif diff --git a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift index a140c1f4eb..69115306cb 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift +++ b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift @@ -1302,6 +1302,10 @@ public enum L10n { } } public enum ServerPicker { + /// No servers found in the “%@” category. Here are results from other categories: + public static func categoryIgnoredMessage(_ p1: Any) -> String { + return L10n.tr("Localizable", "Scene.ServerPicker.CategoryIgnoredMessage", String(describing: p1), fallback: "No servers found in the “%@” category. Here are results from other categories:") + } /// We’ll pick a server based on your language if you continue without making a selection. public static let noServerSelectedHint = L10n.tr("Localizable", "Scene.ServerPicker.NoServerSelectedHint", fallback: "We’ll pick a server based on your language if you continue without making a selection.") /// Pick Server diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings index 970175d5ce..6bbc84ead4 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings @@ -467,6 +467,7 @@ uploaded to Mastodon."; "Scene.ServerPicker.Button.SeeLess" = "See Less"; "Scene.ServerPicker.Button.SeeMore" = "See More"; "Scene.ServerPicker.Button.SignupSpeed" = "Sign-up Speed"; +"Scene.ServerPicker.CategoryIgnoredMessage" = "No servers found in the “%@” category. Here are results from other categories:"; "Scene.ServerPicker.EmptyState.BadNetwork" = "Something went wrong while loading the data. Check your internet connection."; "Scene.ServerPicker.EmptyState.FindingServers" = "Finding available servers..."; "Scene.ServerPicker.EmptyState.NoResults" = "No results"; diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Category.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Category.swift index 3b63d6263b..bd6b5c57e6 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Category.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Category.swift @@ -25,7 +25,7 @@ extension Mastodon.Entity { /// # Reference /// https://joinmastodon.org/communities - public enum Kind: RawRepresentable, Codable, Sendable { + public enum Kind: RawRepresentable, Hashable, Codable, Sendable { case general case regional