feat: implement pick server view search cell & server list cell
This commit is contained in:
parent
eb7a33932e
commit
027fec1cc9
|
@ -16,6 +16,8 @@
|
||||||
0FB3D30825E524C600AAD544 /* PickServerCategoriesCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */; };
|
0FB3D30825E524C600AAD544 /* PickServerCategoriesCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */; };
|
||||||
0FB3D30F25E525CD00AAD544 /* PickServerCategoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D30E25E525CD00AAD544 /* PickServerCategoryView.swift */; };
|
0FB3D30F25E525CD00AAD544 /* PickServerCategoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D30E25E525CD00AAD544 /* PickServerCategoryView.swift */; };
|
||||||
0FB3D31E25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D31D25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift */; };
|
0FB3D31E25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D31D25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift */; };
|
||||||
|
0FB3D33225E5F50E00AAD544 /* PickServerSearchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D33125E5F50E00AAD544 /* PickServerSearchCell.swift */; };
|
||||||
|
0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D33725E6401400AAD544 /* PickServerCell.swift */; };
|
||||||
18BC7629F65E6DB12CB8416D /* Pods_Mastodon_MastodonUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C030226D3C73DCC23D67452 /* Pods_Mastodon_MastodonUITests.framework */; };
|
18BC7629F65E6DB12CB8416D /* Pods_Mastodon_MastodonUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C030226D3C73DCC23D67452 /* Pods_Mastodon_MastodonUITests.framework */; };
|
||||||
2D04F42525C255B9003F936F /* APIService+PublicTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */; };
|
2D04F42525C255B9003F936F /* APIService+PublicTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */; };
|
||||||
2D152A8C25C295CC009AA50C /* StatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D152A8B25C295CC009AA50C /* StatusView.swift */; };
|
2D152A8C25C295CC009AA50C /* StatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D152A8B25C295CC009AA50C /* StatusView.swift */; };
|
||||||
|
@ -214,6 +216,8 @@
|
||||||
0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCategoriesCell.swift; sourceTree = "<group>"; };
|
0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCategoriesCell.swift; sourceTree = "<group>"; };
|
||||||
0FB3D30E25E525CD00AAD544 /* PickServerCategoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCategoryView.swift; sourceTree = "<group>"; };
|
0FB3D30E25E525CD00AAD544 /* PickServerCategoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCategoryView.swift; sourceTree = "<group>"; };
|
||||||
0FB3D31D25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCategoryCollectionViewCell.swift; sourceTree = "<group>"; };
|
0FB3D31D25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCategoryCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
0FB3D33125E5F50E00AAD544 /* PickServerSearchCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerSearchCell.swift; sourceTree = "<group>"; };
|
||||||
|
0FB3D33725E6401400AAD544 /* PickServerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCell.swift; sourceTree = "<group>"; };
|
||||||
2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+PublicTimeline.swift"; sourceTree = "<group>"; };
|
2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+PublicTimeline.swift"; sourceTree = "<group>"; };
|
||||||
2D152A8B25C295CC009AA50C /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = "<group>"; };
|
2D152A8B25C295CC009AA50C /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = "<group>"; };
|
||||||
2D152A9125C2980C009AA50C /* UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = "<group>"; };
|
2D152A9125C2980C009AA50C /* UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = "<group>"; };
|
||||||
|
@ -438,6 +442,8 @@
|
||||||
children = (
|
children = (
|
||||||
0FB3D2FD25E4CB6400AAD544 /* PickServerTitleCell.swift */,
|
0FB3D2FD25E4CB6400AAD544 /* PickServerTitleCell.swift */,
|
||||||
0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */,
|
0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */,
|
||||||
|
0FB3D33125E5F50E00AAD544 /* PickServerSearchCell.swift */,
|
||||||
|
0FB3D33725E6401400AAD544 /* PickServerCell.swift */,
|
||||||
);
|
);
|
||||||
path = TableViewCell;
|
path = TableViewCell;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1370,6 +1376,7 @@
|
||||||
0FB3D2F725E4C24D00AAD544 /* PickServerViewModel.swift in Sources */,
|
0FB3D2F725E4C24D00AAD544 /* PickServerViewModel.swift in Sources */,
|
||||||
2D61335E25C1894B00CAE157 /* APIService.swift in Sources */,
|
2D61335E25C1894B00CAE157 /* APIService.swift in Sources */,
|
||||||
2D38F1F725CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift in Sources */,
|
2D38F1F725CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift in Sources */,
|
||||||
|
0FB3D33225E5F50E00AAD544 /* PickServerSearchCell.swift in Sources */,
|
||||||
2D5A3D3825CF8D9F002347D6 /* ScrollViewContainer.swift in Sources */,
|
2D5A3D3825CF8D9F002347D6 /* ScrollViewContainer.swift in Sources */,
|
||||||
0FAA101225E105390017CCDE /* PrimaryActionButton.swift in Sources */,
|
0FAA101225E105390017CCDE /* PrimaryActionButton.swift in Sources */,
|
||||||
DB8AF53025C13561002E6C99 /* AppContext.swift in Sources */,
|
DB8AF53025C13561002E6C99 /* AppContext.swift in Sources */,
|
||||||
|
@ -1417,6 +1424,7 @@
|
||||||
DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */,
|
DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */,
|
||||||
2D38F1C625CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */,
|
2D38F1C625CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */,
|
||||||
0FB3D30F25E525CD00AAD544 /* PickServerCategoryView.swift in Sources */,
|
0FB3D30F25E525CD00AAD544 /* PickServerCategoryView.swift in Sources */,
|
||||||
|
0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */,
|
||||||
DB8AF54525C13647002E6C99 /* NeedsDependency.swift in Sources */,
|
DB8AF54525C13647002E6C99 /* NeedsDependency.swift in Sources */,
|
||||||
DB9D6BF825E4F5690051B173 /* NotificationViewController.swift in Sources */,
|
DB9D6BF825E4F5690051B173 /* NotificationViewController.swift in Sources */,
|
||||||
DB45FADD25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift in Sources */,
|
DB45FADD25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift in Sources */,
|
||||||
|
|
|
@ -111,6 +111,10 @@ internal enum L10n {
|
||||||
/// Pick a Server,\nany server.
|
/// Pick a Server,\nany server.
|
||||||
internal static let title = L10n.tr("Localizable", "Scene.ServerPicker.Title")
|
internal static let title = L10n.tr("Localizable", "Scene.ServerPicker.Title")
|
||||||
internal enum Button {
|
internal enum Button {
|
||||||
|
/// See less
|
||||||
|
internal static let seeLess = L10n.tr("Localizable", "Scene.ServerPicker.Button.SeeLess")
|
||||||
|
/// See More
|
||||||
|
internal static let seeMore = L10n.tr("Localizable", "Scene.ServerPicker.Button.SeeMore")
|
||||||
internal enum Category {
|
internal enum Category {
|
||||||
/// All
|
/// All
|
||||||
internal static let all = L10n.tr("Localizable", "Scene.ServerPicker.Button.Category.All")
|
internal static let all = L10n.tr("Localizable", "Scene.ServerPicker.Button.Category.All")
|
||||||
|
@ -120,6 +124,14 @@ internal enum L10n {
|
||||||
/// Find a server or join your own...
|
/// Find a server or join your own...
|
||||||
internal static let placeholder = L10n.tr("Localizable", "Scene.ServerPicker.Input.Placeholder")
|
internal static let placeholder = L10n.tr("Localizable", "Scene.ServerPicker.Input.Placeholder")
|
||||||
}
|
}
|
||||||
|
internal enum Label {
|
||||||
|
/// CATEGORY
|
||||||
|
internal static let category = L10n.tr("Localizable", "Scene.ServerPicker.Label.Category")
|
||||||
|
/// LANGUAGE
|
||||||
|
internal static let language = L10n.tr("Localizable", "Scene.ServerPicker.Label.Language")
|
||||||
|
/// USERS
|
||||||
|
internal static let users = L10n.tr("Localizable", "Scene.ServerPicker.Label.Users")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
internal enum ServerRules {
|
internal enum ServerRules {
|
||||||
/// By continuing, you're subject to the terms of service and privacy policy for %@.
|
/// By continuing, you're subject to the terms of service and privacy policy for %@.
|
||||||
|
|
|
@ -33,6 +33,12 @@
|
||||||
"Scene.ServerPicker.Title" = "Pick a Server,
|
"Scene.ServerPicker.Title" = "Pick a Server,
|
||||||
any server.";
|
any server.";
|
||||||
"Scene.ServerPicker.Button.Category.All" = "All";
|
"Scene.ServerPicker.Button.Category.All" = "All";
|
||||||
|
"Scene.ServerPicker.Button.SeeLess" = "See less";
|
||||||
|
"Scene.ServerPicker.Button.SeeMore" = "See More";
|
||||||
|
"Scene.ServerPicker.Label.Language" = "LANGUAGE";
|
||||||
|
"Scene.ServerPicker.Label.Users" = "USERS";
|
||||||
|
"Scene.ServerPicker.Label.Category" = "CATEGORY";
|
||||||
|
|
||||||
"Scene.ServerRules.Button.Confirm" = "I Agree";
|
"Scene.ServerRules.Button.Confirm" = "I Agree";
|
||||||
"Scene.ServerRules.Prompt" = "By continuing, you're subject to the terms of service and privacy policy for %@.";
|
"Scene.ServerRules.Prompt" = "By continuing, you're subject to the terms of service and privacy policy for %@.";
|
||||||
"Scene.ServerRules.Subtitle" = "These rules are set by the admins of %@.";
|
"Scene.ServerRules.Subtitle" = "These rules are set by the admins of %@.";
|
||||||
|
|
|
@ -32,7 +32,8 @@ final class PickServerViewController: UIViewController, NeedsDependency {
|
||||||
let tableView = ControlContainableTableView()
|
let tableView = ControlContainableTableView()
|
||||||
tableView.register(PickServerTitleCell.self, forCellReuseIdentifier: String(describing: PickServerTitleCell.self))
|
tableView.register(PickServerTitleCell.self, forCellReuseIdentifier: String(describing: PickServerTitleCell.self))
|
||||||
tableView.register(PickServerCategoriesCell.self, forCellReuseIdentifier: String(describing: PickServerCategoriesCell.self))
|
tableView.register(PickServerCategoriesCell.self, forCellReuseIdentifier: String(describing: PickServerCategoriesCell.self))
|
||||||
// tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self))
|
tableView.register(PickServerSearchCell.self, forCellReuseIdentifier: String(describing: PickServerSearchCell.self))
|
||||||
|
tableView.register(PickServerCell.self, forCellReuseIdentifier: String(describing: PickServerCell.self))
|
||||||
tableView.rowHeight = UITableView.automaticDimension
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
tableView.separatorStyle = .none
|
tableView.separatorStyle = .none
|
||||||
tableView.backgroundColor = .clear
|
tableView.backgroundColor = .clear
|
||||||
|
@ -83,7 +84,20 @@ extension PickServerViewController {
|
||||||
nextStepButton.setTitle(L10n.Common.Controls.Actions.continue, for: .normal)
|
nextStepButton.setTitle(L10n.Common.Controls.Actions.continue, for: .normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewModel.tableView = tableView
|
||||||
tableView.delegate = viewModel
|
tableView.delegate = viewModel
|
||||||
tableView.dataSource = viewModel
|
tableView.dataSource = viewModel
|
||||||
|
|
||||||
|
viewModel.searchedServers
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { completion in
|
||||||
|
print("22")
|
||||||
|
} receiveValue: { [weak self] servers in
|
||||||
|
self?.tableView.reloadSections(IndexSet(integer: 3), with: .automatic)
|
||||||
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
|
|
||||||
|
viewModel.fetchAllServers()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ class PickServerViewModel: NSObject {
|
||||||
enum Section: CaseIterable {
|
enum Section: CaseIterable {
|
||||||
case title
|
case title
|
||||||
case categories
|
case categories
|
||||||
|
case search
|
||||||
case serverList
|
case serverList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,23 +33,24 @@ class PickServerViewModel: NSObject {
|
||||||
case .All:
|
case .All:
|
||||||
return L10n.Scene.ServerPicker.Button.Category.all
|
return L10n.Scene.ServerPicker.Button.Category.all
|
||||||
case .Some(let masCategory):
|
case .Some(let masCategory):
|
||||||
|
// TODO: Use emoji as placeholders
|
||||||
switch masCategory.category {
|
switch masCategory.category {
|
||||||
case .academia:
|
case .academia:
|
||||||
return "AC"
|
return "📚"
|
||||||
case .activism:
|
case .activism:
|
||||||
return "AT"
|
return "✊"
|
||||||
case .food:
|
case .food:
|
||||||
return "F"
|
return "🍕"
|
||||||
case .furry:
|
case .furry:
|
||||||
return "FU"
|
return "🦁"
|
||||||
case .games:
|
case .games:
|
||||||
return "G"
|
return "🕹"
|
||||||
case .general:
|
case .general:
|
||||||
return "GE"
|
return "GE"
|
||||||
case .journalism:
|
case .journalism:
|
||||||
return "JO"
|
return "📰"
|
||||||
case .lgbt:
|
case .lgbt:
|
||||||
return "LG"
|
return "🏳️🌈"
|
||||||
case .regional:
|
case .regional:
|
||||||
return "📍"
|
return "📍"
|
||||||
case .art:
|
case .art:
|
||||||
|
@ -58,7 +60,7 @@ class PickServerViewModel: NSObject {
|
||||||
case .tech:
|
case .tech:
|
||||||
return "📱"
|
return "📱"
|
||||||
case ._other:
|
case ._other:
|
||||||
return "UN"
|
return "❓"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,11 +74,15 @@ class PickServerViewModel: NSObject {
|
||||||
|
|
||||||
let searchText = CurrentValueSubject<String?, Never>(nil)
|
let searchText = CurrentValueSubject<String?, Never>(nil)
|
||||||
|
|
||||||
let allServers = CurrentValueSubject<[Mastodon.Entity.Instance], Error>([])
|
let allServers = CurrentValueSubject<[Mastodon.Entity.Server], Never>([])
|
||||||
let searchedServers = CurrentValueSubject<[Mastodon.Entity.Instance], Error>([])
|
let searchedServers = CurrentValueSubject<[Mastodon.Entity.Server], Error>([])
|
||||||
|
|
||||||
let nextButtonEnable = CurrentValueSubject<Bool, Never>(false)
|
let nextButtonEnable = CurrentValueSubject<Bool, Never>(false)
|
||||||
|
|
||||||
|
private var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
weak var tableView: UITableView?
|
||||||
|
|
||||||
init(context: AppContext, mode: PickServerMode) {
|
init(context: AppContext, mode: PickServerMode) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
|
@ -89,20 +95,89 @@ class PickServerViewModel: NSObject {
|
||||||
let masCategories = context.apiService.stubCategories()
|
let masCategories = context.apiService.stubCategories()
|
||||||
categories.append(.All)
|
categories.append(.All)
|
||||||
categories.append(contentsOf: masCategories.map { Category.Some($0) })
|
categories.append(contentsOf: masCategories.map { Category.Some($0) })
|
||||||
|
|
||||||
|
Publishers.CombineLatest3(
|
||||||
|
selectCategoryIndex,
|
||||||
|
searchText.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main).removeDuplicates(),
|
||||||
|
allServers
|
||||||
|
)
|
||||||
|
.flatMap { [weak self] (selectCategoryIndex, searchText, allServers) -> AnyPublisher<[Mastodon.Entity.Server], Error> in
|
||||||
|
guard let self = self else { return Just([]).setFailureType(to: Error.self).eraseToAnyPublisher() }
|
||||||
|
|
||||||
|
// 1. Search from the servers recorded in joinmastodon.org
|
||||||
|
let searchedServersFromAPI = self.searchServersFromAPI(category: self.categories[selectCategoryIndex], searchText: searchText, allServers: allServers)
|
||||||
|
if !searchedServersFromAPI.isEmpty {
|
||||||
|
// If found servers, just return
|
||||||
|
return Just(searchedServersFromAPI).setFailureType(to: Error.self).eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
// 2. No server found in the recorded list, check if searchText is a valid mastodon server domain
|
||||||
|
if let toSearchText = searchText, !toSearchText.isEmpty {
|
||||||
|
return self.context.apiService.instance(domain: toSearchText)
|
||||||
|
.map { return [Mastodon.Entity.Server(instance: $0.value)] }.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
return Just(searchedServersFromAPI).setFailureType(to: Error.self).eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
.sink { completion in
|
||||||
|
print("1")
|
||||||
|
} receiveValue: { [weak self] servers in
|
||||||
|
self?.searchedServers.send(servers)
|
||||||
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchAllServers() {
|
||||||
|
context.apiService.servers(language: nil, category: nil)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { error in
|
||||||
|
print("11")
|
||||||
|
} receiveValue: { [weak self] result in
|
||||||
|
self?.allServers.send(result.value)
|
||||||
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private func searchServersFromAPI(category: Category, searchText: String?, allServers: [Mastodon.Entity.Server]) -> [Mastodon.Entity.Server] {
|
||||||
|
return allServers
|
||||||
|
// 1. Filter the category
|
||||||
|
.filter {
|
||||||
|
switch category {
|
||||||
|
case .All:
|
||||||
|
return true
|
||||||
|
case .Some(let masCategory):
|
||||||
|
return $0.category.caseInsensitiveCompare(masCategory.category.rawValue) == .orderedSame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 2. Filter the searchText
|
||||||
|
.filter {
|
||||||
|
if let searchText = searchText {
|
||||||
|
return $0.domain.contains(searchText)
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PickServerViewModel: UITableViewDelegate {
|
extension PickServerViewModel: UITableViewDelegate {
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||||
if section == 0 {
|
let category = Section.allCases[section]
|
||||||
|
switch category {
|
||||||
|
case .title:
|
||||||
return 20
|
return 20
|
||||||
}
|
case .categories:
|
||||||
else if section == 1 {
|
// Since category view has a blur shadow effect, its height need to be large than the actual height,
|
||||||
|
// Thus we reduce the section header's height by 10, and make the category cell height 60+20(10 inset for top and bottom)
|
||||||
return 10
|
return 10
|
||||||
}
|
case .search:
|
||||||
else {
|
// Same reason as above
|
||||||
return 10
|
return 10
|
||||||
|
case .serverList:
|
||||||
|
// Header with 1 height as the separator
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,7 +196,8 @@ extension PickServerViewModel: UITableViewDataSource {
|
||||||
let section = Self.Section.allCases[section]
|
let section = Self.Section.allCases[section]
|
||||||
switch section {
|
switch section {
|
||||||
case .title,
|
case .title,
|
||||||
.categories:
|
.categories,
|
||||||
|
.search:
|
||||||
return 1
|
return 1
|
||||||
case .serverList:
|
case .serverList:
|
||||||
return searchedServers.value.count
|
return searchedServers.value.count
|
||||||
|
@ -140,8 +216,15 @@ extension PickServerViewModel: UITableViewDataSource {
|
||||||
cell.dataSource = self
|
cell.dataSource = self
|
||||||
cell.delegate = self
|
cell.delegate = self
|
||||||
return cell
|
return cell
|
||||||
|
case .search:
|
||||||
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerSearchCell.self), for: indexPath) as! PickServerSearchCell
|
||||||
|
cell.delegate = self
|
||||||
|
return cell
|
||||||
case .serverList:
|
case .serverList:
|
||||||
return UITableViewCell(style: .default, reuseIdentifier: "1")
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerCell.self), for: indexPath) as! PickServerCell
|
||||||
|
cell.server = searchedServers.value[indexPath.row]
|
||||||
|
cell.delegate = self
|
||||||
|
return cell
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,3 +246,17 @@ extension PickServerViewModel: PickServerCategoriesDataSource, PickServerCategor
|
||||||
selectCategoryIndex.send(index)
|
selectCategoryIndex.send(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension PickServerViewModel: PickServerSearchCellDelegate {
|
||||||
|
func pickServerSearchCell(didChange searchText: String?) {
|
||||||
|
self.searchText.send(searchText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PickServerViewModel: PickServerCellDelegate {
|
||||||
|
func pickServerCell(modeChange updates: (() -> Void)) {
|
||||||
|
tableView?.beginUpdates()
|
||||||
|
tableView?.performBatchUpdates(updates, completion: nil)
|
||||||
|
tableView?.endUpdates()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,322 @@
|
||||||
|
//
|
||||||
|
// PickServerCell.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by BradGao on 2021/2/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import MastodonSDK
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
protocol PickServerCellDelegate: class {
|
||||||
|
func pickServerCell(modeChange updates: (() -> Void))
|
||||||
|
}
|
||||||
|
|
||||||
|
class PickServerCell: UITableViewCell {
|
||||||
|
|
||||||
|
weak var delegate: PickServerCellDelegate?
|
||||||
|
|
||||||
|
enum Mode {
|
||||||
|
case collapse
|
||||||
|
case expand
|
||||||
|
}
|
||||||
|
|
||||||
|
private var bgView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = Asset.Colors.lightWhite.color
|
||||||
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var domainLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .preferredFont(forTextStyle: .headline)
|
||||||
|
label.textColor = Asset.Colors.lightDarkGray.color
|
||||||
|
label.adjustsFontForContentSizeCategory = true
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var checkbox: UIImageView = {
|
||||||
|
let imageView = UIImageView()
|
||||||
|
imageView.contentMode = .scaleAspectFill
|
||||||
|
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var descriptionLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .preferredFont(forTextStyle: .subheadline)
|
||||||
|
label.numberOfLines = 0
|
||||||
|
label.textColor = Asset.Colors.lightDarkGray.color
|
||||||
|
label.adjustsFontForContentSizeCategory = true
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var thumbImageView: UIImageView = {
|
||||||
|
let imageView = UIImageView()
|
||||||
|
imageView.clipsToBounds = true
|
||||||
|
imageView.contentMode = .scaleAspectFill
|
||||||
|
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var infoStackView: UIStackView = {
|
||||||
|
let stackView = UIStackView()
|
||||||
|
stackView.axis = .horizontal
|
||||||
|
stackView.alignment = .fill
|
||||||
|
stackView.distribution = .fillEqually
|
||||||
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
return stackView
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var expandBox: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = .clear
|
||||||
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var expandButton: UIButton = {
|
||||||
|
let button = UIButton(type: .system)
|
||||||
|
button.setTitle(L10n.Scene.ServerPicker.Button.seeMore, for: .normal)
|
||||||
|
button.setTitle(L10n.Scene.ServerPicker.Button.seeLess, for: .selected)
|
||||||
|
button.setTitleColor(Asset.Colors.lightBrandBlue.color, for: .normal)
|
||||||
|
button.titleLabel?.font = .preferredFont(forTextStyle: .footnote)
|
||||||
|
button.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var seperator: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = Asset.Colors.lightBackground.color
|
||||||
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var langValueLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.textColor = Asset.Colors.lightDarkGray.color
|
||||||
|
label.font = UIFontMetrics(forTextStyle: .title2).scaledFont(for: UIFont.systemFont(ofSize: 22, weight: .semibold))
|
||||||
|
label.textAlignment = .center
|
||||||
|
label.adjustsFontForContentSizeCategory = true
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var usersValueLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.textColor = Asset.Colors.lightDarkGray.color
|
||||||
|
label.font = UIFontMetrics(forTextStyle: .title2).scaledFont(for: UIFont.systemFont(ofSize: 22, weight: .semibold))
|
||||||
|
label.textAlignment = .center
|
||||||
|
label.adjustsFontForContentSizeCategory = true
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var categoryValueLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.textColor = Asset.Colors.lightDarkGray.color
|
||||||
|
label.font = UIFontMetrics(forTextStyle: .title2).scaledFont(for: UIFont.systemFont(ofSize: 22, weight: .semibold))
|
||||||
|
label.textAlignment = .center
|
||||||
|
label.adjustsFontForContentSizeCategory = true
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var langTitleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.textColor = Asset.Colors.lightDarkGray.color
|
||||||
|
label.font = .preferredFont(forTextStyle: .caption2)
|
||||||
|
label.text = L10n.Scene.ServerPicker.Label.language
|
||||||
|
label.textAlignment = .center
|
||||||
|
label.adjustsFontForContentSizeCategory = true
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var usersTitleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.textColor = Asset.Colors.lightDarkGray.color
|
||||||
|
label.font = .preferredFont(forTextStyle: .caption2)
|
||||||
|
label.text = L10n.Scene.ServerPicker.Label.users
|
||||||
|
label.textAlignment = .center
|
||||||
|
label.adjustsFontForContentSizeCategory = true
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var categoryTitleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.textColor = Asset.Colors.lightDarkGray.color
|
||||||
|
label.font = .preferredFont(forTextStyle: .caption2)
|
||||||
|
label.text = L10n.Scene.ServerPicker.Label.category
|
||||||
|
label.textAlignment = .center
|
||||||
|
label.adjustsFontForContentSizeCategory = true
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var collapseConstraints: [NSLayoutConstraint] = []
|
||||||
|
private var expandConstraints: [NSLayoutConstraint] = []
|
||||||
|
|
||||||
|
var mode: PickServerCell.Mode = .collapse {
|
||||||
|
didSet {
|
||||||
|
updateMode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var server: Mastodon.Entity.Server? {
|
||||||
|
didSet {
|
||||||
|
updateServerInfo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
|
_init()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
super.init(coder: coder)
|
||||||
|
_init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Methods to configure appearance
|
||||||
|
extension PickServerCell {
|
||||||
|
private func _init() {
|
||||||
|
selectionStyle = .none
|
||||||
|
backgroundColor = .clear
|
||||||
|
|
||||||
|
contentView.addSubview(bgView)
|
||||||
|
contentView.addSubview(domainLabel)
|
||||||
|
contentView.addSubview(checkbox)
|
||||||
|
contentView.addSubview(descriptionLabel)
|
||||||
|
contentView.addSubview(seperator)
|
||||||
|
|
||||||
|
contentView.addSubview(expandButton)
|
||||||
|
|
||||||
|
// Always add the expandbox which contains elements only visible in expand mode
|
||||||
|
contentView.addSubview(expandBox)
|
||||||
|
expandBox.addSubview(thumbImageView)
|
||||||
|
expandBox.addSubview(infoStackView)
|
||||||
|
expandBox.isHidden = true
|
||||||
|
|
||||||
|
let verticalInfoStackViewLang = makeVerticalInfoStackView(arrangedView: langValueLabel, langTitleLabel)
|
||||||
|
let verticalInfoStackViewUsers = makeVerticalInfoStackView(arrangedView: usersValueLabel, usersTitleLabel)
|
||||||
|
let verticalInfoStackViewCategory = makeVerticalInfoStackView(arrangedView: categoryValueLabel, categoryTitleLabel)
|
||||||
|
infoStackView.addArrangedSubview(verticalInfoStackViewLang)
|
||||||
|
infoStackView.addArrangedSubview(verticalInfoStackViewUsers)
|
||||||
|
infoStackView.addArrangedSubview(verticalInfoStackViewCategory)
|
||||||
|
|
||||||
|
let expandButtonTopConstraintInCollapse = expandButton.topAnchor.constraint(equalTo: descriptionLabel.lastBaselineAnchor, constant: 12)
|
||||||
|
collapseConstraints.append(expandButtonTopConstraintInCollapse)
|
||||||
|
|
||||||
|
let expandButtonTopConstraintInExpand = expandButton.topAnchor.constraint(equalTo: expandBox.bottomAnchor, constant: 8)
|
||||||
|
expandConstraints.append(expandButtonTopConstraintInExpand)
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
// Set background view
|
||||||
|
bgView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
|
||||||
|
bgView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||||
|
contentView.readableContentGuide.trailingAnchor.constraint(equalTo: bgView.trailingAnchor),
|
||||||
|
contentView.bottomAnchor.constraint(equalTo: bgView.bottomAnchor, constant: 1),
|
||||||
|
|
||||||
|
// Set bottom separator
|
||||||
|
seperator.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
|
||||||
|
contentView.readableContentGuide.trailingAnchor.constraint(equalTo: seperator.trailingAnchor),
|
||||||
|
contentView.bottomAnchor.constraint(equalTo: seperator.bottomAnchor),
|
||||||
|
seperator.heightAnchor.constraint(equalToConstant: 1),
|
||||||
|
|
||||||
|
domainLabel.leadingAnchor.constraint(equalTo: bgView.leadingAnchor, constant: 16),
|
||||||
|
domainLabel.topAnchor.constraint(equalTo: bgView.topAnchor, constant: 16),
|
||||||
|
|
||||||
|
checkbox.widthAnchor.constraint(equalToConstant: 23),
|
||||||
|
checkbox.heightAnchor.constraint(equalToConstant: 22),
|
||||||
|
bgView.trailingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: 16),
|
||||||
|
checkbox.leadingAnchor.constraint(equalTo: domainLabel.trailingAnchor, constant: 16),
|
||||||
|
|
||||||
|
descriptionLabel.leadingAnchor.constraint(equalTo: bgView.leadingAnchor, constant: 16),
|
||||||
|
descriptionLabel.topAnchor.constraint(equalTo: domainLabel.firstBaselineAnchor, constant: 8),
|
||||||
|
bgView.trailingAnchor.constraint(equalTo: descriptionLabel.trailingAnchor, constant: 16),
|
||||||
|
|
||||||
|
// Set expandBox constraints
|
||||||
|
expandBox.leadingAnchor.constraint(equalTo: bgView.leadingAnchor, constant: 16),
|
||||||
|
bgView.trailingAnchor.constraint(equalTo: expandBox.trailingAnchor, constant: 16),
|
||||||
|
expandBox.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: 8),
|
||||||
|
expandBox.bottomAnchor.constraint(equalTo: infoStackView.bottomAnchor),
|
||||||
|
|
||||||
|
thumbImageView.leadingAnchor.constraint(equalTo: expandBox.leadingAnchor),
|
||||||
|
expandBox.trailingAnchor.constraint(equalTo: thumbImageView.trailingAnchor),
|
||||||
|
thumbImageView.topAnchor.constraint(equalTo: expandBox.topAnchor),
|
||||||
|
thumbImageView.heightAnchor.constraint(equalTo: thumbImageView.widthAnchor, multiplier: 151.0 / 303.0),
|
||||||
|
|
||||||
|
infoStackView.leadingAnchor.constraint(equalTo: expandBox.leadingAnchor),
|
||||||
|
expandBox.trailingAnchor.constraint(equalTo: infoStackView.trailingAnchor),
|
||||||
|
infoStackView.topAnchor.constraint(equalTo: thumbImageView.bottomAnchor, constant: 16),
|
||||||
|
|
||||||
|
expandButton.leadingAnchor.constraint(equalTo: bgView.leadingAnchor, constant: 16),
|
||||||
|
bgView.trailingAnchor.constraint(equalTo: expandButton.trailingAnchor, constant: 16),
|
||||||
|
bgView.bottomAnchor.constraint(equalTo: expandButton.bottomAnchor, constant: 8),
|
||||||
|
])
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate(collapseConstraints)
|
||||||
|
|
||||||
|
expandButton.addTarget(self, action: #selector(expandButtonDidClicked(_:)), for: .touchUpInside)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private func makeVerticalInfoStackView(arrangedView: UIView...) -> UIStackView {
|
||||||
|
let stackView = UIStackView()
|
||||||
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
stackView.axis = .vertical
|
||||||
|
stackView.alignment = .center
|
||||||
|
stackView.distribution = .equalCentering
|
||||||
|
stackView.spacing = 2
|
||||||
|
arrangedView.forEach { stackView.addArrangedSubview($0) }
|
||||||
|
return stackView
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateMode() {
|
||||||
|
switch mode {
|
||||||
|
case .collapse:
|
||||||
|
expandBox.isHidden = true
|
||||||
|
NSLayoutConstraint.deactivate(expandConstraints)
|
||||||
|
NSLayoutConstraint.activate(collapseConstraints)
|
||||||
|
case .expand:
|
||||||
|
expandBox.isHidden = false
|
||||||
|
NSLayoutConstraint.activate(expandConstraints)
|
||||||
|
NSLayoutConstraint.deactivate(collapseConstraints)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
private func expandButtonDidClicked(_ sender: UIButton) {
|
||||||
|
delegate?.pickServerCell(modeChange: {
|
||||||
|
let newMode: Mode = mode == .collapse ? .expand : .collapse
|
||||||
|
self.mode = newMode
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Methods to update data
|
||||||
|
extension PickServerCell {
|
||||||
|
private func updateServerInfo() {
|
||||||
|
guard let serverInfo = server else { return }
|
||||||
|
domainLabel.text = serverInfo.domain
|
||||||
|
descriptionLabel.text = serverInfo.description
|
||||||
|
let processor = RoundCornerImageProcessor(cornerRadius: 3)
|
||||||
|
thumbImageView.kf.indicatorType = .activity
|
||||||
|
thumbImageView.kf.setImage(with: URL(string: serverInfo.proxiedThumbnail ?? "")!, placeholder: UIImage.placeholder(color: .yellow), options: [
|
||||||
|
.processor(processor),
|
||||||
|
.scaleFactor(UIScreen.main.scale),
|
||||||
|
.transition(.fade(1))
|
||||||
|
])
|
||||||
|
langValueLabel.text = serverInfo.language.uppercased()
|
||||||
|
usersValueLabel.text = "\(serverInfo.totalUsers)"
|
||||||
|
categoryValueLabel.text = serverInfo.category.uppercased()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
//
|
||||||
|
// PickServerSearchCell.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by BradGao on 2021/2/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
protocol PickServerSearchCellDelegate: class {
|
||||||
|
func pickServerSearchCell(didChange searchText: String?)
|
||||||
|
}
|
||||||
|
|
||||||
|
class PickServerSearchCell: UITableViewCell {
|
||||||
|
|
||||||
|
weak var delegate: PickServerSearchCellDelegate?
|
||||||
|
|
||||||
|
private var bgView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = Asset.Colors.lightWhite.color
|
||||||
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
view.layer.maskedCorners = [
|
||||||
|
.layerMinXMinYCorner,
|
||||||
|
.layerMaxXMinYCorner
|
||||||
|
]
|
||||||
|
view.layer.cornerCurve = .continuous
|
||||||
|
view.layer.cornerRadius = 10
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var textFieldBgView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = Asset.Colors.lightBackground.color.withAlphaComponent(0.6)
|
||||||
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
view.layer.masksToBounds = true
|
||||||
|
view.layer.cornerRadius = 6
|
||||||
|
view.layer.cornerCurve = .continuous
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var searchTextField: UITextField = {
|
||||||
|
let textField = UITextField()
|
||||||
|
textField.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
textField.font = .preferredFont(forTextStyle: .headline)
|
||||||
|
textField.tintColor = Asset.Colors.lightDarkGray.color
|
||||||
|
textField.textColor = Asset.Colors.lightDarkGray.color
|
||||||
|
textField.adjustsFontForContentSizeCategory = true
|
||||||
|
textField.attributedPlaceholder =
|
||||||
|
NSAttributedString(string: L10n.Scene.ServerPicker.Input.placeholder,
|
||||||
|
attributes: [.font: UIFont.preferredFont(forTextStyle: .headline),
|
||||||
|
.foregroundColor: Asset.Colors.lightSecondaryText.color.withAlphaComponent(0.6)])
|
||||||
|
textField.clearButtonMode = .whileEditing
|
||||||
|
return textField
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
|
_init()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
super.init(coder: coder)
|
||||||
|
_init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PickServerSearchCell {
|
||||||
|
private func _init() {
|
||||||
|
self.selectionStyle = .none
|
||||||
|
backgroundColor = .clear
|
||||||
|
|
||||||
|
searchTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
|
||||||
|
|
||||||
|
contentView.addSubview(bgView)
|
||||||
|
contentView.addSubview(textFieldBgView)
|
||||||
|
contentView.addSubview(searchTextField)
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
bgView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
|
||||||
|
bgView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||||
|
bgView.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
|
||||||
|
bgView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||||
|
|
||||||
|
textFieldBgView.leadingAnchor.constraint(equalTo: bgView.leadingAnchor, constant: 14),
|
||||||
|
textFieldBgView.topAnchor.constraint(equalTo: bgView.topAnchor, constant: 12),
|
||||||
|
bgView.trailingAnchor.constraint(equalTo: textFieldBgView.trailingAnchor, constant: 14),
|
||||||
|
bgView.bottomAnchor.constraint(equalTo: textFieldBgView.bottomAnchor, constant: 13),
|
||||||
|
|
||||||
|
searchTextField.leadingAnchor.constraint(equalTo: textFieldBgView.leadingAnchor, constant: 11),
|
||||||
|
searchTextField.topAnchor.constraint(equalTo: textFieldBgView.topAnchor, constant: 4),
|
||||||
|
textFieldBgView.trailingAnchor.constraint(equalTo: searchTextField.trailingAnchor, constant: 11),
|
||||||
|
textFieldBgView.bottomAnchor.constraint(equalTo: searchTextField.bottomAnchor, constant: 4),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PickServerSearchCell {
|
||||||
|
@objc func textFieldDidChange(_ textField: UITextField) {
|
||||||
|
delegate?.pickServerSearchCell(didChange: textField.text)
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,20 +54,12 @@ class PickServerCategoryView: UIView {
|
||||||
|
|
||||||
extension PickServerCategoryView {
|
extension PickServerCategoryView {
|
||||||
private func configure() {
|
private func configure() {
|
||||||
// bgShadowView.backgroundColor = nil
|
|
||||||
// addSubview(bgShadowView)
|
|
||||||
// bgShadowView.addSubview(bgView)
|
|
||||||
addSubview(bgView)
|
addSubview(bgView)
|
||||||
addSubview(titleLabel)
|
addSubview(titleLabel)
|
||||||
|
|
||||||
bgView.backgroundColor = .white
|
bgView.backgroundColor = Asset.Colors.lightWhite.color
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
// bgShadowView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
|
|
||||||
// bgShadowView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
|
|
||||||
// bgShadowView.topAnchor.constraint(equalTo: self.topAnchor),
|
|
||||||
// bgShadowView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
|
|
||||||
|
|
||||||
bgView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
|
bgView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
|
||||||
bgView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
|
bgView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
|
||||||
bgView.topAnchor.constraint(equalTo: self.topAnchor),
|
bgView.topAnchor.constraint(equalTo: self.topAnchor),
|
||||||
|
@ -94,10 +86,10 @@ extension PickServerCategoryView {
|
||||||
bgView.backgroundColor = Asset.Colors.lightBrandBlue.color
|
bgView.backgroundColor = Asset.Colors.lightBrandBlue.color
|
||||||
bgView.applyShadow(color: Asset.Colors.lightBrandBlue.color, alpha: 1, x: 0, y: 0, blur: 4.0)
|
bgView.applyShadow(color: Asset.Colors.lightBrandBlue.color, alpha: 1, x: 0, y: 0, blur: 4.0)
|
||||||
if case .All = category {
|
if case .All = category {
|
||||||
titleLabel.textColor = .white
|
titleLabel.textColor = Asset.Colors.lightWhite.color
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bgView.backgroundColor = .white
|
bgView.backgroundColor = Asset.Colors.lightWhite.color
|
||||||
bgView.applyShadow(color: Asset.Colors.lightBrandBlue.color, alpha: 0, x: 0, y: 0, blur: 0.0)
|
bgView.applyShadow(color: Asset.Colors.lightBrandBlue.color, alpha: 0, x: 0, y: 0, blur: 0.0)
|
||||||
if case .All = category {
|
if case .All = category {
|
||||||
titleLabel.textColor = Asset.Colors.lightBackground.color
|
titleLabel.textColor = Asset.Colors.lightBackground.color
|
||||||
|
|
|
@ -37,6 +37,21 @@ extension Mastodon.Entity {
|
||||||
case language
|
case language
|
||||||
case category
|
case category
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public init(instance: Instance) {
|
||||||
|
self.domain = instance.title
|
||||||
|
self.version = "\(instance.version)"
|
||||||
|
self.description = instance.description
|
||||||
|
self.language = instance.languages?.first ?? ""
|
||||||
|
self.languages = instance.languages ?? []
|
||||||
|
self.region = "Unknown" // TODO: how to handle properties not in an instance
|
||||||
|
self.categories = []
|
||||||
|
self.category = "Unknown"
|
||||||
|
self.proxiedThumbnail = instance.thumbnail
|
||||||
|
self.totalUsers = instance.statistics?.userCount ?? 0
|
||||||
|
self.lastWeekUsers = 0
|
||||||
|
self.approvalRequired = instance.approvalRequired ?? false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue