chore: [WIP] refactor pick server scene with diffable data source
This commit is contained in:
parent
652c286c71
commit
54c7610c7f
@ -96,6 +96,12 @@
|
|||||||
DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */; };
|
DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */; };
|
||||||
DB118A8225E4B6E600FAB162 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */; };
|
DB118A8225E4B6E600FAB162 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */; };
|
||||||
DB118A8C25E4BFB500FAB162 /* HighlightDimmableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */; };
|
DB118A8C25E4BFB500FAB162 /* HighlightDimmableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */; };
|
||||||
|
DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD43525F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift */; };
|
||||||
|
DB1FD44425F26CCC004CFCFC /* PickServerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD44325F26CCC004CFCFC /* PickServerSection.swift */; };
|
||||||
|
DB1FD44A25F26CD7004CFCFC /* PickServerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD44925F26CD7004CFCFC /* PickServerItem.swift */; };
|
||||||
|
DB1FD45025F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD44F25F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift */; };
|
||||||
|
DB1FD45A25F27898004CFCFC /* CategoryPickerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD45925F27898004CFCFC /* CategoryPickerItem.swift */; };
|
||||||
|
DB1FD46025F278AF004CFCFC /* CategoryPickerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD45F25F278AF004CFCFC /* CategoryPickerSection.swift */; };
|
||||||
DB2B3ABC25E37E15007045F9 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB2B3ABE25E37E15007045F9 /* InfoPlist.strings */; };
|
DB2B3ABC25E37E15007045F9 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB2B3ABE25E37E15007045F9 /* InfoPlist.strings */; };
|
||||||
DB2B3AE925E38850007045F9 /* UIViewPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2B3AE825E38850007045F9 /* UIViewPreview.swift */; };
|
DB2B3AE925E38850007045F9 /* UIViewPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2B3AE825E38850007045F9 /* UIViewPreview.swift */; };
|
||||||
DB2F073525E8ECF000957B2D /* AuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */; };
|
DB2F073525E8ECF000957B2D /* AuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */; };
|
||||||
@ -310,6 +316,12 @@
|
|||||||
DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Instance.swift"; sourceTree = "<group>"; };
|
DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Instance.swift"; sourceTree = "<group>"; };
|
||||||
DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||||
DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDimmableButton.swift; sourceTree = "<group>"; };
|
DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDimmableButton.swift; sourceTree = "<group>"; };
|
||||||
|
DB1FD43525F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonPickServerViewModel+LoadIndexedServerState.swift"; sourceTree = "<group>"; };
|
||||||
|
DB1FD44325F26CCC004CFCFC /* PickServerSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerSection.swift; sourceTree = "<group>"; };
|
||||||
|
DB1FD44925F26CD7004CFCFC /* PickServerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PickServerItem.swift; path = Mastodon/Diffiable/Section/PickServerItem.swift; sourceTree = SOURCE_ROOT; };
|
||||||
|
DB1FD44F25F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonPickServerViewModel+Diffable.swift"; sourceTree = "<group>"; };
|
||||||
|
DB1FD45925F27898004CFCFC /* CategoryPickerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryPickerItem.swift; sourceTree = "<group>"; };
|
||||||
|
DB1FD45F25F278AF004CFCFC /* CategoryPickerSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryPickerSection.swift; sourceTree = "<group>"; };
|
||||||
DB2B3ABD25E37E15007045F9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
DB2B3ABD25E37E15007045F9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
DB2B3AE825E38850007045F9 /* UIViewPreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewPreview.swift; sourceTree = "<group>"; };
|
DB2B3AE825E38850007045F9 /* UIViewPreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewPreview.swift; sourceTree = "<group>"; };
|
||||||
DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationViewModel.swift; sourceTree = "<group>"; };
|
DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationViewModel.swift; sourceTree = "<group>"; };
|
||||||
@ -456,11 +468,13 @@
|
|||||||
0FAA102525E1125D0017CCDE /* PickServer */ = {
|
0FAA102525E1125D0017CCDE /* PickServer */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
0FB3D31825E525DE00AAD544 /* CollectionViewCell */,
|
|
||||||
0FB3D30D25E525C000AAD544 /* View */,
|
0FB3D30D25E525C000AAD544 /* View */,
|
||||||
|
0FB3D31825E525DE00AAD544 /* CollectionViewCell */,
|
||||||
0FB3D2FC25E4CB4B00AAD544 /* TableViewCell */,
|
0FB3D2FC25E4CB4B00AAD544 /* TableViewCell */,
|
||||||
0FAA102625E1126A0017CCDE /* MastodonPickServerViewController.swift */,
|
0FAA102625E1126A0017CCDE /* MastodonPickServerViewController.swift */,
|
||||||
0FB3D2F625E4C24D00AAD544 /* MastodonPickServerViewModel.swift */,
|
0FB3D2F625E4C24D00AAD544 /* MastodonPickServerViewModel.swift */,
|
||||||
|
DB1FD44F25F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift */,
|
||||||
|
DB1FD43525F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift */,
|
||||||
);
|
);
|
||||||
path = PickServer;
|
path = PickServer;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -642,6 +656,8 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
2D76319E25C1521200929FB9 /* StatusSection.swift */,
|
2D76319E25C1521200929FB9 /* StatusSection.swift */,
|
||||||
|
DB1FD44325F26CCC004CFCFC /* PickServerSection.swift */,
|
||||||
|
DB1FD45F25F278AF004CFCFC /* CategoryPickerSection.swift */,
|
||||||
);
|
);
|
||||||
path = Section;
|
path = Section;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -683,6 +699,8 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
2D7631B225C159F700929FB9 /* Item.swift */,
|
2D7631B225C159F700929FB9 /* Item.swift */,
|
||||||
|
DB1FD44925F26CD7004CFCFC /* PickServerItem.swift */,
|
||||||
|
DB1FD45925F27898004CFCFC /* CategoryPickerItem.swift */,
|
||||||
);
|
);
|
||||||
path = Item;
|
path = Item;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -1464,8 +1482,10 @@
|
|||||||
2D38F1F725CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift in Sources */,
|
2D38F1F725CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift in Sources */,
|
||||||
0FB3D33225E5F50E00AAD544 /* PickServerSearchCell.swift in Sources */,
|
0FB3D33225E5F50E00AAD544 /* PickServerSearchCell.swift in Sources */,
|
||||||
2D5A3D3825CF8D9F002347D6 /* ScrollViewContainer.swift in Sources */,
|
2D5A3D3825CF8D9F002347D6 /* ScrollViewContainer.swift in Sources */,
|
||||||
|
DB1FD45A25F27898004CFCFC /* CategoryPickerItem.swift in Sources */,
|
||||||
0FAA101225E105390017CCDE /* PrimaryActionButton.swift in Sources */,
|
0FAA101225E105390017CCDE /* PrimaryActionButton.swift in Sources */,
|
||||||
DB8AF53025C13561002E6C99 /* AppContext.swift in Sources */,
|
DB8AF53025C13561002E6C99 /* AppContext.swift in Sources */,
|
||||||
|
DB1FD44A25F26CD7004CFCFC /* PickServerItem.swift in Sources */,
|
||||||
DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */,
|
DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */,
|
||||||
2D364F7225E66D7500204FDC /* MastodonResendEmailViewController.swift in Sources */,
|
2D364F7225E66D7500204FDC /* MastodonResendEmailViewController.swift in Sources */,
|
||||||
2D38F1F125CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift in Sources */,
|
2D38F1F125CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift in Sources */,
|
||||||
@ -1511,12 +1531,14 @@
|
|||||||
2DF75BA125D0E29D00694EC8 /* StatusProvider+TimelinePostTableViewCellDelegate.swift in Sources */,
|
2DF75BA125D0E29D00694EC8 /* StatusProvider+TimelinePostTableViewCellDelegate.swift in Sources */,
|
||||||
2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */,
|
2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */,
|
||||||
DB0140A125C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift in Sources */,
|
DB0140A125C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift in Sources */,
|
||||||
|
DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */,
|
||||||
2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */,
|
2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */,
|
||||||
2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */,
|
2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */,
|
||||||
DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */,
|
DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */,
|
||||||
DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */,
|
DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */,
|
||||||
2D38F1C625CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */,
|
2D38F1C625CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */,
|
||||||
DB6C8C0F25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift in Sources */,
|
DB6C8C0F25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift in Sources */,
|
||||||
|
DB1FD44425F26CCC004CFCFC /* PickServerSection.swift in Sources */,
|
||||||
0FB3D30F25E525CD00AAD544 /* PickServerCategoryView.swift in Sources */,
|
0FB3D30F25E525CD00AAD544 /* PickServerCategoryView.swift in Sources */,
|
||||||
DB2F073525E8ECF000957B2D /* AuthenticationViewModel.swift in Sources */,
|
DB2F073525E8ECF000957B2D /* AuthenticationViewModel.swift in Sources */,
|
||||||
DB68A04A25E9027700CFDF14 /* DarkContentStatusBarStyleNavigationController.swift in Sources */,
|
DB68A04A25E9027700CFDF14 /* DarkContentStatusBarStyleNavigationController.swift in Sources */,
|
||||||
@ -1533,10 +1555,12 @@
|
|||||||
DB118A8C25E4BFB500FAB162 /* HighlightDimmableButton.swift in Sources */,
|
DB118A8C25E4BFB500FAB162 /* HighlightDimmableButton.swift in Sources */,
|
||||||
DB084B5725CBC56C00F898ED /* Toot.swift in Sources */,
|
DB084B5725CBC56C00F898ED /* Toot.swift in Sources */,
|
||||||
DB0140A825C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift in Sources */,
|
DB0140A825C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift in Sources */,
|
||||||
|
DB1FD45025F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift in Sources */,
|
||||||
DB2B3AE925E38850007045F9 /* UIViewPreview.swift in Sources */,
|
DB2B3AE925E38850007045F9 /* UIViewPreview.swift in Sources */,
|
||||||
DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */,
|
DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */,
|
||||||
DB9D6C0E25E4F9780051B173 /* MosaicImageViewContainer.swift in Sources */,
|
DB9D6C0E25E4F9780051B173 /* MosaicImageViewContainer.swift in Sources */,
|
||||||
DB98338725C945ED00AD9700 /* Strings.swift in Sources */,
|
DB98338725C945ED00AD9700 /* Strings.swift in Sources */,
|
||||||
|
DB1FD46025F278AF004CFCFC /* CategoryPickerSection.swift in Sources */,
|
||||||
DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */,
|
DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */,
|
||||||
DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */,
|
DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */,
|
||||||
2D5A3D0325CF8742002347D6 /* ControlContainableScrollViews.swift in Sources */,
|
2D5A3D0325CF8742002347D6 /* ControlContainableScrollViews.swift in Sources */,
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
<key>Mastodon.xcscheme_^#shared#^_</key>
|
<key>Mastodon.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>12</integer>
|
<integer>8</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>SuppressBuildableAutocreation</key>
|
<key>SuppressBuildableAutocreation</key>
|
||||||
|
76
Mastodon/Diffiable/Item/CategoryPickerItem.swift
Normal file
76
Mastodon/Diffiable/Item/CategoryPickerItem.swift
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
//
|
||||||
|
// CategoryPickerItem.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by Cirno MainasuK on 2021/3/5.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import MastodonSDK
|
||||||
|
|
||||||
|
enum CategoryPickerItem {
|
||||||
|
case all
|
||||||
|
case category(category: Mastodon.Entity.Category)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CategoryPickerItem {
|
||||||
|
var title: String {
|
||||||
|
switch self {
|
||||||
|
case .all:
|
||||||
|
return L10n.Scene.ServerPicker.Button.Category.all
|
||||||
|
case .category(let category):
|
||||||
|
switch category.category {
|
||||||
|
case .academia:
|
||||||
|
return "📚"
|
||||||
|
case .activism:
|
||||||
|
return "✊"
|
||||||
|
case .food:
|
||||||
|
return "🍕"
|
||||||
|
case .furry:
|
||||||
|
return "🦁"
|
||||||
|
case .games:
|
||||||
|
return "🕹"
|
||||||
|
case .general:
|
||||||
|
return "💬"
|
||||||
|
case .journalism:
|
||||||
|
return "📰"
|
||||||
|
case .lgbt:
|
||||||
|
return "🏳️🌈"
|
||||||
|
case .regional:
|
||||||
|
return "📍"
|
||||||
|
case .art:
|
||||||
|
return "🎨"
|
||||||
|
case .music:
|
||||||
|
return "🎼"
|
||||||
|
case .tech:
|
||||||
|
return "📱"
|
||||||
|
case ._other:
|
||||||
|
return "❓"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CategoryPickerItem: Equatable {
|
||||||
|
static func == (lhs: CategoryPickerItem, rhs: CategoryPickerItem) -> Bool {
|
||||||
|
switch (lhs, rhs) {
|
||||||
|
case (.all, .all):
|
||||||
|
return true
|
||||||
|
case (.category(let categoryLeft), .category(let categoryRight)):
|
||||||
|
return categoryLeft.category.rawValue == categoryRight.category.rawValue
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CategoryPickerItem: Hashable {
|
||||||
|
func hash(into hasher: inout Hasher) {
|
||||||
|
switch self {
|
||||||
|
case .all:
|
||||||
|
hasher.combine(String(describing: CategoryPickerItem.all.self))
|
||||||
|
case .category(let category):
|
||||||
|
hasher.combine(category.category.rawValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -30,7 +30,7 @@ protocol StatusContentWarningAttribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension Item {
|
extension Item {
|
||||||
class StatusTimelineAttribute: Hashable, StatusContentWarningAttribute {
|
class StatusTimelineAttribute: Equatable, Hashable, StatusContentWarningAttribute {
|
||||||
var isStatusTextSensitive: Bool
|
var isStatusTextSensitive: Bool
|
||||||
var isStatusSensitive: Bool
|
var isStatusSensitive: Bool
|
||||||
|
|
||||||
@ -51,7 +51,6 @@ extension Item {
|
|||||||
hasher.combine(isStatusTextSensitive)
|
hasher.combine(isStatusTextSensitive)
|
||||||
hasher.combine(isStatusSensitive)
|
hasher.combine(isStatusSensitive)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
31
Mastodon/Diffiable/Section/CategoryPickerSection.swift
Normal file
31
Mastodon/Diffiable/Section/CategoryPickerSection.swift
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
//
|
||||||
|
// CategoryPickerSection.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by Cirno MainasuK on 2021/3/5.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
enum CategoryPickerSection: Equatable, Hashable {
|
||||||
|
case main
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CategoryPickerSection {
|
||||||
|
static func collectionViewDiffableDataSource(
|
||||||
|
for collectionView: UICollectionView,
|
||||||
|
dependency: NeedsDependency
|
||||||
|
) -> UICollectionViewDiffableDataSource<CategoryPickerSection, CategoryPickerItem> {
|
||||||
|
UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item -> UICollectionViewCell? in
|
||||||
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: PickServerCategoryCollectionViewCell.self), for: indexPath) as! PickServerCategoryCollectionViewCell
|
||||||
|
switch item {
|
||||||
|
case .all:
|
||||||
|
cell.categoryView.titleLabel.font = .systemFont(ofSize: 17)
|
||||||
|
case .category:
|
||||||
|
cell.categoryView.titleLabel.font = .systemFont(ofSize: 28)
|
||||||
|
}
|
||||||
|
cell.categoryView.titleLabel.text = item.title
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
67
Mastodon/Diffiable/Section/PickServerItem.swift
Normal file
67
Mastodon/Diffiable/Section/PickServerItem.swift
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
//
|
||||||
|
// PickServerItem.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by Cirno MainasuK on 2021/3/5.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import MastodonSDK
|
||||||
|
|
||||||
|
/// Note: update Equatable when change case
|
||||||
|
enum PickServerItem {
|
||||||
|
case header
|
||||||
|
case categoryPicker(items: [CategoryPickerItem])
|
||||||
|
case search
|
||||||
|
case server(server: Mastodon.Entity.Server, attribute: ServerItemAttribute)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PickServerItem {
|
||||||
|
final class ServerItemAttribute: Equatable, Hashable {
|
||||||
|
var isExpand: Bool
|
||||||
|
|
||||||
|
init(isExpand: Bool) {
|
||||||
|
self.isExpand = isExpand
|
||||||
|
}
|
||||||
|
|
||||||
|
static func == (lhs: PickServerItem.ServerItemAttribute, rhs: PickServerItem.ServerItemAttribute) -> Bool {
|
||||||
|
return lhs.isExpand == rhs.isExpand
|
||||||
|
}
|
||||||
|
|
||||||
|
func hash(into hasher: inout Hasher) {
|
||||||
|
hasher.combine(isExpand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PickServerItem: Equatable {
|
||||||
|
static func == (lhs: PickServerItem, rhs: PickServerItem) -> Bool {
|
||||||
|
switch (lhs, rhs) {
|
||||||
|
case (.header, .header):
|
||||||
|
return true
|
||||||
|
case (.categoryPicker(let itemsLeft), .categoryPicker(let itemsRight)):
|
||||||
|
return itemsLeft == itemsRight
|
||||||
|
case (.search, .search):
|
||||||
|
return true
|
||||||
|
case (.server(let serverLeft, _), .server(let serverRight, _)):
|
||||||
|
return serverLeft.domain == serverRight.domain
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PickServerItem: Hashable {
|
||||||
|
func hash(into hasher: inout Hasher) {
|
||||||
|
switch self {
|
||||||
|
case .header:
|
||||||
|
hasher.combine(String(describing: PickServerItem.header.self))
|
||||||
|
case .categoryPicker(let items):
|
||||||
|
hasher.combine(items)
|
||||||
|
case .search:
|
||||||
|
hasher.combine(String(describing: PickServerItem.search.self))
|
||||||
|
case .server(let server, _):
|
||||||
|
hasher.combine(server.domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
100
Mastodon/Diffiable/Section/PickServerSection.swift
Normal file
100
Mastodon/Diffiable/Section/PickServerSection.swift
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
//
|
||||||
|
// PickServerSection.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by Cirno MainasuK on 2021/3/5.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import MastodonSDK
|
||||||
|
import Kanna
|
||||||
|
|
||||||
|
enum PickServerSection: Equatable, Hashable {
|
||||||
|
case header
|
||||||
|
case category
|
||||||
|
case search
|
||||||
|
case servers
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PickServerSection {
|
||||||
|
static func tableViewDiffableDataSource(
|
||||||
|
for tableView: UITableView,
|
||||||
|
dependency: NeedsDependency,
|
||||||
|
pickServerSearchCellDelegate: PickServerSearchCellDelegate,
|
||||||
|
pickServerCellDelegate: PickServerCellDelegate
|
||||||
|
) -> UITableViewDiffableDataSource<PickServerSection, PickServerItem> {
|
||||||
|
UITableViewDiffableDataSource(tableView: tableView) { [weak pickServerSearchCellDelegate, weak pickServerCellDelegate] tableView, indexPath, item -> UITableViewCell? in
|
||||||
|
switch item {
|
||||||
|
case .header:
|
||||||
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerTitleCell.self), for: indexPath) as! PickServerTitleCell
|
||||||
|
return cell
|
||||||
|
case .categoryPicker(let items):
|
||||||
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerCategoriesCell.self), for: indexPath) as! PickServerCategoriesCell
|
||||||
|
cell.diffableDataSource = CategoryPickerSection.collectionViewDiffableDataSource(
|
||||||
|
for: cell.collectionView,
|
||||||
|
dependency: dependency
|
||||||
|
)
|
||||||
|
var snapshot = NSDiffableDataSourceSnapshot<CategoryPickerSection, CategoryPickerItem>()
|
||||||
|
snapshot.appendSections([.main])
|
||||||
|
snapshot.appendItems(items, toSection: .main)
|
||||||
|
cell.diffableDataSource?.apply(snapshot, animatingDifferences: false, completion: nil)
|
||||||
|
return cell
|
||||||
|
case .search:
|
||||||
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerSearchCell.self), for: indexPath) as! PickServerSearchCell
|
||||||
|
cell.delegate = pickServerSearchCellDelegate
|
||||||
|
return cell
|
||||||
|
case .server(let server, let attribute):
|
||||||
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerCell.self), for: indexPath) as! PickServerCell
|
||||||
|
PickServerSection.configure(cell: cell, server: server, attribute: attribute)
|
||||||
|
cell.delegate = pickServerCellDelegate
|
||||||
|
// cell.server = server
|
||||||
|
// if expandServerDomainSet.contains(server.domain) {
|
||||||
|
// cell.mode = .expand
|
||||||
|
// } else {
|
||||||
|
// cell.mode = .collapse
|
||||||
|
// }
|
||||||
|
// if server == viewModel.selectedServer.value {
|
||||||
|
// tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
|
||||||
|
// } else {
|
||||||
|
// tableView.deselectRow(at: indexPath, animated: false)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// cell.delegate = self
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PickServerSection {
|
||||||
|
|
||||||
|
static func configure(cell: PickServerCell, server: Mastodon.Entity.Server, attribute: PickServerItem.ServerItemAttribute) {
|
||||||
|
cell.domainLabel.text = server.domain
|
||||||
|
cell.descriptionLabel.text = {
|
||||||
|
guard let html = try? HTML(html: server.description, encoding: .utf8) else {
|
||||||
|
return server.description
|
||||||
|
}
|
||||||
|
|
||||||
|
return html.text ?? server.description
|
||||||
|
}()
|
||||||
|
cell.langValueLabel.text = server.language.uppercased()
|
||||||
|
cell.usersValueLabel.text = parseUsersCount(server.totalUsers)
|
||||||
|
cell.categoryValueLabel.text = server.category.uppercased()
|
||||||
|
|
||||||
|
cell.updateExpandMode(mode: attribute.isExpand ? .expand : .collapse)
|
||||||
|
// UIView.animate(withDuration: 0.33) {
|
||||||
|
// cell.expandBox.layoutIfNeeded()
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func parseUsersCount(_ usersCount: Int) -> String {
|
||||||
|
switch usersCount {
|
||||||
|
case 0..<1000:
|
||||||
|
return "\(usersCount)"
|
||||||
|
default:
|
||||||
|
let usersCountInThousand = Float(usersCount) / 1000.0
|
||||||
|
return String(format: "%.1fK", usersCountInThousand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -9,12 +9,6 @@ import UIKit
|
|||||||
|
|
||||||
class PickServerCategoryCollectionViewCell: UICollectionViewCell {
|
class PickServerCategoryCollectionViewCell: UICollectionViewCell {
|
||||||
|
|
||||||
var category: MastodonPickServerViewModel.Category? {
|
|
||||||
didSet {
|
|
||||||
categoryView.category = category
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var categoryView: PickServerCategoryView = {
|
var categoryView: PickServerCategoryView = {
|
||||||
let view = PickServerCategoryView()
|
let view = PickServerCategoryView()
|
||||||
view.translatesAutoresizingMaskIntoConstraints = false
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
@ -5,10 +5,9 @@
|
|||||||
// Created by BradGao on 2021/2/20.
|
// Created by BradGao on 2021/2/20.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import OSLog
|
|
||||||
import MastodonSDK
|
|
||||||
|
|
||||||
final class MastodonPickServerViewController: UIViewController, NeedsDependency {
|
final class MastodonPickServerViewController: UIViewController, NeedsDependency {
|
||||||
|
|
||||||
@ -23,13 +22,6 @@ final class MastodonPickServerViewController: UIViewController, NeedsDependency
|
|||||||
|
|
||||||
private var expandServerDomainSet = Set<String>()
|
private var expandServerDomainSet = Set<String>()
|
||||||
|
|
||||||
enum Section: CaseIterable {
|
|
||||||
case title
|
|
||||||
case categories
|
|
||||||
case search
|
|
||||||
case serverList
|
|
||||||
}
|
|
||||||
|
|
||||||
let tableView: UITableView = {
|
let tableView: UITableView = {
|
||||||
let tableView = ControlContainableTableView()
|
let tableView = ControlContainableTableView()
|
||||||
tableView.register(PickServerTitleCell.self, forCellReuseIdentifier: String(describing: PickServerTitleCell.self))
|
tableView.register(PickServerTitleCell.self, forCellReuseIdentifier: String(describing: PickServerTitleCell.self))
|
||||||
@ -95,31 +87,16 @@ extension MastodonPickServerViewController {
|
|||||||
nextStepButton.addTarget(self, action: #selector(nextStepButtonDidClicked(_:)), for: .touchUpInside)
|
nextStepButton.addTarget(self, action: #selector(nextStepButtonDidClicked(_:)), for: .touchUpInside)
|
||||||
|
|
||||||
tableView.delegate = self
|
tableView.delegate = self
|
||||||
tableView.dataSource = self
|
viewModel.setupDiffableDataSource(
|
||||||
|
for: tableView,
|
||||||
viewModel
|
dependency: self,
|
||||||
.searchedServers
|
pickServerSearchCellDelegate: self,
|
||||||
.receive(on: DispatchQueue.main)
|
pickServerCellDelegate: self
|
||||||
.sink { _ in
|
)
|
||||||
|
|
||||||
} receiveValue: { [weak self] servers in
|
|
||||||
self?.tableView.beginUpdates()
|
|
||||||
self?.tableView.reloadSections(IndexSet(integer: 3), with: .automatic)
|
|
||||||
self?.tableView.endUpdates()
|
|
||||||
if let selectedServer = self?.viewModel.selectedServer.value, servers.contains(selectedServer) {
|
|
||||||
// Previously selected server is still in the list, do nothing
|
|
||||||
} else {
|
|
||||||
// Previously selected server is not in the updated list, reset the selectedServer's value
|
|
||||||
self?.viewModel.selectedServer.send(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
|
|
||||||
viewModel
|
viewModel
|
||||||
.selectedServer
|
.selectedServer
|
||||||
.map {
|
.map { $0 != nil }
|
||||||
$0 != nil
|
|
||||||
}
|
|
||||||
.assign(to: \.isEnabled, on: nextStepButton)
|
.assign(to: \.isEnabled, on: nextStepButton)
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
@ -165,8 +142,6 @@ extension MastodonPickServerViewController {
|
|||||||
isAuthenticating ? self.nextStepButton.showLoading() : self.nextStepButton.stopLoading()
|
isAuthenticating ? self.nextStepButton.showLoading() : self.nextStepButton.stopLoading()
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
viewModel.fetchAllServers()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
@ -292,142 +267,150 @@ extension MastodonPickServerViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension MastodonPickServerViewController: UITableViewDelegate {
|
extension MastodonPickServerViewController: UITableViewDelegate {
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||||
|
return UIView()
|
||||||
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||||
let category = Section.allCases[section]
|
guard let diffableDataSource = viewModel.diffableDataSource else { return 0 }
|
||||||
switch category {
|
let sections = diffableDataSource.snapshot().sectionIdentifiers
|
||||||
case .title:
|
let section = sections[section]
|
||||||
|
switch section {
|
||||||
|
case .header:
|
||||||
return 20
|
return 20
|
||||||
case .categories:
|
case .category:
|
||||||
// Since category view has a blur shadow effect, its height need to be large than the actual height,
|
// 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)
|
// 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:
|
case .search:
|
||||||
// Same reason as above
|
// Same reason as above
|
||||||
return 10
|
return 10
|
||||||
case .serverList:
|
case .servers:
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
||||||
|
guard let diffableDataSource = viewModel.diffableDataSource else { return nil }
|
||||||
|
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return nil }
|
||||||
|
guard case let .server(server) = item else { return nil }
|
||||||
|
|
||||||
if tableView.indexPathForSelectedRow == indexPath {
|
if tableView.indexPathForSelectedRow == indexPath {
|
||||||
tableView.deselectRow(at: indexPath, animated: false)
|
tableView.deselectRow(at: indexPath, animated: false)
|
||||||
viewModel.selectedServer.send(nil)
|
viewModel.selectedServer.send(nil)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return indexPath
|
return indexPath
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
|
guard let diffableDataSource = viewModel.diffableDataSource else { return }
|
||||||
|
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
||||||
|
guard case let .server(server, _) = item else { return }
|
||||||
tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
|
tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
|
||||||
viewModel.selectedServer.send(viewModel.searchedServers.value[indexPath.row])
|
viewModel.selectedServer.send(server)
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
|
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
|
||||||
tableView.deselectRow(at: indexPath, animated: false)
|
tableView.deselectRow(at: indexPath, animated: false)
|
||||||
viewModel.selectedServer.send(nil)
|
viewModel.selectedServer.send(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MastodonPickServerViewController: UITableViewDataSource {
|
//extension MastodonPickServerViewController: UITableViewDataSource {
|
||||||
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
|
||||||
return UIView()
|
|
||||||
}
|
|
||||||
|
|
||||||
func numberOfSections(in tableView: UITableView) -> Int {
|
// func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
return Self.Section.allCases.count
|
//
|
||||||
}
|
// let section = Self.Section.allCases[indexPath.section]
|
||||||
|
// switch section {
|
||||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
// case .title:
|
||||||
let section = Self.Section.allCases[section]
|
//
|
||||||
switch section {
|
// case .categories:
|
||||||
case .title,
|
//
|
||||||
.categories,
|
// case .search:
|
||||||
.search:
|
//
|
||||||
return 1
|
// case .serverList:
|
||||||
case .serverList:
|
// let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerCell.self), for: indexPath) as! PickServerCell
|
||||||
return viewModel.searchedServers.value.count
|
// let server = viewModel.servers.value[indexPath.row]
|
||||||
}
|
// // cell.server = server
|
||||||
}
|
//// if expandServerDomainSet.contains(server.domain) {
|
||||||
|
//// cell.mode = .expand
|
||||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
//// } else {
|
||||||
|
//// cell.mode = .collapse
|
||||||
let section = Self.Section.allCases[indexPath.section]
|
//// }
|
||||||
switch section {
|
// if server == viewModel.selectedServer.value {
|
||||||
case .title:
|
// tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerTitleCell.self), for: indexPath) as! PickServerTitleCell
|
// } else {
|
||||||
return cell
|
// tableView.deselectRow(at: indexPath, animated: false)
|
||||||
case .categories:
|
// }
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerCategoriesCell.self), for: indexPath) as! PickServerCategoriesCell
|
//
|
||||||
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:
|
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerCell.self), for: indexPath) as! PickServerCell
|
|
||||||
let server = viewModel.searchedServers.value[indexPath.row]
|
|
||||||
cell.server = server
|
|
||||||
if expandServerDomainSet.contains(server.domain) {
|
|
||||||
cell.mode = .expand
|
|
||||||
} else {
|
|
||||||
cell.mode = .collapse
|
|
||||||
}
|
|
||||||
if server == viewModel.selectedServer.value {
|
|
||||||
tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
|
|
||||||
} else {
|
|
||||||
tableView.deselectRow(at: indexPath, animated: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
cell.delegate = self
|
|
||||||
return cell
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension MastodonPickServerViewController: PickServerCellDelegate {
|
|
||||||
func pickServerCell(modeChange server: Mastodon.Entity.Server, newMode: PickServerCell.Mode, updates: (() -> Void)) {
|
|
||||||
if newMode == .collapse {
|
|
||||||
expandServerDomainSet.remove(server.domain)
|
|
||||||
} else {
|
|
||||||
expandServerDomainSet.insert(server.domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
tableView.beginUpdates()
|
|
||||||
updates()
|
|
||||||
tableView.endUpdates()
|
|
||||||
|
|
||||||
if newMode == .expand, let modeChangeIndex = self.viewModel.searchedServers.value.firstIndex(where: { $0 == server }), self.tableView.indexPathsForVisibleRows?.last?.row == modeChangeIndex {
|
|
||||||
self.tableView.scrollToRow(at: IndexPath(row: modeChangeIndex, section: 3), at: .bottom, animated: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// MARK: - PickServerSearchCellDelegate
|
||||||
extension MastodonPickServerViewController: PickServerSearchCellDelegate {
|
extension MastodonPickServerViewController: PickServerSearchCellDelegate {
|
||||||
func pickServerSearchCell(didChange searchText: String?) {
|
func pickServerSearchCell(_ cell: PickServerSearchCell, searchTextDidChange searchText: String?) {
|
||||||
viewModel.searchText.send(searchText)
|
viewModel.searchText.send(searchText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MastodonPickServerViewController: PickServerCategoriesDataSource, PickServerCategoriesDelegate {
|
// MARK: - PickServerCellDelegate
|
||||||
func numberOfCategories() -> Int {
|
extension MastodonPickServerViewController: PickServerCellDelegate {
|
||||||
return viewModel.categories.count
|
func pickServerCell(_ cell: PickServerCell, expandButtonPressed button: UIButton) {
|
||||||
|
guard let diffableDataSource = viewModel.diffableDataSource else { return }
|
||||||
|
guard let indexPath = tableView.indexPath(for: cell) else { return }
|
||||||
|
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
||||||
|
guard case let .server(_, attribute) = item else { return }
|
||||||
|
|
||||||
|
attribute.isExpand.toggle()
|
||||||
|
tableView.beginUpdates()
|
||||||
|
cell.updateExpandMode(mode: attribute.isExpand ? .expand : .collapse)
|
||||||
|
tableView.endUpdates()
|
||||||
|
|
||||||
|
// expand attribute change do not needs apply snapshot to diffable data source
|
||||||
|
// but should I block the viewModel data binding during tableView.beginUpdates/endUpdates?
|
||||||
}
|
}
|
||||||
|
|
||||||
func category(at index: Int) -> MastodonPickServerViewModel.Category {
|
// func pickServerCell(modeChange server: Mastodon.Entity.Server, newMode: PickServerCell.Mode, updates: (() -> Void)) {
|
||||||
return viewModel.categories[index]
|
// if newMode == .collapse {
|
||||||
}
|
// expandServerDomainSet.remove(server.domain)
|
||||||
|
// } else {
|
||||||
func selectedIndex() -> Int {
|
// expandServerDomainSet.insert(server.domain)
|
||||||
return viewModel.selectCategoryIndex.value
|
// }
|
||||||
}
|
//
|
||||||
|
// tableView.beginUpdates()
|
||||||
func pickServerCategoriesCell(didSelect index: Int) {
|
// updates()
|
||||||
return viewModel.selectCategoryIndex.send(index)
|
// tableView.endUpdates()
|
||||||
}
|
//
|
||||||
|
// if newMode == .expand, let modeChangeIndex = self.viewModel.servers.value.firstIndex(where: { $0 == server }), self.tableView.indexPathsForVisibleRows?.last?.row == modeChangeIndex {
|
||||||
|
// self.tableView.scrollToRow(at: IndexPath(row: modeChangeIndex, section: 3), at: .bottom, animated: true)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//extension MastodonPickServerViewController: PickServerCategoriesDataSource, PickServerCategoriesCellDelegate {
|
||||||
|
// func numberOfCategories() -> Int {
|
||||||
|
// return viewModel.categories.count
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func category(at index: Int) -> MastodonPickServerViewModel.Category {
|
||||||
|
// return viewModel.categories[index]
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func selectedIndex() -> Int {
|
||||||
|
// return viewModel.selectCategoryIndex.value
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func pickServerCategoriesCell(_ cell: PickServerCategoriesCell, didSelect index: Int) {
|
||||||
|
// return viewModel.selectCategoryIndex.send(index)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
// MARK: - OnboardingViewControllerAppearance
|
// MARK: - OnboardingViewControllerAppearance
|
||||||
extension MastodonPickServerViewController: OnboardingViewControllerAppearance { }
|
extension MastodonPickServerViewController: OnboardingViewControllerAppearance { }
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
//
|
||||||
|
// MastodonPickServerViewController+Diffable.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by Cirno MainasuK on 2021/3/5.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
extension MastodonPickServerViewModel {
|
||||||
|
|
||||||
|
func setupDiffableDataSource(
|
||||||
|
for tableView: UITableView,
|
||||||
|
dependency: NeedsDependency,
|
||||||
|
pickServerSearchCellDelegate: PickServerSearchCellDelegate,
|
||||||
|
pickServerCellDelegate: PickServerCellDelegate
|
||||||
|
) {
|
||||||
|
diffableDataSource = PickServerSection.tableViewDiffableDataSource(
|
||||||
|
for: tableView,
|
||||||
|
dependency: dependency,
|
||||||
|
pickServerSearchCellDelegate: pickServerSearchCellDelegate,
|
||||||
|
pickServerCellDelegate: pickServerCellDelegate
|
||||||
|
)
|
||||||
|
|
||||||
|
var snapshot = NSDiffableDataSourceSnapshot<PickServerSection, PickServerItem>()
|
||||||
|
snapshot.appendSections([.header, .category, .search, .servers])
|
||||||
|
snapshot.appendItems([.header], toSection: .header)
|
||||||
|
snapshot.appendItems([.categoryPicker(items: categoryPickerItems)], toSection: .category)
|
||||||
|
snapshot.appendItems([.search], toSection: .search)
|
||||||
|
diffableDataSource?.apply(snapshot, animatingDifferences: false, completion: nil)
|
||||||
|
|
||||||
|
loadIndexedServerStateMachine.enter(LoadIndexedServerState.Loading.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,84 @@
|
|||||||
|
//
|
||||||
|
// MastodonPickServerViewModel+LoadIndexedServerState.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by Cirno MainasuK on 2021/3/5.
|
||||||
|
//
|
||||||
|
|
||||||
|
import os.log
|
||||||
|
import Foundation
|
||||||
|
import GameplayKit
|
||||||
|
import MastodonSDK
|
||||||
|
|
||||||
|
extension MastodonPickServerViewModel {
|
||||||
|
class LoadIndexedServerState: GKState {
|
||||||
|
weak var viewModel: MastodonPickServerViewModel?
|
||||||
|
|
||||||
|
init(viewModel: MastodonPickServerViewModel) {
|
||||||
|
self.viewModel = viewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didEnter(from previousState: GKState?) {
|
||||||
|
os_log("%{public}s[%{public}ld], %{public}s: enter %s, previous: %s", ((#file as NSString).lastPathComponent), #line, #function, self.debugDescription, previousState.debugDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension MastodonPickServerViewModel.LoadIndexedServerState {
|
||||||
|
|
||||||
|
class Initial: MastodonPickServerViewModel.LoadIndexedServerState {
|
||||||
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
|
return stateClass == Loading.self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Loading: MastodonPickServerViewModel.LoadIndexedServerState {
|
||||||
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
|
return stateClass == Fail.self || stateClass == Idle.self
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didEnter(from previousState: GKState?) {
|
||||||
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
|
guard let viewModel = self.viewModel, let stateMachine = self.stateMachine else { return }
|
||||||
|
viewModel.context.apiService.servers(language: nil, category: nil)
|
||||||
|
.sink { completion in
|
||||||
|
switch completion {
|
||||||
|
case .failure(let error):
|
||||||
|
// TODO: handle error
|
||||||
|
break
|
||||||
|
case .finished:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} receiveValue: { [weak self] response in
|
||||||
|
guard let _ = self else { return }
|
||||||
|
stateMachine.enter(Idle.self)
|
||||||
|
viewModel.indexedServers.value = response.value
|
||||||
|
}
|
||||||
|
.store(in: &viewModel.disposeBag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Fail: MastodonPickServerViewModel.LoadIndexedServerState {
|
||||||
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
|
return stateClass == Loading.self
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didEnter(from previousState: GKState?) {
|
||||||
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
|
guard let viewModel = self.viewModel, let stateMachine = self.stateMachine else { return }
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
stateMachine.enter(Loading.self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Idle: MastodonPickServerViewModel.LoadIndexedServerState {
|
||||||
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,9 +5,10 @@
|
|||||||
// Created by BradGao on 2021/2/23.
|
// Created by BradGao on 2021/2/23.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import OSLog
|
|
||||||
import Combine
|
import Combine
|
||||||
|
import GameplayKit
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
|
|
||||||
@ -17,69 +18,41 @@ class MastodonPickServerViewModel: NSObject {
|
|||||||
case signIn
|
case signIn
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Category {
|
var disposeBag = Set<AnyCancellable>()
|
||||||
// `all` means search for all categories
|
|
||||||
case all
|
|
||||||
// `some` means search for specific category
|
|
||||||
case some(Mastodon.Entity.Category)
|
|
||||||
|
|
||||||
var title: String {
|
|
||||||
switch self {
|
|
||||||
case .all:
|
|
||||||
return L10n.Scene.ServerPicker.Button.Category.all
|
|
||||||
case .some(let masCategory):
|
|
||||||
// TODO: Use emoji as placeholders
|
|
||||||
switch masCategory.category {
|
|
||||||
case .academia:
|
|
||||||
return "📚"
|
|
||||||
case .activism:
|
|
||||||
return "✊"
|
|
||||||
case .food:
|
|
||||||
return "🍕"
|
|
||||||
case .furry:
|
|
||||||
return "🦁"
|
|
||||||
case .games:
|
|
||||||
return "🕹"
|
|
||||||
case .general:
|
|
||||||
return "GE"
|
|
||||||
case .journalism:
|
|
||||||
return "📰"
|
|
||||||
case .lgbt:
|
|
||||||
return "🏳️🌈"
|
|
||||||
case .regional:
|
|
||||||
return "📍"
|
|
||||||
case .art:
|
|
||||||
return "🎨"
|
|
||||||
case .music:
|
|
||||||
return "🎼"
|
|
||||||
case .tech:
|
|
||||||
return "📱"
|
|
||||||
case ._other:
|
|
||||||
return "❓"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// input
|
||||||
let mode: PickServerMode
|
let mode: PickServerMode
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
|
var categoryPickerItems: [CategoryPickerItem] = {
|
||||||
var categories = [Category]()
|
var items: [CategoryPickerItem] = []
|
||||||
|
items.append(.all)
|
||||||
|
items.append(contentsOf: APIService.stubCategories().map { CategoryPickerItem.category(category: $0) })
|
||||||
|
return items
|
||||||
|
}()
|
||||||
let selectCategoryIndex = CurrentValueSubject<Int, Never>(0)
|
let selectCategoryIndex = CurrentValueSubject<Int, Never>(0)
|
||||||
|
|
||||||
let searchText = CurrentValueSubject<String?, Never>(nil)
|
let searchText = CurrentValueSubject<String?, Never>(nil)
|
||||||
|
let indexedServers = CurrentValueSubject<[Mastodon.Entity.Server], Never>([])
|
||||||
|
let unindexedServers = CurrentValueSubject<[Mastodon.Entity.Instance], Never>([])
|
||||||
|
|
||||||
let allServers = CurrentValueSubject<[Mastodon.Entity.Server], Never>([])
|
// output
|
||||||
let searchedServers = CurrentValueSubject<[Mastodon.Entity.Server], Error>([])
|
var diffableDataSource: UITableViewDiffableDataSource<PickServerSection, PickServerItem>?
|
||||||
|
private(set) lazy var loadIndexedServerStateMachine: GKStateMachine = {
|
||||||
|
// exclude timeline middle fetcher state
|
||||||
|
let stateMachine = GKStateMachine(states: [
|
||||||
|
LoadIndexedServerState.Initial(viewModel: self),
|
||||||
|
LoadIndexedServerState.Loading(viewModel: self),
|
||||||
|
LoadIndexedServerState.Fail(viewModel: self),
|
||||||
|
LoadIndexedServerState.Idle(viewModel: self),
|
||||||
|
])
|
||||||
|
stateMachine.enter(LoadIndexedServerState.Initial.self)
|
||||||
|
return stateMachine
|
||||||
|
}()
|
||||||
|
|
||||||
|
let servers = CurrentValueSubject<[Mastodon.Entity.Server], Error>([])
|
||||||
let selectedServer = CurrentValueSubject<Mastodon.Entity.Server?, Never>(nil)
|
let selectedServer = CurrentValueSubject<Mastodon.Entity.Server?, Never>(nil)
|
||||||
let error = PassthroughSubject<Error, Never>()
|
let error = PassthroughSubject<Error, Never>()
|
||||||
let authenticated = PassthroughSubject<(domain: String, account: Mastodon.Entity.Account), Never>()
|
let authenticated = PassthroughSubject<(domain: String, account: Mastodon.Entity.Account), Never>()
|
||||||
|
|
||||||
private var disposeBag = Set<AnyCancellable>()
|
|
||||||
|
|
||||||
weak var tableView: UITableView?
|
|
||||||
|
|
||||||
var mastodonPinBasedAuthenticationViewController: UIViewController?
|
var mastodonPinBasedAuthenticationViewController: UIViewController?
|
||||||
|
|
||||||
init(context: AppContext, mode: PickServerMode) {
|
init(context: AppContext, mode: PickServerMode) {
|
||||||
@ -91,83 +64,115 @@ class MastodonPickServerViewModel: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func configure() {
|
private func configure() {
|
||||||
let masCategories = context.apiService.stubCategories()
|
|
||||||
categories.append(.all)
|
|
||||||
categories.append(contentsOf: masCategories.map { Category.some($0) })
|
|
||||||
|
|
||||||
Publishers.CombineLatest3(
|
Publishers.CombineLatest3(
|
||||||
selectCategoryIndex,
|
indexedServers,
|
||||||
searchText.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main).removeDuplicates(),
|
unindexedServers,
|
||||||
allServers
|
searchText
|
||||||
)
|
)
|
||||||
.flatMap { [weak self] (selectCategoryIndex, searchText, allServers) -> AnyPublisher<Result<[Mastodon.Entity.Server], Error>, Never> in
|
.receive(on: DispatchQueue.main)
|
||||||
guard let self = self else { return Just(Result.success([])).eraseToAnyPublisher() }
|
.sink(receiveValue: { [weak self] indexedServers, unindexedServers, searchText in
|
||||||
|
guard let self = self else { return }
|
||||||
|
guard let diffableDataSource = self.diffableDataSource else { return }
|
||||||
|
|
||||||
// 1. Search from the servers recorded in joinmastodon.org
|
let oldSnapshot = diffableDataSource.snapshot()
|
||||||
let searchedServersFromAPI = self.searchServersFromAPI(category: self.categories[selectCategoryIndex], searchText: searchText, allServers: allServers)
|
var oldSnapshotServerItemAttributeDict: [String : PickServerItem.ServerItemAttribute] = [:]
|
||||||
if !searchedServersFromAPI.isEmpty {
|
for item in oldSnapshot.itemIdentifiers {
|
||||||
// If found servers, just return
|
guard case let .server(server, attribute) = item else { continue }
|
||||||
return Just(Result.success(searchedServersFromAPI)).eraseToAnyPublisher()
|
oldSnapshotServerItemAttributeDict[server.domain] = attribute
|
||||||
}
|
|
||||||
// 2. No server found in the recorded list, check if searchText is a valid mastodon server domain
|
|
||||||
if let toSearchText = searchText, !toSearchText.isEmpty, let _ = URL(string: "https://\(toSearchText)") {
|
|
||||||
return self.context.apiService.instance(domain: toSearchText)
|
|
||||||
.map { return Result.success([Mastodon.Entity.Server(instance: $0.value)]) }
|
|
||||||
.catch({ error -> Just<Result<[Mastodon.Entity.Server], Error>> in
|
|
||||||
return Just(Result.failure(error))
|
|
||||||
})
|
|
||||||
.eraseToAnyPublisher()
|
|
||||||
}
|
|
||||||
return Just(Result.success(searchedServersFromAPI)).eraseToAnyPublisher()
|
|
||||||
}
|
|
||||||
.sink { _ in
|
|
||||||
|
|
||||||
} receiveValue: { [weak self] result in
|
|
||||||
switch result {
|
|
||||||
case .success(let servers):
|
|
||||||
self?.searchedServers.send(servers)
|
|
||||||
case .failure(let error):
|
|
||||||
// TODO: What should be presented when user inputs invalid search text?
|
|
||||||
self?.searchedServers.send([])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
var snapshot = NSDiffableDataSourceSnapshot<PickServerSection, PickServerItem>()
|
||||||
|
snapshot.appendSections([.header, .category, .search, .servers])
|
||||||
|
snapshot.appendItems([.header], toSection: .header)
|
||||||
|
snapshot.appendItems([.categoryPicker(items: self.categoryPickerItems)], toSection: .category)
|
||||||
|
snapshot.appendItems([.search], toSection: .search)
|
||||||
|
// TODO: handle filter
|
||||||
|
var serverItems: [PickServerItem] = []
|
||||||
|
for server in indexedServers {
|
||||||
|
let attribute = oldSnapshotServerItemAttributeDict[server.domain] ?? PickServerItem.ServerItemAttribute(isExpand: false)
|
||||||
|
let item = PickServerItem.server(server: server, attribute: attribute)
|
||||||
|
serverItems.append(item)
|
||||||
|
}
|
||||||
|
snapshot.appendItems(serverItems, toSection: .servers)
|
||||||
|
|
||||||
|
diffableDataSource.apply(snapshot)
|
||||||
|
})
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
|
|
||||||
}
|
// Publishers.CombineLatest3(
|
||||||
|
// selectCategoryIndex,
|
||||||
|
// searchText.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main).removeDuplicates(),
|
||||||
|
// indexedServers
|
||||||
|
// )
|
||||||
|
// .flatMap { [weak self] (selectCategoryIndex, searchText, allServers) -> AnyPublisher<Result<[Mastodon.Entity.Server], Error>, Never> in
|
||||||
|
// guard let self = self else { return Just(Result.success([])).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(Result.success(searchedServersFromAPI)).eraseToAnyPublisher()
|
||||||
|
// }
|
||||||
|
// // 2. No server found in the recorded list, check if searchText is a valid mastodon server domain
|
||||||
|
// if let toSearchText = searchText, !toSearchText.isEmpty, let _ = URL(string: "https://\(toSearchText)") {
|
||||||
|
// return self.context.apiService.instance(domain: toSearchText)
|
||||||
|
// .map { return Result.success([Mastodon.Entity.Server(instance: $0.value)]) }
|
||||||
|
// .catch({ error -> Just<Result<[Mastodon.Entity.Server], Error>> in
|
||||||
|
// return Just(Result.failure(error))
|
||||||
|
// })
|
||||||
|
// .eraseToAnyPublisher()
|
||||||
|
// }
|
||||||
|
// return Just(Result.success(searchedServersFromAPI)).eraseToAnyPublisher()
|
||||||
|
// }
|
||||||
|
// .sink { _ in
|
||||||
|
//
|
||||||
|
// } receiveValue: { [weak self] result in
|
||||||
|
// switch result {
|
||||||
|
// case .success(let servers):
|
||||||
|
// self?.servers.send(servers)
|
||||||
|
// case .failure(let error):
|
||||||
|
// // TODO: What should be presented when user inputs invalid search text?
|
||||||
|
// self?.servers.send([])
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// .store(in: &disposeBag)
|
||||||
|
|
||||||
func fetchAllServers() {
|
|
||||||
context.apiService.servers(language: nil, category: nil)
|
|
||||||
.sink { completion in
|
|
||||||
// TODO: Add a reload button when fails to fetch servers initially
|
|
||||||
} 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] {
|
// func fetchAllServers() {
|
||||||
return allServers
|
// context.apiService.servers(language: nil, category: nil)
|
||||||
// 1. Filter the category
|
// .sink { completion in
|
||||||
.filter {
|
// // TODO: Add a reload button when fails to fetch servers initially
|
||||||
switch category {
|
// } receiveValue: { [weak self] result in
|
||||||
case .all:
|
// self?.indexedServers.send(result.value)
|
||||||
return true
|
// }
|
||||||
case .some(let masCategory):
|
// .store(in: &disposeBag)
|
||||||
return $0.category.caseInsensitiveCompare(masCategory.category.rawValue) == .orderedSame
|
//
|
||||||
}
|
// }
|
||||||
}
|
//
|
||||||
// 2. Filter the searchText
|
// private func searchServersFromAPI(category: Category, searchText: String?, allServers: [Mastodon.Entity.Server]) -> [Mastodon.Entity.Server] {
|
||||||
.filter {
|
// return allServers
|
||||||
if let searchText = searchText, !searchText.isEmpty {
|
// // 1. Filter the category
|
||||||
return $0.domain.lowercased().contains(searchText.lowercased())
|
// .filter {
|
||||||
} else {
|
// switch category {
|
||||||
return true
|
// 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, !searchText.isEmpty {
|
||||||
|
// return $0.domain.lowercased().contains(searchText.lowercased())
|
||||||
|
// } else {
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - SignIn methods & structs
|
// MARK: - SignIn methods & structs
|
||||||
|
@ -5,23 +5,19 @@
|
|||||||
// Created by BradGao on 2021/2/23.
|
// Created by BradGao on 2021/2/23.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
|
||||||
protocol PickServerCategoriesDataSource: class {
|
protocol PickServerCategoriesCellDelegate: class {
|
||||||
func numberOfCategories() -> Int
|
func pickServerCategoriesCell(_ cell: PickServerCategoriesCell, collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
|
||||||
func category(at index: Int) -> MastodonPickServerViewModel.Category
|
|
||||||
func selectedIndex() -> Int
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol PickServerCategoriesDelegate: class {
|
|
||||||
func pickServerCategoriesCell(didSelect index: Int)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final class PickServerCategoriesCell: UITableViewCell {
|
final class PickServerCategoriesCell: UITableViewCell {
|
||||||
|
|
||||||
weak var dataSource: PickServerCategoriesDataSource!
|
weak var delegate: PickServerCategoriesCellDelegate?
|
||||||
weak var delegate: PickServerCategoriesDelegate!
|
|
||||||
|
var diffableDataSource: UICollectionViewDiffableDataSource<CategoryPickerSection, CategoryPickerItem>?
|
||||||
|
|
||||||
let metricView = UIView()
|
let metricView = UIView()
|
||||||
|
|
||||||
@ -38,6 +34,12 @@ final class PickServerCategoriesCell: UITableViewCell {
|
|||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
override func prepareForReuse() {
|
||||||
|
super.prepareForReuse()
|
||||||
|
|
||||||
|
delegate = nil
|
||||||
|
}
|
||||||
|
|
||||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
_init()
|
_init()
|
||||||
@ -75,7 +77,6 @@ extension PickServerCategoriesCell {
|
|||||||
])
|
])
|
||||||
|
|
||||||
collectionView.delegate = self
|
collectionView.delegate = self
|
||||||
collectionView.dataSource = self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func layoutSubviews() {
|
override func layoutSubviews() {
|
||||||
@ -86,10 +87,11 @@ extension PickServerCategoriesCell {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - UICollectionViewDelegateFlowLayout
|
||||||
extension PickServerCategoriesCell: UICollectionViewDelegateFlowLayout {
|
extension PickServerCategoriesCell: UICollectionViewDelegateFlowLayout {
|
||||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
|
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
|
||||||
delegate.pickServerCategoriesCell(didSelect: indexPath.row)
|
// delegate.pickServerCategoriesCell(self, didSelect: indexPath.row)
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
|
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
|
||||||
@ -106,25 +108,25 @@ extension PickServerCategoriesCell: UICollectionViewDelegateFlowLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PickServerCategoriesCell: UICollectionViewDataSource {
|
//extension PickServerCategoriesCell: UICollectionViewDataSource {
|
||||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
// func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||||
return dataSource.numberOfCategories()
|
// return dataSource.numberOfCategories()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
// func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||||
let category = dataSource.category(at: indexPath.row)
|
// let category = dataSource.category(at: indexPath.row)
|
||||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: PickServerCategoryCollectionViewCell.self), for: indexPath) as! PickServerCategoryCollectionViewCell
|
// let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: PickServerCategoryCollectionViewCell.self), for: indexPath) as! PickServerCategoryCollectionViewCell
|
||||||
cell.category = category
|
// cell.category = category
|
||||||
|
//
|
||||||
// Select the default category by default
|
// // Select the default category by default
|
||||||
if indexPath.row == dataSource.selectedIndex() {
|
// if indexPath.row == dataSource.selectedIndex() {
|
||||||
// Use `[]` as the scrollPosition to avoid contentOffset change
|
// // Use `[]` as the scrollPosition to avoid contentOffset change
|
||||||
collectionView.selectItem(at: indexPath, animated: false, scrollPosition: [])
|
// collectionView.selectItem(at: indexPath, animated: false, scrollPosition: [])
|
||||||
cell.isSelected = true
|
// cell.isSelected = true
|
||||||
}
|
// }
|
||||||
return cell
|
// return cell
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
@ -5,25 +5,21 @@
|
|||||||
// Created by BradGao on 2021/2/24.
|
// Created by BradGao on 2021/2/24.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import AlamofireImage
|
import AlamofireImage
|
||||||
import Kanna
|
import Kanna
|
||||||
|
|
||||||
protocol PickServerCellDelegate: class {
|
protocol PickServerCellDelegate: class {
|
||||||
func pickServerCell(modeChange server: Mastodon.Entity.Server, newMode: PickServerCell.Mode, updates: (() -> Void))
|
func pickServerCell(_ cell: PickServerCell, expandButtonPressed button: UIButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
class PickServerCell: UITableViewCell {
|
class PickServerCell: UITableViewCell {
|
||||||
|
|
||||||
weak var delegate: PickServerCellDelegate?
|
weak var delegate: PickServerCellDelegate?
|
||||||
|
|
||||||
enum Mode {
|
let containerView: UIView = {
|
||||||
case collapse
|
|
||||||
case expand
|
|
||||||
}
|
|
||||||
|
|
||||||
private var containerView: UIView = {
|
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
view.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 10, right: 16)
|
view.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 10, right: 16)
|
||||||
view.backgroundColor = Asset.Colors.lightWhite.color
|
view.backgroundColor = Asset.Colors.lightWhite.color
|
||||||
@ -31,7 +27,7 @@ class PickServerCell: UITableViewCell {
|
|||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private var domainLabel: UILabel = {
|
let domainLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.font = .preferredFont(forTextStyle: .headline)
|
label.font = .preferredFont(forTextStyle: .headline)
|
||||||
label.textColor = Asset.Colors.lightDarkGray.color
|
label.textColor = Asset.Colors.lightDarkGray.color
|
||||||
@ -40,7 +36,7 @@ class PickServerCell: UITableViewCell {
|
|||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private var checkbox: UIImageView = {
|
let checkbox: UIImageView = {
|
||||||
let imageView = UIImageView()
|
let imageView = UIImageView()
|
||||||
imageView.preferredSymbolConfiguration = UIImage.SymbolConfiguration(textStyle: .body)
|
imageView.preferredSymbolConfiguration = UIImage.SymbolConfiguration(textStyle: .body)
|
||||||
imageView.tintColor = Asset.Colors.lightSecondaryText.color
|
imageView.tintColor = Asset.Colors.lightSecondaryText.color
|
||||||
@ -49,7 +45,7 @@ class PickServerCell: UITableViewCell {
|
|||||||
return imageView
|
return imageView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private var descriptionLabel: UILabel = {
|
let descriptionLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.font = .preferredFont(forTextStyle: .subheadline)
|
label.font = .preferredFont(forTextStyle: .subheadline)
|
||||||
label.numberOfLines = 0
|
label.numberOfLines = 0
|
||||||
@ -59,9 +55,9 @@ class PickServerCell: UITableViewCell {
|
|||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private let thumbnailActivityIdicator = UIActivityIndicatorView(style: .medium)
|
let thumbnailActivityIdicator = UIActivityIndicatorView(style: .medium)
|
||||||
|
|
||||||
private var thumbnailImageView: UIImageView = {
|
let thumbnailImageView: UIImageView = {
|
||||||
let imageView = UIImageView()
|
let imageView = UIImageView()
|
||||||
imageView.clipsToBounds = true
|
imageView.clipsToBounds = true
|
||||||
imageView.contentMode = .scaleAspectFill
|
imageView.contentMode = .scaleAspectFill
|
||||||
@ -69,7 +65,7 @@ class PickServerCell: UITableViewCell {
|
|||||||
return imageView
|
return imageView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private var infoStackView: UIStackView = {
|
let infoStackView: UIStackView = {
|
||||||
let stackView = UIStackView()
|
let stackView = UIStackView()
|
||||||
stackView.axis = .horizontal
|
stackView.axis = .horizontal
|
||||||
stackView.alignment = .fill
|
stackView.alignment = .fill
|
||||||
@ -78,14 +74,14 @@ class PickServerCell: UITableViewCell {
|
|||||||
return stackView
|
return stackView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private var expandBox: UIView = {
|
let expandBox: UIView = {
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
view.backgroundColor = .clear
|
view.backgroundColor = .clear
|
||||||
view.translatesAutoresizingMaskIntoConstraints = false
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private var expandButton: UIButton = {
|
let expandButton: UIButton = {
|
||||||
let button = UIButton(type: .custom)
|
let button = UIButton(type: .custom)
|
||||||
button.setTitle(L10n.Scene.ServerPicker.Button.seemore, for: .normal)
|
button.setTitle(L10n.Scene.ServerPicker.Button.seemore, for: .normal)
|
||||||
button.setTitle(L10n.Scene.ServerPicker.Button.seeless, for: .selected)
|
button.setTitle(L10n.Scene.ServerPicker.Button.seeless, for: .selected)
|
||||||
@ -95,14 +91,14 @@ class PickServerCell: UITableViewCell {
|
|||||||
return button
|
return button
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private var seperator: UIView = {
|
let seperator: UIView = {
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
view.backgroundColor = Asset.Colors.lightBackground.color
|
view.backgroundColor = Asset.Colors.lightBackground.color
|
||||||
view.translatesAutoresizingMaskIntoConstraints = false
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private var langValueLabel: UILabel = {
|
let langValueLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.textColor = Asset.Colors.lightDarkGray.color
|
label.textColor = Asset.Colors.lightDarkGray.color
|
||||||
label.font = UIFontMetrics(forTextStyle: .title2).scaledFont(for: UIFont.systemFont(ofSize: 22, weight: .semibold))
|
label.font = UIFontMetrics(forTextStyle: .title2).scaledFont(for: UIFont.systemFont(ofSize: 22, weight: .semibold))
|
||||||
@ -112,7 +108,7 @@ class PickServerCell: UITableViewCell {
|
|||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private var usersValueLabel: UILabel = {
|
let usersValueLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.textColor = Asset.Colors.lightDarkGray.color
|
label.textColor = Asset.Colors.lightDarkGray.color
|
||||||
label.font = UIFontMetrics(forTextStyle: .title2).scaledFont(for: UIFont.systemFont(ofSize: 22, weight: .semibold))
|
label.font = UIFontMetrics(forTextStyle: .title2).scaledFont(for: UIFont.systemFont(ofSize: 22, weight: .semibold))
|
||||||
@ -122,7 +118,7 @@ class PickServerCell: UITableViewCell {
|
|||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private var categoryValueLabel: UILabel = {
|
let categoryValueLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.textColor = Asset.Colors.lightDarkGray.color
|
label.textColor = Asset.Colors.lightDarkGray.color
|
||||||
label.font = UIFontMetrics(forTextStyle: .title2).scaledFont(for: UIFont.systemFont(ofSize: 22, weight: .semibold))
|
label.font = UIFontMetrics(forTextStyle: .title2).scaledFont(for: UIFont.systemFont(ofSize: 22, weight: .semibold))
|
||||||
@ -132,7 +128,7 @@ class PickServerCell: UITableViewCell {
|
|||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private var langTitleLabel: UILabel = {
|
let langTitleLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.textColor = Asset.Colors.lightDarkGray.color
|
label.textColor = Asset.Colors.lightDarkGray.color
|
||||||
label.font = .preferredFont(forTextStyle: .caption2)
|
label.font = .preferredFont(forTextStyle: .caption2)
|
||||||
@ -143,7 +139,7 @@ class PickServerCell: UITableViewCell {
|
|||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private var usersTitleLabel: UILabel = {
|
let usersTitleLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.textColor = Asset.Colors.lightDarkGray.color
|
label.textColor = Asset.Colors.lightDarkGray.color
|
||||||
label.font = .preferredFont(forTextStyle: .caption2)
|
label.font = .preferredFont(forTextStyle: .caption2)
|
||||||
@ -154,7 +150,7 @@ class PickServerCell: UITableViewCell {
|
|||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private var categoryTitleLabel: UILabel = {
|
let categoryTitleLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.textColor = Asset.Colors.lightDarkGray.color
|
label.textColor = Asset.Colors.lightDarkGray.color
|
||||||
label.font = .preferredFont(forTextStyle: .caption2)
|
label.font = .preferredFont(forTextStyle: .caption2)
|
||||||
@ -168,22 +164,12 @@ class PickServerCell: UITableViewCell {
|
|||||||
private var collapseConstraints: [NSLayoutConstraint] = []
|
private var collapseConstraints: [NSLayoutConstraint] = []
|
||||||
private var expandConstraints: [NSLayoutConstraint] = []
|
private var expandConstraints: [NSLayoutConstraint] = []
|
||||||
|
|
||||||
var mode: PickServerCell.Mode = .collapse {
|
|
||||||
didSet {
|
|
||||||
updateMode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var server: Mastodon.Entity.Server? {
|
|
||||||
didSet {
|
|
||||||
updateServerInfo()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func prepareForReuse() {
|
override func prepareForReuse() {
|
||||||
super.prepareForReuse()
|
super.prepareForReuse()
|
||||||
|
|
||||||
|
thumbnailImageView.isHidden = false
|
||||||
thumbnailImageView.af.cancelImageRequest()
|
thumbnailImageView.af.cancelImageRequest()
|
||||||
|
thumbnailActivityIdicator.stopAnimating()
|
||||||
}
|
}
|
||||||
|
|
||||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
@ -195,6 +181,7 @@ class PickServerCell: UITableViewCell {
|
|||||||
super.init(coder: coder)
|
super.init(coder: coder)
|
||||||
_init()
|
_init()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Methods to configure appearance
|
// MARK: - Methods to configure appearance
|
||||||
@ -224,7 +211,7 @@ extension PickServerCell {
|
|||||||
infoStackView.addArrangedSubview(verticalInfoStackViewUsers)
|
infoStackView.addArrangedSubview(verticalInfoStackViewUsers)
|
||||||
infoStackView.addArrangedSubview(verticalInfoStackViewCategory)
|
infoStackView.addArrangedSubview(verticalInfoStackViewCategory)
|
||||||
|
|
||||||
let expandButtonTopConstraintInCollapse = expandButton.topAnchor.constraint(equalTo: descriptionLabel.lastBaselineAnchor, constant: 12).priority(.required)
|
let expandButtonTopConstraintInCollapse = expandButton.topAnchor.constraint(equalTo: descriptionLabel.lastBaselineAnchor, constant: 12).priority(.required - 1)
|
||||||
collapseConstraints.append(expandButtonTopConstraintInCollapse)
|
collapseConstraints.append(expandButtonTopConstraintInCollapse)
|
||||||
|
|
||||||
let expandButtonTopConstraintInExpand = expandButton.topAnchor.constraint(equalTo: expandBox.bottomAnchor, constant: 8).priority(.defaultHigh)
|
let expandButtonTopConstraintInExpand = expandButton.topAnchor.constraint(equalTo: expandBox.bottomAnchor, constant: 8).priority(.defaultHigh)
|
||||||
@ -292,7 +279,7 @@ extension PickServerCell {
|
|||||||
descriptionLabel.setContentHuggingPriority(.required - 2, for: .vertical)
|
descriptionLabel.setContentHuggingPriority(.required - 2, for: .vertical)
|
||||||
descriptionLabel.setContentCompressionResistancePriority(.required - 2, for: .vertical)
|
descriptionLabel.setContentCompressionResistancePriority(.required - 2, for: .vertical)
|
||||||
|
|
||||||
expandButton.addTarget(self, action: #selector(expandButtonDidClicked(_:)), for: .touchUpInside)
|
expandButton.addTarget(self, action: #selector(expandButtonDidPressed(_:)), for: .touchUpInside)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func makeVerticalInfoStackView(arrangedView: UIView...) -> UIStackView {
|
private func makeVerticalInfoStackView(arrangedView: UIView...) -> UIStackView {
|
||||||
@ -306,7 +293,30 @@ extension PickServerCell {
|
|||||||
return stackView
|
return stackView
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateMode() {
|
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||||
|
super.setSelected(selected, animated: animated)
|
||||||
|
if selected {
|
||||||
|
checkbox.image = UIImage(systemName: "checkmark.circle.fill")
|
||||||
|
} else {
|
||||||
|
checkbox.image = UIImage(systemName: "circle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
private func expandButtonDidPressed(_ sender: UIButton) {
|
||||||
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
|
delegate?.pickServerCell(self, expandButtonPressed: sender)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PickServerCell {
|
||||||
|
|
||||||
|
enum ExpandMode {
|
||||||
|
case collapse
|
||||||
|
case expand
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateExpandMode(mode: ExpandMode) {
|
||||||
switch mode {
|
switch mode {
|
||||||
case .collapse:
|
case .collapse:
|
||||||
expandBox.isHidden = true
|
expandBox.isHidden = true
|
||||||
@ -318,73 +328,35 @@ extension PickServerCell {
|
|||||||
expandButton.isSelected = true
|
expandButton.isSelected = true
|
||||||
NSLayoutConstraint.activate(expandConstraints)
|
NSLayoutConstraint.activate(expandConstraints)
|
||||||
NSLayoutConstraint.deactivate(collapseConstraints)
|
NSLayoutConstraint.deactivate(collapseConstraints)
|
||||||
|
|
||||||
updateThumbnail()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func setSelected(_ selected: Bool, animated: Bool) {
|
// private func updateThumbnail() {
|
||||||
super.setSelected(selected, animated: animated)
|
// guard let serverInfo = server,
|
||||||
if selected {
|
// let proxiedThumbnail = serverInfo.proxiedThumbnail,
|
||||||
checkbox.image = UIImage(systemName: "checkmark.circle.fill")
|
// let url = URL(string: proxiedThumbnail) else {
|
||||||
} else {
|
// thumbnailImageView.isHidden = true
|
||||||
checkbox.image = UIImage(systemName: "circle")
|
// thumbnailActivityIdicator.stopAnimating()
|
||||||
}
|
// return
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
// thumbnailImageView.isHidden = false
|
||||||
|
// thumbnailActivityIdicator.startAnimating()
|
||||||
|
//
|
||||||
|
// let placeholderImage = UIImage.placeholder(color: .systemFill).af.imageRounded(withCornerRadius: 3.0, divideRadiusByImageScale: true)
|
||||||
|
// thumbnailImageView.af.setImage(
|
||||||
|
// withURL: url,
|
||||||
|
// placeholderImage: placeholderImage,
|
||||||
|
// filter: AspectScaledToFillSizeWithRoundedCornersFilter(size: thumbnailImageView.frame.size, radius: 3),
|
||||||
|
// imageTransition: .crossDissolve(0.33),
|
||||||
|
// completion: { [weak self] response in
|
||||||
|
// guard let self = self else { return }
|
||||||
|
// switch response.result {
|
||||||
|
// case .success, .failure:
|
||||||
|
// self.thumbnailActivityIdicator.stopAnimating()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
@objc
|
|
||||||
private func expandButtonDidClicked(_ sender: UIButton) {
|
|
||||||
let newMode: Mode = mode == .collapse ? .expand : .collapse
|
|
||||||
delegate?.pickServerCell(modeChange: server!, newMode: newMode, updates: { [weak self] in
|
|
||||||
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 = {
|
|
||||||
guard let html = try? HTML(html: serverInfo.description, encoding: .utf8) else {
|
|
||||||
return serverInfo.description
|
|
||||||
}
|
|
||||||
|
|
||||||
return html.text ?? serverInfo.description
|
|
||||||
}()
|
|
||||||
langValueLabel.text = serverInfo.language.uppercased()
|
|
||||||
usersValueLabel.text = parseUsersCount(serverInfo.totalUsers)
|
|
||||||
categoryValueLabel.text = serverInfo.category.uppercased()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateThumbnail() {
|
|
||||||
guard let serverInfo = server else { return }
|
|
||||||
|
|
||||||
thumbnailActivityIdicator.startAnimating()
|
|
||||||
let placeholderImage = UIImage.placeholder(color: .systemFill).af.imageRounded(withCornerRadius: 3.0, divideRadiusByImageScale: true)
|
|
||||||
thumbnailImageView.af.setImage(
|
|
||||||
withURL: URL(string: serverInfo.proxiedThumbnail ?? "")!,
|
|
||||||
placeholderImage: placeholderImage,
|
|
||||||
filter: AspectScaledToFillSizeWithRoundedCornersFilter(size: thumbnailImageView.frame.size, radius: 3),
|
|
||||||
imageTransition: .crossDissolve(0.33),
|
|
||||||
completion: { [weak self] response in
|
|
||||||
guard let self = self else { return }
|
|
||||||
switch response.result {
|
|
||||||
case .success, .failure:
|
|
||||||
self.thumbnailActivityIdicator.stopAnimating()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func parseUsersCount(_ usersCount: Int) -> String {
|
|
||||||
switch usersCount {
|
|
||||||
case 0..<1000:
|
|
||||||
return "\(usersCount)"
|
|
||||||
default:
|
|
||||||
let usersCountInThousand = Float(usersCount) / 1000.0
|
|
||||||
return String(format: "%.1fK", usersCountInThousand)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
protocol PickServerSearchCellDelegate: class {
|
protocol PickServerSearchCellDelegate: class {
|
||||||
func pickServerSearchCell(didChange searchText: String?)
|
func pickServerSearchCell(_ cell: PickServerSearchCell, searchTextDidChange searchText: String?)
|
||||||
}
|
}
|
||||||
|
|
||||||
class PickServerSearchCell: UITableViewCell {
|
class PickServerSearchCell: UITableViewCell {
|
||||||
@ -55,6 +55,12 @@ class PickServerSearchCell: UITableViewCell {
|
|||||||
return textField
|
return textField
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
override func prepareForReuse() {
|
||||||
|
super.prepareForReuse()
|
||||||
|
|
||||||
|
delegate = nil
|
||||||
|
}
|
||||||
|
|
||||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
_init()
|
_init()
|
||||||
@ -97,7 +103,7 @@ extension PickServerSearchCell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension PickServerSearchCell {
|
extension PickServerSearchCell {
|
||||||
@objc func textFieldDidChange(_ textField: UITextField) {
|
@objc private func textFieldDidChange(_ textField: UITextField) {
|
||||||
delegate?.pickServerSearchCell(didChange: textField.text)
|
delegate?.pickServerSearchCell(self, searchTextDidChange: textField.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,14 +9,14 @@ import UIKit
|
|||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
|
||||||
class PickServerCategoryView: UIView {
|
class PickServerCategoryView: UIView {
|
||||||
var category: MastodonPickServerViewModel.Category? {
|
// var category: MastodonPickServerViewModel.Category? {
|
||||||
didSet {
|
// didSet {
|
||||||
updateCategory()
|
// updateCategory()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
var selected: Bool = false {
|
var selected: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
updateSelectStatus()
|
// updateSelectStatus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,30 +70,42 @@ extension PickServerCategoryView {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateCategory() {
|
// private func updateCategory() {
|
||||||
guard let category = category else { return }
|
// guard let category = category else { return }
|
||||||
titleLabel.text = category.title
|
// titleLabel.text = category.title
|
||||||
switch category {
|
// switch category {
|
||||||
case .all:
|
// case .all:
|
||||||
titleLabel.font = UIFont.systemFont(ofSize: 17)
|
// titleLabel.font = UIFont.systemFont(ofSize: 17)
|
||||||
case .some:
|
// case .some:
|
||||||
titleLabel.font = UIFont.systemFont(ofSize: 28)
|
// titleLabel.font = UIFont.systemFont(ofSize: 28)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
// private func updateSelectStatus() {
|
||||||
|
// if selected {
|
||||||
|
// bgView.backgroundColor = Asset.Colors.lightBrandBlue.color
|
||||||
|
// bgView.applyShadow(color: Asset.Colors.lightBrandBlue.color, alpha: 1, x: 0, y: 0, blur: 4.0)
|
||||||
|
// if case .all = category {
|
||||||
|
// titleLabel.textColor = Asset.Colors.lightWhite.color
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// bgView.backgroundColor = Asset.Colors.lightWhite.color
|
||||||
|
// bgView.applyShadow(color: Asset.Colors.lightBrandBlue.color, alpha: 0, x: 0, y: 0, blur: 0.0)
|
||||||
|
// if case .all = category {
|
||||||
|
// titleLabel.textColor = Asset.Colors.lightBrandBlue.color
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
private func updateSelectStatus() {
|
#if DEBUG && canImport(SwiftUI)
|
||||||
if selected {
|
import SwiftUI
|
||||||
bgView.backgroundColor = Asset.Colors.lightBrandBlue.color
|
|
||||||
bgView.applyShadow(color: Asset.Colors.lightBrandBlue.color, alpha: 1, x: 0, y: 0, blur: 4.0)
|
struct PickServerCategoryView_Previews: PreviewProvider {
|
||||||
if case .all = category {
|
static var previews: some View {
|
||||||
titleLabel.textColor = Asset.Colors.lightWhite.color
|
UIViewPreview {
|
||||||
}
|
PickServerCategoryView()
|
||||||
} else {
|
|
||||||
bgView.backgroundColor = Asset.Colors.lightWhite.color
|
|
||||||
bgView.applyShadow(color: Asset.Colors.lightBrandBlue.color, alpha: 0, x: 0, y: 0, blur: 0.0)
|
|
||||||
if case .all = category {
|
|
||||||
titleLabel.textColor = Asset.Colors.lightBrandBlue.color
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
@ -39,8 +39,6 @@ final class MastodonPinBasedAuthenticationViewController: UIViewController, Need
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
extension MastodonPinBasedAuthenticationViewController {
|
extension MastodonPinBasedAuthenticationViewController {
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
|
@ -23,7 +23,7 @@ extension APIService {
|
|||||||
return Mastodon.API.Onboarding.categories(session: session)
|
return Mastodon.API.Onboarding.categories(session: session)
|
||||||
}
|
}
|
||||||
|
|
||||||
func stubCategories() -> [Mastodon.Entity.Category] {
|
static func stubCategories() -> [Mastodon.Entity.Category] {
|
||||||
return Mastodon.Entity.Category.Kind.allCases.map { kind in
|
return Mastodon.Entity.Category.Kind.allCases.map { kind in
|
||||||
return Mastodon.Entity.Category(category: kind.rawValue, serversCount: 0)
|
return Mastodon.Entity.Category(category: kind.rawValue, serversCount: 0)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user