Merge pull request #84 from tootsuite/feature/searchAPI

Feature/search api
This commit is contained in:
sxiaojian88 2021-04-01 11:52:17 +08:00 committed by GitHub
commit 379884b001
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 935 additions and 81 deletions

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17709" systemVersion="20D80" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17709" systemVersion="20D75" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Application" representedClassName=".Application" syncable="YES">
<attribute name="identifier" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
<attribute name="name" attributeType="String"/>
@ -35,11 +35,11 @@
<relationship name="toot" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Toot" inverseName="emojis" inverseEntity="Toot"/>
</entity>
<entity name="History" representedClassName=".History" syncable="YES">
<attribute name="accounts" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="accounts" optional="YES" attributeType="String"/>
<attribute name="createAt" attributeType="Date" defaultDateTimeInterval="631123200" usesScalarValueType="NO"/>
<attribute name="day" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="identifier" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
<attribute name="uses" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="uses" optional="YES" attributeType="String"/>
<relationship name="tag" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Tag" inverseName="histories" inverseEntity="Tag"/>
</entity>
<entity name="HomeTimelineIndex" representedClassName=".HomeTimelineIndex" syncable="YES">

View File

@ -14,8 +14,8 @@ public final class History: NSManagedObject {
@NSManaged public private(set) var createAt: Date
@NSManaged public private(set) var day: Date
@NSManaged public private(set) var uses: Int
@NSManaged public private(set) var accounts: Int
@NSManaged public private(set) var uses: String
@NSManaged public private(set) var accounts: String
// many-to-one relationship
@NSManaged public private(set) var tag: Tag
@ -43,10 +43,10 @@ public extension History {
public extension History {
struct Property {
public let day: Date
public let uses: Int
public let accounts: Int
public let uses: String
public let accounts: String
public init(day: Date, uses: Int, accounts: Int) {
public init(day: Date, uses: String, accounts: String) {
self.day = day
self.uses = uses
self.accounts = accounts

View File

@ -234,6 +234,12 @@
"private": "Followers only",
"direct": "Only people I mention"
}
},
"search": {
"searchBar": {
"placeholder": "Search hashtags and users",
"cancel": "Cancel"
}
}
}
}

View File

@ -30,6 +30,10 @@
2D32EAAC25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */; };
2D32EABA25CB9B0500C9ED86 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32EAB925CB9B0500C9ED86 /* UIView.swift */; };
2D32EADA25CBCC3300C9ED86 /* PublicTimelineViewModel+LoadMiddleState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32EAD925CBCC3300C9ED86 /* PublicTimelineViewModel+LoadMiddleState.swift */; };
2D34D9CB261489930081BFC0 /* SearchViewController+recomendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D34D9CA261489930081BFC0 /* SearchViewController+recomendView.swift */; };
2D34D9D126148D9E0081BFC0 /* APIService+Recommend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D34D9D026148D9E0081BFC0 /* APIService+Recommend.swift */; };
2D34D9DB261494120081BFC0 /* APIService+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D34D9DA261494120081BFC0 /* APIService+Search.swift */; };
2D34D9E226149C920081BFC0 /* SearchRecommendTagsCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D34D9E126149C920081BFC0 /* SearchRecommendTagsCollectionViewCell.swift */; };
2D364F7225E66D7500204FDC /* MastodonResendEmailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D364F7125E66D7500204FDC /* MastodonResendEmailViewController.swift */; };
2D364F7825E66D8300204FDC /* MastodonResendEmailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D364F7725E66D8300204FDC /* MastodonResendEmailViewModel.swift */; };
2D38F1C625CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1C525CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */; };
@ -42,7 +46,7 @@
2D38F1FE25CD481700561493 /* StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1FD25CD481700561493 /* StatusProvider.swift */; };
2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F20725CD491300561493 /* DisposeBagCollectable.swift */; };
2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3F9E0325DFA133004262D9 /* UITapGestureRecognizer.swift */; };
2D42FF6125C8177C004A627A /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = 2D42FF6025C8177C004A627A /* SwiftPackageProductDependency */; };
2D42FF6125C8177C004A627A /* ActiveLabel in Frameworks */ = {isa = PBXBuildFile; productRef = 2D42FF6025C8177C004A627A /* ActiveLabel */; };
2D42FF6B25C817D2004A627A /* TootContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF6A25C817D2004A627A /* TootContent.swift */; };
2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF7D25C82218004A627A /* ActionToolBarContainer.swift */; };
2D42FF8525C8224F004A627A /* HitTestExpandedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF8425C8224F004A627A /* HitTestExpandedButton.swift */; };
@ -52,18 +56,19 @@
2D571B2F26004EC000540450 /* NavigationBarProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D571B2E26004EC000540450 /* NavigationBarProgressView.swift */; };
2D59819B25E4A581000FB903 /* MastodonConfirmEmailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D59819A25E4A581000FB903 /* MastodonConfirmEmailViewController.swift */; };
2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5981A025E4A593000FB903 /* MastodonConfirmEmailViewModel.swift */; };
2D5981BA25E4D7F8000FB903 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = 2D5981B925E4D7F8000FB903 /* SwiftPackageProductDependency */; };
2D5981BA25E4D7F8000FB903 /* ThirdPartyMailer in Frameworks */ = {isa = PBXBuildFile; productRef = 2D5981B925E4D7F8000FB903 /* ThirdPartyMailer */; };
2D5A3D0325CF8742002347D6 /* ControlContainableScrollViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5A3D0225CF8742002347D6 /* ControlContainableScrollViews.swift */; };
2D5A3D2825CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5A3D2725CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift */; };
2D5A3D3825CF8D9F002347D6 /* ScrollViewContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5A3D3725CF8D9F002347D6 /* ScrollViewContainer.swift */; };
2D5A3D6225CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5A3D6125CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift */; };
2D61335825C188A000CAE157 /* APIService+Persist+Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D61335725C188A000CAE157 /* APIService+Persist+Status.swift */; };
2D61335E25C1894B00CAE157 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D61335D25C1894B00CAE157 /* APIService.swift */; };
2D61336925C18A4F00CAE157 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = 2D61336825C18A4F00CAE157 /* SwiftPackageProductDependency */; };
2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */ = {isa = PBXBuildFile; productRef = 2D61336825C18A4F00CAE157 /* AlamofireNetworkActivityIndicator */; };
2D650FAB25ECDC9300851B58 /* Mastodon+Entity+Error+Detail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D650FAA25ECDC9300851B58 /* Mastodon+Entity+Error+Detail.swift */; };
2D694A7425F9EB4E0038ADDC /* ContentWarningOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D694A7325F9EB4E0038ADDC /* ContentWarningOverlayView.swift */; };
2D69CFF425CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D69CFF325CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift */; };
2D69D00A25CAA00300C3A1B2 /* APIService+CoreData+Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D69D00925CAA00300C3A1B2 /* APIService+CoreData+Status.swift */; };
2D6DE40026141DF600A63F6A /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D6DE3FF26141DF600A63F6A /* SearchViewModel.swift */; };
2D76316525C14BD100929FB9 /* PublicTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76316425C14BD100929FB9 /* PublicTimelineViewController.swift */; };
2D76316B25C14D4C00929FB9 /* PublicTimelineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76316A25C14D4C00929FB9 /* PublicTimelineViewModel.swift */; };
2D76317D25C14DF500929FB9 /* PublicTimelineViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76317C25C14DF400929FB9 /* PublicTimelineViewController+StatusProvider.swift */; };
@ -81,7 +86,7 @@
2D927F0E25C7E9C9004F19B8 /* History.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F0D25C7E9C9004F19B8 /* History.swift */; };
2D927F1425C7EDD9004F19B8 /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F1325C7EDD9004F19B8 /* Emoji.swift */; };
2D939AB525EDD8A90076FA61 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AB425EDD8A90076FA61 /* String.swift */; };
2D939AC825EE14620076FA61 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = 2D939AC725EE14620076FA61 /* SwiftPackageProductDependency */; };
2D939AC825EE14620076FA61 /* CropViewController in Frameworks */ = {isa = PBXBuildFile; productRef = 2D939AC725EE14620076FA61 /* CropViewController */; };
2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AE725EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift */; };
2DA504692601ADE7008F4E6C /* SawToothView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA504682601ADE7008F4E6C /* SawToothView.swift */; };
2DA6054725F716A2006356F9 /* PlaybackState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA6054625F716A2006356F9 /* PlaybackState.swift */; };
@ -95,7 +100,8 @@
2DF75BA725D10E1000694EC8 /* APIService+Favorite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF75BA625D10E1000694EC8 /* APIService+Favorite.swift */; };
2DF75BB925D1474100694EC8 /* ManagedObjectObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF75BB825D1474100694EC8 /* ManagedObjectObserver.swift */; };
2DF75BC725D1475D00694EC8 /* ManagedObjectContextObjectsDidChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF75BC625D1475D00694EC8 /* ManagedObjectContextObjectsDidChange.swift */; };
5D526FE225BE9AC400460CB9 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = 5D526FE125BE9AC400460CB9 /* SwiftPackageProductDependency */; };
2DFF41892614A4DC00F776A4 /* UIView+Constraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DFF41882614A4DC00F776A4 /* UIView+Constraint.swift */; };
5D526FE225BE9AC400460CB9 /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 5D526FE125BE9AC400460CB9 /* MastodonSDK */; };
5DF1054125F886D400D6C0D4 /* ViedeoPlaybackService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF1054025F886D400D6C0D4 /* ViedeoPlaybackService.swift */; };
5DF1054725F8870E00D6C0D4 /* VideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF1054625F8870E00D6C0D4 /* VideoPlayerViewModel.swift */; };
5DF1056425F887CB00D6C0D4 /* AVPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF1056325F887CB00D6C0D4 /* AVPlayer.swift */; };
@ -108,7 +114,7 @@
DB0140A125C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140A025C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift */; };
DB0140A825C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140A725C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift */; };
DB0140AE25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140AD25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift */; };
DB0140BD25C40D7500F9F3CF /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = DB0140BC25C40D7500F9F3CF /* SwiftPackageProductDependency */; };
DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DB0140BC25C40D7500F9F3CF /* CommonOSLog */; };
DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140CE25C42AEE00F9F3CF /* OSLog.swift */; };
DB084B5725CBC56C00F898ED /* Toot.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB084B5625CBC56C00F898ED /* Toot.swift */; };
DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */; };
@ -126,7 +132,7 @@
DB2B3AE925E38850007045F9 /* UIViewPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2B3AE825E38850007045F9 /* UIViewPreview.swift */; };
DB2F073525E8ECF000957B2D /* AuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */; };
DB2FF510260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2FF50F260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift */; };
DB3D0FF325BAA61700EAA174 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = DB3D0FF225BAA61700EAA174 /* SwiftPackageProductDependency */; };
DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB3D0FF225BAA61700EAA174 /* AlamofireImage */; };
DB3D100D25BAA75E00EAA174 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB3D100F25BAA75E00EAA174 /* Localizable.strings */; };
DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD525BAA00100D1B89D /* AppDelegate.swift */; };
DB427DD825BAA00100D1B89D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD725BAA00100D1B89D /* SceneDelegate.swift */; };
@ -163,7 +169,7 @@
DB49A63D25FF609300B98345 /* PlayerContainerView+MediaTypeIndicotorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB49A63C25FF609300B98345 /* PlayerContainerView+MediaTypeIndicotorView.swift */; };
DB5086A525CC0B7000C2C187 /* AvatarBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086A425CC0B7000C2C187 /* AvatarBarButtonItem.swift */; };
DB5086AB25CC0BBB00C2C187 /* AvatarConfigurableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086AA25CC0BBB00C2C187 /* AvatarConfigurableView.swift */; };
DB5086B825CC0D6400C2C187 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = DB5086B725CC0D6400C2C187 /* SwiftPackageProductDependency */; };
DB5086B825CC0D6400C2C187 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = DB5086B725CC0D6400C2C187 /* Kingfisher */; };
DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086BD25CC0D9900C2C187 /* SplashPreference.swift */; };
DB55D33025FB630A0002F825 /* TwitterTextEditor+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB55D32F25FB630A0002F825 /* TwitterTextEditor+String.swift */; };
DB59F0FE25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB59F0FD25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift */; };
@ -229,7 +235,7 @@
DB98339C25C96DE600AD9700 /* APIService+Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98339B25C96DE600AD9700 /* APIService+Account.swift */; };
DB9A485C2603010E008B817C /* PHPickerResultLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9A485B2603010E008B817C /* PHPickerResultLoader.swift */; };
DB9A486C26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9A486B26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift */; };
DB9A487E2603456B008B817C /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = DB9A487D2603456B008B817C /* SwiftPackageProductDependency */; };
DB9A487E2603456B008B817C /* UITextView+Placeholder in Frameworks */ = {isa = PBXBuildFile; productRef = DB9A487D2603456B008B817C /* UITextView+Placeholder */; };
DB9A488426034BD7008B817C /* APIService+Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9A488326034BD7008B817C /* APIService+Status.swift */; };
DB9A488A26034D40008B817C /* ComposeViewModel+PublishState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9A488926034D40008B817C /* ComposeViewModel+PublishState.swift */; };
DB9A489026035963008B817C /* APIService+Media.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9A488F26035963008B817C /* APIService+Media.swift */; };
@ -252,8 +258,8 @@
DBD9149025DF6D8D00903DFD /* APIService+Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD9148F25DF6D8D00903DFD /* APIService+Onboarding.swift */; };
DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE0821425CD382600FD6BBD /* MastodonRegisterViewController.swift */; };
DBE0822425CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE0822325CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift */; };
DBE64A8B260C49D200E6359A /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = DBE64A8A260C49D200E6359A /* SwiftPackageProductDependency */; };
DBE64A8C260C49D200E6359A /* BuildFile in Embed Frameworks */ = {isa = PBXBuildFile; productRef = DBE64A8A260C49D200E6359A /* SwiftPackageProductDependency */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
DBE64A8B260C49D200E6359A /* TwitterTextEditor in Frameworks */ = {isa = PBXBuildFile; productRef = DBE64A8A260C49D200E6359A /* TwitterTextEditor */; };
DBE64A8C260C49D200E6359A /* TwitterTextEditor in Embed Frameworks */ = {isa = PBXBuildFile; productRef = DBE64A8A260C49D200E6359A /* TwitterTextEditor */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -301,7 +307,7 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
DBE64A8C260C49D200E6359A /* BuildFile in Embed Frameworks */,
DBE64A8C260C49D200E6359A /* TwitterTextEditor in Embed Frameworks */,
DB89BA0425C10FD0008580ED /* CoreDataStack.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
@ -332,6 +338,10 @@
2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMiddleLoaderTableViewCell.swift; sourceTree = "<group>"; };
2D32EAB925CB9B0500C9ED86 /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = "<group>"; };
2D32EAD925CBCC3300C9ED86 /* PublicTimelineViewModel+LoadMiddleState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewModel+LoadMiddleState.swift"; sourceTree = "<group>"; };
2D34D9CA261489930081BFC0 /* SearchViewController+recomendView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchViewController+recomendView.swift"; sourceTree = "<group>"; };
2D34D9D026148D9E0081BFC0 /* APIService+Recommend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Recommend.swift"; sourceTree = "<group>"; };
2D34D9DA261494120081BFC0 /* APIService+Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Search.swift"; sourceTree = "<group>"; };
2D34D9E126149C920081BFC0 /* SearchRecommendTagsCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchRecommendTagsCollectionViewCell.swift; sourceTree = "<group>"; };
2D364F7125E66D7500204FDC /* MastodonResendEmailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonResendEmailViewController.swift; sourceTree = "<group>"; };
2D364F7725E66D8300204FDC /* MastodonResendEmailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonResendEmailViewModel.swift; sourceTree = "<group>"; };
2D38F1C525CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentOffsetAdjustableTimelineViewControllerDelegate.swift; sourceTree = "<group>"; };
@ -363,6 +373,7 @@
2D694A7325F9EB4E0038ADDC /* ContentWarningOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentWarningOverlayView.swift; sourceTree = "<group>"; };
2D69CFF325CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadMoreConfigurableTableViewContainer.swift; sourceTree = "<group>"; };
2D69D00925CAA00300C3A1B2 /* APIService+CoreData+Status.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "APIService+CoreData+Status.swift"; sourceTree = "<group>"; };
2D6DE3FF26141DF600A63F6A /* SearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = "<group>"; };
2D76316425C14BD100929FB9 /* PublicTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicTimelineViewController.swift; sourceTree = "<group>"; };
2D76316A25C14D4C00929FB9 /* PublicTimelineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicTimelineViewModel.swift; sourceTree = "<group>"; };
2D76317C25C14DF400929FB9 /* PublicTimelineViewController+StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewController+StatusProvider.swift"; sourceTree = "<group>"; };
@ -394,6 +405,7 @@
2DF75BA625D10E1000694EC8 /* APIService+Favorite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Favorite.swift"; sourceTree = "<group>"; };
2DF75BB825D1474100694EC8 /* ManagedObjectObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedObjectObserver.swift; sourceTree = "<group>"; };
2DF75BC625D1475D00694EC8 /* ManagedObjectContextObjectsDidChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedObjectContextObjectsDidChange.swift; sourceTree = "<group>"; };
2DFF41882614A4DC00F776A4 /* UIView+Constraint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Constraint.swift"; sourceTree = "<group>"; };
2E1F6A67FDF9771D3E064FDC /* Pods-Mastodon.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.debug.xcconfig"; sourceTree = "<group>"; };
3C030226D3C73DCC23D67452 /* Pods_Mastodon_MastodonUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Mastodon_MastodonUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
452147B2903DF38070FE56A2 /* Pods_MastodonTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MastodonTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -572,17 +584,17 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
DB0140BD25C40D7500F9F3CF /* BuildFile in Frameworks */,
DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */,
DB89BA0325C10FD0008580ED /* CoreDataStack.framework in Frameworks */,
2D42FF6125C8177C004A627A /* BuildFile in Frameworks */,
DB9A487E2603456B008B817C /* BuildFile in Frameworks */,
2D939AC825EE14620076FA61 /* BuildFile in Frameworks */,
5D526FE225BE9AC400460CB9 /* BuildFile in Frameworks */,
DB5086B825CC0D6400C2C187 /* BuildFile in Frameworks */,
2D61336925C18A4F00CAE157 /* BuildFile in Frameworks */,
DB3D0FF325BAA61700EAA174 /* BuildFile in Frameworks */,
DBE64A8B260C49D200E6359A /* BuildFile in Frameworks */,
2D5981BA25E4D7F8000FB903 /* BuildFile in Frameworks */,
2D42FF6125C8177C004A627A /* ActiveLabel in Frameworks */,
DB9A487E2603456B008B817C /* UITextView+Placeholder in Frameworks */,
2D939AC825EE14620076FA61 /* CropViewController in Frameworks */,
5D526FE225BE9AC400460CB9 /* MastodonSDK in Frameworks */,
DB5086B825CC0D6400C2C187 /* Kingfisher in Frameworks */,
2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */,
DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */,
DBE64A8B260C49D200E6359A /* TwitterTextEditor in Frameworks */,
2D5981BA25E4D7F8000FB903 /* ThirdPartyMailer in Frameworks */,
87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -698,6 +710,14 @@
path = Content;
sourceTree = "<group>";
};
2D34D9E026149C550081BFC0 /* CollectionViewCell */ = {
isa = PBXGroup;
children = (
2D34D9E126149C920081BFC0 /* SearchRecommendTagsCollectionViewCell.swift */,
);
path = CollectionViewCell;
sourceTree = "<group>";
};
2D364F7025E66D5B00204FDC /* ResendEmail */ = {
isa = PBXGroup;
children = (
@ -1097,6 +1117,8 @@
DB49A62A25FF36C700B98345 /* APIService+CustomEmoji.swift */,
DB9A488326034BD7008B817C /* APIService+Status.swift */,
DB9A488F26035963008B817C /* APIService+Media.swift */,
2D34D9D026148D9E0081BFC0 /* APIService+Recommend.swift */,
2D34D9DA261494120081BFC0 /* APIService+Search.swift */,
);
path = APIService;
sourceTree = "<group>";
@ -1342,6 +1364,7 @@
5DF1056325F887CB00D6C0D4 /* AVPlayer.swift */,
DB47229625F9EFAD00DA7F53 /* NSManagedObjectContext.swift */,
2D32EAB925CB9B0500C9ED86 /* UIView.swift */,
2DFF41882614A4DC00F776A4 /* UIView+Constraint.swift */,
DB8AF55C25C138B7002E6C99 /* UIViewController.swift */,
2D3F9E0325DFA133004262D9 /* UITapGestureRecognizer.swift */,
2D84350425FF858100EECE90 /* UIScrollView.swift */,
@ -1382,6 +1405,9 @@
isa = PBXGroup;
children = (
DB9D6BE825E4F5340051B173 /* SearchViewController.swift */,
2D34D9CA261489930081BFC0 /* SearchViewController+recomendView.swift */,
2D6DE3FF26141DF600A63F6A /* SearchViewModel.swift */,
2D34D9E026149C550081BFC0 /* CollectionViewCell */,
);
path = Search;
sourceTree = "<group>";
@ -1492,16 +1518,16 @@
);
name = Mastodon;
packageProductDependencies = (
DB3D0FF225BAA61700EAA174 /* SwiftPackageProductDependency */,
5D526FE125BE9AC400460CB9 /* SwiftPackageProductDependency */,
2D61336825C18A4F00CAE157 /* SwiftPackageProductDependency */,
2D42FF6025C8177C004A627A /* SwiftPackageProductDependency */,
DB0140BC25C40D7500F9F3CF /* SwiftPackageProductDependency */,
DB5086B725CC0D6400C2C187 /* SwiftPackageProductDependency */,
2D5981B925E4D7F8000FB903 /* SwiftPackageProductDependency */,
2D939AC725EE14620076FA61 /* SwiftPackageProductDependency */,
DB9A487D2603456B008B817C /* SwiftPackageProductDependency */,
DBE64A8A260C49D200E6359A /* SwiftPackageProductDependency */,
DB3D0FF225BAA61700EAA174 /* AlamofireImage */,
5D526FE125BE9AC400460CB9 /* MastodonSDK */,
2D61336825C18A4F00CAE157 /* AlamofireNetworkActivityIndicator */,
2D42FF6025C8177C004A627A /* ActiveLabel */,
DB0140BC25C40D7500F9F3CF /* CommonOSLog */,
DB5086B725CC0D6400C2C187 /* Kingfisher */,
2D5981B925E4D7F8000FB903 /* ThirdPartyMailer */,
2D939AC725EE14620076FA61 /* CropViewController */,
DB9A487D2603456B008B817C /* UITextView+Placeholder */,
DBE64A8A260C49D200E6359A /* TwitterTextEditor */,
);
productName = Mastodon;
productReference = DB427DD225BAA00100D1B89D /* Mastodon.app */;
@ -1624,15 +1650,15 @@
);
mainGroup = DB427DC925BAA00100D1B89D;
packageReferences = (
DB3D0FF125BAA61700EAA174 /* RemoteSwiftPackageReference */,
2D61336725C18A4F00CAE157 /* RemoteSwiftPackageReference */,
2D42FF5F25C8177C004A627A /* RemoteSwiftPackageReference */,
DB0140BB25C40D7500F9F3CF /* RemoteSwiftPackageReference */,
DB5086B625CC0D6400C2C187 /* RemoteSwiftPackageReference */,
2D5981B825E4D7F8000FB903 /* RemoteSwiftPackageReference */,
2D939AC625EE14620076FA61 /* RemoteSwiftPackageReference */,
DB9A487C2603456B008B817C /* RemoteSwiftPackageReference */,
DBE64A89260C49D200E6359A /* RemoteSwiftPackageReference */,
DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */,
2D61336725C18A4F00CAE157 /* XCRemoteSwiftPackageReference "AlamofireNetworkActivityIndicator" */,
2D42FF5F25C8177C004A627A /* XCRemoteSwiftPackageReference "ActiveLabel" */,
DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */,
DB5086B625CC0D6400C2C187 /* XCRemoteSwiftPackageReference "Kingfisher" */,
2D5981B825E4D7F8000FB903 /* XCRemoteSwiftPackageReference "ThirdPartyMailer" */,
2D939AC625EE14620076FA61 /* XCRemoteSwiftPackageReference "TOCropViewController" */,
DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */,
DBE64A89260C49D200E6359A /* XCRemoteSwiftPackageReference "TwitterTextEditor" */,
);
productRefGroup = DB427DD325BAA00100D1B89D /* Products */;
projectDirPath = "";
@ -1863,6 +1889,7 @@
DB59F0FE25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift in Sources */,
DB68586425E619B700F0A850 /* NSKeyValueObservation.swift in Sources */,
2D61335825C188A000CAE157 /* APIService+Persist+Status.swift in Sources */,
2D34D9DB261494120081BFC0 /* APIService+Search.swift in Sources */,
DB45FAE325CA7181005A8AC7 /* MastodonUser.swift in Sources */,
DB2FF510260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift in Sources */,
DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */,
@ -1923,6 +1950,7 @@
DBC7A672260C897100E57475 /* StatusContentWarningEditorView.swift in Sources */,
DB59F10E25EF724F001F1DAB /* APIService+Poll.swift in Sources */,
DB47229725F9EFAD00DA7F53 /* NSManagedObjectContext.swift in Sources */,
2D34D9D126148D9E0081BFC0 /* APIService+Recommend.swift in Sources */,
2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */,
DB221B16260C395900AEFE46 /* CustomEmojiPickerInputViewModel.swift in Sources */,
DB0140A125C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift in Sources */,
@ -1953,6 +1981,7 @@
DB49A63D25FF609300B98345 /* PlayerContainerView+MediaTypeIndicotorView.swift in Sources */,
DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */,
DB44384F25E8C1FA008912A2 /* CALayer.swift in Sources */,
2D34D9CB261489930081BFC0 /* SearchViewController+recomendView.swift in Sources */,
2D206B8625F5FB0900143C56 /* Double.swift in Sources */,
DB9A485C2603010E008B817C /* PHPickerResultLoader.swift in Sources */,
2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */,
@ -1971,6 +2000,7 @@
DB98338725C945ED00AD9700 /* Strings.swift in Sources */,
DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */,
DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */,
2DFF41892614A4DC00F776A4 /* UIView+Constraint.swift in Sources */,
2D5A3D0325CF8742002347D6 /* ControlContainableScrollViews.swift in Sources */,
DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */,
DBA0A11325FB3FC10079C110 /* ComposeToolbarView.swift in Sources */,
@ -1980,6 +2010,7 @@
DB447697260B439000B66B82 /* CustomEmojiPickerHeaderCollectionReusableView.swift in Sources */,
DB45FAF925CA80A2005A8AC7 /* APIService+CoreData+MastodonAuthentication.swift in Sources */,
2D04F42525C255B9003F936F /* APIService+PublicTimeline.swift in Sources */,
2D34D9E226149C920081BFC0 /* SearchRecommendTagsCollectionViewCell.swift in Sources */,
2D76317D25C14DF500929FB9 /* PublicTimelineViewController+StatusProvider.swift in Sources */,
2D206B8025F5F45E00143C56 /* UIImage.swift in Sources */,
2D5A3D2825CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift in Sources */,
@ -2011,6 +2042,7 @@
0FB3D31E25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift in Sources */,
5DF1057925F88A1D00D6C0D4 /* PlayerContainerView.swift in Sources */,
DB45FB0F25CA87D0005A8AC7 /* AuthenticationService.swift in Sources */,
2D6DE40026141DF600A63F6A /* SearchViewModel.swift in Sources */,
DBCCC71E25F73297007E1AB6 /* APIService+Reblog.swift in Sources */,
DB789A2B25F9F7AB0071ACA0 /* ComposeRepliedToTootContentCollectionViewCell.swift in Sources */,
);
@ -2543,7 +2575,7 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
2D42FF5F25C8177C004A627A /* RemoteSwiftPackageReference */ = {
2D42FF5F25C8177C004A627A /* XCRemoteSwiftPackageReference "ActiveLabel" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/TwidereProject/ActiveLabel.swift";
requirement = {
@ -2551,7 +2583,7 @@
minimumVersion = 4.0.0;
};
};
2D5981B825E4D7F8000FB903 /* RemoteSwiftPackageReference */ = {
2D5981B825E4D7F8000FB903 /* XCRemoteSwiftPackageReference "ThirdPartyMailer" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/vtourraine/ThirdPartyMailer.git";
requirement = {
@ -2559,7 +2591,7 @@
minimumVersion = 1.7.1;
};
};
2D61336725C18A4F00CAE157 /* RemoteSwiftPackageReference */ = {
2D61336725C18A4F00CAE157 /* XCRemoteSwiftPackageReference "AlamofireNetworkActivityIndicator" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/Alamofire/AlamofireNetworkActivityIndicator";
requirement = {
@ -2567,7 +2599,7 @@
minimumVersion = 3.1.0;
};
};
2D939AC625EE14620076FA61 /* RemoteSwiftPackageReference */ = {
2D939AC625EE14620076FA61 /* XCRemoteSwiftPackageReference "TOCropViewController" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/TimOliver/TOCropViewController.git";
requirement = {
@ -2575,7 +2607,7 @@
minimumVersion = 2.6.0;
};
};
DB0140BB25C40D7500F9F3CF /* RemoteSwiftPackageReference */ = {
DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/MainasuK/CommonOSLog";
requirement = {
@ -2583,7 +2615,7 @@
minimumVersion = 0.1.1;
};
};
DB3D0FF125BAA61700EAA174 /* RemoteSwiftPackageReference */ = {
DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/Alamofire/AlamofireImage.git";
requirement = {
@ -2591,7 +2623,7 @@
minimumVersion = 4.1.0;
};
};
DB5086B625CC0D6400C2C187 /* RemoteSwiftPackageReference */ = {
DB5086B625CC0D6400C2C187 /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/onevcat/Kingfisher.git";
requirement = {
@ -2599,7 +2631,7 @@
minimumVersion = 6.1.0;
};
};
DB9A487C2603456B008B817C /* RemoteSwiftPackageReference */ = {
DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/MainasuK/UITextView-Placeholder";
requirement = {
@ -2607,7 +2639,7 @@
minimumVersion = 1.4.1;
};
};
DBE64A89260C49D200E6359A /* RemoteSwiftPackageReference */ = {
DBE64A89260C49D200E6359A /* XCRemoteSwiftPackageReference "TwitterTextEditor" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/MainasuK/TwitterTextEditor";
requirement = {
@ -2618,53 +2650,53 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
2D42FF6025C8177C004A627A /* SwiftPackageProductDependency */ = {
2D42FF6025C8177C004A627A /* ActiveLabel */ = {
isa = XCSwiftPackageProductDependency;
package = 2D42FF5F25C8177C004A627A /* RemoteSwiftPackageReference */;
package = 2D42FF5F25C8177C004A627A /* XCRemoteSwiftPackageReference "ActiveLabel" */;
productName = ActiveLabel;
};
2D5981B925E4D7F8000FB903 /* SwiftPackageProductDependency */ = {
2D5981B925E4D7F8000FB903 /* ThirdPartyMailer */ = {
isa = XCSwiftPackageProductDependency;
package = 2D5981B825E4D7F8000FB903 /* RemoteSwiftPackageReference */;
package = 2D5981B825E4D7F8000FB903 /* XCRemoteSwiftPackageReference "ThirdPartyMailer" */;
productName = ThirdPartyMailer;
};
2D61336825C18A4F00CAE157 /* SwiftPackageProductDependency */ = {
2D61336825C18A4F00CAE157 /* AlamofireNetworkActivityIndicator */ = {
isa = XCSwiftPackageProductDependency;
package = 2D61336725C18A4F00CAE157 /* RemoteSwiftPackageReference */;
package = 2D61336725C18A4F00CAE157 /* XCRemoteSwiftPackageReference "AlamofireNetworkActivityIndicator" */;
productName = AlamofireNetworkActivityIndicator;
};
2D939AC725EE14620076FA61 /* SwiftPackageProductDependency */ = {
2D939AC725EE14620076FA61 /* CropViewController */ = {
isa = XCSwiftPackageProductDependency;
package = 2D939AC625EE14620076FA61 /* RemoteSwiftPackageReference */;
package = 2D939AC625EE14620076FA61 /* XCRemoteSwiftPackageReference "TOCropViewController" */;
productName = CropViewController;
};
5D526FE125BE9AC400460CB9 /* SwiftPackageProductDependency */ = {
5D526FE125BE9AC400460CB9 /* MastodonSDK */ = {
isa = XCSwiftPackageProductDependency;
productName = MastodonSDK;
};
DB0140BC25C40D7500F9F3CF /* SwiftPackageProductDependency */ = {
DB0140BC25C40D7500F9F3CF /* CommonOSLog */ = {
isa = XCSwiftPackageProductDependency;
package = DB0140BB25C40D7500F9F3CF /* RemoteSwiftPackageReference */;
package = DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */;
productName = CommonOSLog;
};
DB3D0FF225BAA61700EAA174 /* SwiftPackageProductDependency */ = {
DB3D0FF225BAA61700EAA174 /* AlamofireImage */ = {
isa = XCSwiftPackageProductDependency;
package = DB3D0FF125BAA61700EAA174 /* RemoteSwiftPackageReference */;
package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */;
productName = AlamofireImage;
};
DB5086B725CC0D6400C2C187 /* SwiftPackageProductDependency */ = {
DB5086B725CC0D6400C2C187 /* Kingfisher */ = {
isa = XCSwiftPackageProductDependency;
package = DB5086B625CC0D6400C2C187 /* RemoteSwiftPackageReference */;
package = DB5086B625CC0D6400C2C187 /* XCRemoteSwiftPackageReference "Kingfisher" */;
productName = Kingfisher;
};
DB9A487D2603456B008B817C /* SwiftPackageProductDependency */ = {
DB9A487D2603456B008B817C /* UITextView+Placeholder */ = {
isa = XCSwiftPackageProductDependency;
package = DB9A487C2603456B008B817C /* RemoteSwiftPackageReference */;
package = DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */;
productName = "UITextView+Placeholder";
};
DBE64A8A260C49D200E6359A /* SwiftPackageProductDependency */ = {
DBE64A8A260C49D200E6359A /* TwitterTextEditor */ = {
isa = XCSwiftPackageProductDependency;
package = DBE64A89260C49D200E6359A /* RemoteSwiftPackageReference */;
package = DBE64A89260C49D200E6359A /* XCRemoteSwiftPackageReference "TwitterTextEditor" */;
productName = TwitterTextEditor;
};
/* End XCSwiftPackageProductDependency section */

View File

@ -0,0 +1,271 @@
//
// UIView+Constraint.swift
// Mastodon
//
// Created by sxiaojian on 2021/3/31.
//
import UIKit
enum Dimension {
case width
case height
var layoutAttribute: NSLayoutConstraint.Attribute {
switch self {
case .width:
return .width
case .height:
return .height
}
}
}
extension UIView {
func constrain(toSuperviewEdges: UIEdgeInsets?) {
guard let view = superview else { assert(false, "Superview cannot be nil when adding contraints"); return}
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
NSLayoutConstraint(item: self,
attribute: .leading,
relatedBy: .equal,
toItem: view,
attribute: .leading,
multiplier: 1.0,
constant: toSuperviewEdges?.left ?? 0.0),
NSLayoutConstraint(item: self,
attribute: .top,
relatedBy: .equal,
toItem: view,
attribute: .top,
multiplier: 1.0,
constant: toSuperviewEdges?.top ?? 0.0),
NSLayoutConstraint(item: self,
attribute: .trailing,
relatedBy: .equal,
toItem: view,
attribute: .trailing,
multiplier: 1.0,
constant: toSuperviewEdges?.right ?? 0.0),
NSLayoutConstraint(item: self,
attribute: .bottom,
relatedBy: .equal,
toItem: view,
attribute: .bottom,
multiplier: 1.0,
constant: toSuperviewEdges?.bottom ?? 0.0)
])
}
func constrain(_ constraints: [NSLayoutConstraint?]) {
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return }
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(constraints.compactMap { $0 })
}
func constraint(_ attribute: NSLayoutConstraint.Attribute, toView: UIView, constant: CGFloat?) -> NSLayoutConstraint? {
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return nil}
translatesAutoresizingMaskIntoConstraints = false
return NSLayoutConstraint(item: self, attribute: attribute, relatedBy: .equal, toItem: toView, attribute: attribute, multiplier: 1.0, constant: constant ?? 0.0)
}
func constraint(_ attribute: NSLayoutConstraint.Attribute, toView: UIView) -> NSLayoutConstraint? {
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return nil}
translatesAutoresizingMaskIntoConstraints = false
return NSLayoutConstraint(item: self, attribute: attribute, relatedBy: .equal, toItem: toView, attribute: attribute, multiplier: 1.0, constant: 0.0)
}
func constraint(_ dimension: Dimension, constant: CGFloat) -> NSLayoutConstraint? {
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return nil }
translatesAutoresizingMaskIntoConstraints = false
return NSLayoutConstraint(item: self,
attribute: dimension.layoutAttribute,
relatedBy: .equal,
toItem: nil,
attribute: .notAnAttribute,
multiplier: 1.0,
constant: constant)
}
func constraint(toBottom: UIView, constant: CGFloat) -> NSLayoutConstraint? {
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return nil }
translatesAutoresizingMaskIntoConstraints = false
return NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: toBottom, attribute: .bottom, multiplier: 1.0, constant: constant)
}
func pinToBottom(to: UIView, height: CGFloat) {
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return }
translatesAutoresizingMaskIntoConstraints = false
constrain([
constraint(.width, toView: to),
constraint(toBottom: to, constant: 0.0),
constraint(.height, constant: height)
])
}
func constraint(toTop: UIView, constant: CGFloat) -> NSLayoutConstraint? {
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return nil }
translatesAutoresizingMaskIntoConstraints = false
return NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: .equal, toItem: toTop, attribute: .top, multiplier: 1.0, constant: constant)
}
func constraint(toTrailing: UIView, constant: CGFloat) -> NSLayoutConstraint? {
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return nil }
translatesAutoresizingMaskIntoConstraints = false
return NSLayoutConstraint(item: self, attribute: .leading, relatedBy: .equal, toItem: toTrailing, attribute: .trailing, multiplier: 1.0, constant: constant)
}
func constraint(toLeading: UIView, constant: CGFloat) -> NSLayoutConstraint? {
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return nil }
translatesAutoresizingMaskIntoConstraints = false
return NSLayoutConstraint(item: self, attribute: .trailing, relatedBy: .equal, toItem: toLeading, attribute: .leading, multiplier: 1.0, constant: constant)
}
func constrainTopCorners(sidePadding: CGFloat, topPadding: CGFloat, topLayoutGuide: UILayoutSupport) {
guard let view = superview else { assert(false, "Superview cannot be nil when adding contraints"); return }
translatesAutoresizingMaskIntoConstraints = false
constrain([
constraint(.leading, toView: view, constant: sidePadding),
NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: topLayoutGuide, attribute: .bottom, multiplier: 1.0, constant: topPadding),
constraint(.trailing, toView: view, constant: -sidePadding)
])
}
func constrainTopCorners(sidePadding: CGFloat, topPadding: CGFloat) {
guard let view = superview else { assert(false, "Superview cannot be nil when adding contraints"); return }
translatesAutoresizingMaskIntoConstraints = false
constrain([
constraint(.leading, toView: view, constant: sidePadding),
constraint(.top, toView: view, constant: topPadding),
constraint(.trailing, toView: view, constant: -sidePadding)
])
}
func constrainTopCorners(height: CGFloat) {
guard let view = superview else { assert(false, "Superview cannot be nil when adding contraints"); return }
translatesAutoresizingMaskIntoConstraints = false
constrain([
constraint(.leading, toView: view),
constraint(.top, toView: view),
constraint(.trailing, toView: view),
constraint(.height, constant: height)
])
}
func constrainBottomCorners(sidePadding: CGFloat, bottomPadding: CGFloat) {
guard let view = superview else { assert(false, "Superview cannot be nil when adding contraints"); return }
translatesAutoresizingMaskIntoConstraints = false
constrain([
constraint(.leading, toView: view, constant: sidePadding),
constraint(.bottom, toView: view, constant: -bottomPadding),
constraint(.trailing, toView: view, constant: -sidePadding)
])
}
func constrainBottomCorners(height: CGFloat) {
guard let view = superview else { assert(false, "Superview cannot be nil when adding contraints"); return }
translatesAutoresizingMaskIntoConstraints = false
constrain([
constraint(.leading, toView: view),
constraint(.bottom, toView: view),
constraint(.trailing, toView: view),
constraint(.height, constant: height)
])
}
func constrainLeadingCorners() {
guard let view = superview else { assert(false, "Superview cannot be nil when adding contraints"); return }
translatesAutoresizingMaskIntoConstraints = false
constrain([
constraint(.top, toView: view),
constraint(.leading, toView: view),
constraint(.bottom, toView: view)
])
}
func constrainTrailingCorners() {
guard let view = superview else { assert(false, "Superview cannot be nil when adding contraints"); return }
translatesAutoresizingMaskIntoConstraints = false
constrain([
constraint(.top, toView: view),
constraint(.trailing, toView: view),
constraint(.bottom, toView: view)
])
}
func constrainToCenter() {
guard let view = superview else { assert(false, "Superview cannot be nil when adding contraints"); return }
translatesAutoresizingMaskIntoConstraints = false
constrain([
constraint(.centerX, toView: view),
constraint(.centerY, toView: view)
])
}
func pinTo(viewAbove: UIView, padding: CGFloat = 0.0, height: CGFloat? = nil) {
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return }
translatesAutoresizingMaskIntoConstraints = false
constrain([
constraint(.width, toView: viewAbove),
constraint(toBottom: viewAbove, constant: padding),
self.centerXAnchor.constraint(equalTo: viewAbove.centerXAnchor),
height != nil ? constraint(.height, constant: height!) : nil
])
}
func pin(toSize: CGSize) {
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return }
translatesAutoresizingMaskIntoConstraints = false
constrain([
widthAnchor.constraint(equalToConstant: toSize.width),
heightAnchor.constraint(equalToConstant: toSize.height)])
}
func pinTopLeft(padding: CGFloat) {
guard let view = superview else { assert(false, "Superview cannot be nil when adding contraints"); return }
translatesAutoresizingMaskIntoConstraints = false
constrain([
leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding),
topAnchor.constraint(equalTo: view.topAnchor, constant: padding)])
}
func pinTopRight(padding: CGFloat) {
guard let view = superview else { assert(false, "Superview cannot be nil when adding contraints"); return }
translatesAutoresizingMaskIntoConstraints = false
constrain([
view.trailingAnchor.constraint(equalTo: trailingAnchor, constant: padding),
topAnchor.constraint(equalTo: view.topAnchor, constant: padding)])
}
func pinTopLeft(toView: UIView, topPadding: CGFloat) {
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return }
translatesAutoresizingMaskIntoConstraints = false
constrain([
leadingAnchor.constraint(equalTo: toView.leadingAnchor),
topAnchor.constraint(equalTo: toView.bottomAnchor, constant: topPadding)])
}
/// Cross-fades between two views by animating their alpha then setting one or the other hidden.
/// - parameters:
/// - lhs: left view
/// - rhs: right view
/// - toRight: fade to the right view if true, fade to the left view if false
/// - duration: animation duration
///
static func crossfade(_ lhs: UIView, _ rhs: UIView, toRight: Bool, duration: TimeInterval) {
lhs.alpha = toRight ? 1.0 : 0.0
rhs.alpha = toRight ? 0.0 : 1.0
lhs.isHidden = false
rhs.isHidden = false
UIView.animate(withDuration: duration, animations: {
lhs.alpha = toRight ? 0.0 : 1.0
rhs.alpha = toRight ? 1.0 : 0.0
}, completion: { _ in
lhs.isHidden = toRight
rhs.isHidden = !toRight
})
}
}

View File

@ -369,6 +369,14 @@ internal enum L10n {
}
}
}
internal enum Search {
internal enum Searchbar {
/// Cancel
internal static let cancel = L10n.tr("Localizable", "Scene.Search.Searchbar.Cancel")
/// Search hashtags and users
internal static let placeholder = L10n.tr("Localizable", "Scene.Search.Searchbar.Placeholder")
}
}
internal enum ServerPicker {
/// Pick a Server,\nany server.
internal static let title = L10n.tr("Localizable", "Scene.ServerPicker.Title")

View File

@ -115,6 +115,8 @@ tap the link to confirm your account.";
"Scene.Register.Input.Username.DuplicatePrompt" = "This username is taken.";
"Scene.Register.Input.Username.Placeholder" = "username";
"Scene.Register.Title" = "Tell us about you.";
"Scene.Search.Searchbar.Cancel" = "Cancel";
"Scene.Search.Searchbar.Placeholder" = "Search hashtags and users";
"Scene.ServerPicker.Button.Category.All" = "All";
"Scene.ServerPicker.Button.SeeLess" = "See Less";
"Scene.ServerPicker.Button.SeeMore" = "See More";

View File

@ -0,0 +1,76 @@
//
// SearchRecommendTagsCollectionViewCell.swift
// Mastodon
//
// Created by sxiaojian on 2021/3/31.
//
import Foundation
import UIKit
class SearchRecommendTagsCollectionViewCell: UICollectionViewCell {
let backgroundImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
let hashTagTitleLabel: UILabel = {
let label = UILabel()
label.textColor = .white
label.font = .preferredFont(forTextStyle: .caption1)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let peopleLabel: UILabel = {
let label = UILabel()
label.textColor = .white
label.font = .preferredFont(forTextStyle: .body)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let flameIconView: UIImageView = {
let imageView = UIImageView()
let image = UIImage(systemName: "flame.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .semibold))!.withRenderingMode(.alwaysTemplate)
imageView.image = image
imageView.tintColor = .white
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
override func prepareForReuse() {
super.prepareForReuse()
}
override init(frame: CGRect) {
super.init(frame: .zero)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
}
extension SearchRecommendTagsCollectionViewCell {
private func configure() {
contentView.addSubview(backgroundImageView)
backgroundImageView.constrain(toSuperviewEdges: nil)
contentView.addSubview(hashTagTitleLabel)
hashTagTitleLabel.pinTopLeft(padding: 16)
contentView.addSubview(peopleLabel)
peopleLabel.constrain([
peopleLabel.constraint(toTop: contentView, constant: 46),
peopleLabel.constraint(toLeading: contentView, constant: 16)
])
contentView.addSubview(flameIconView)
flameIconView.pinTopRight(padding: 16)
}
}

View File

@ -0,0 +1,49 @@
//
// SearchViewController+recommendView.swift
// Mastodon
//
// Created by sxiaojian on 2021/3/31.
//
import Foundation
import UIKit
extension SearchViewController {
func setuprecommendView() {
recommendView.register(SearchRecommendTagsCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: SearchRecommendTagsCollectionViewCell.self))
recommendView.dataSource = self
recommendView.delegate = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
recommendView.collectionViewLayout.invalidateLayout()
}
}
extension SearchViewController: UICollectionViewDelegate {
}
extension SearchViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return (self.viewModel.recommendAccounts.isEmpty ? 0 : 1) + (self.viewModel.recommendHashTags.isEmpty ? 0 : 1)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
switch section {
case 0:
return viewModel.recommendHashTags.count
case 1:
return viewModel.recommendAccounts.count
default:
return 0
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return UICollectionViewCell()
}
}

View File

@ -2,23 +2,80 @@
// SearchViewController.swift
// Mastodon
//
// Created by MainasuK Cirno on 2021-2-23.
// Created by sxiaojian on 2021/3/31.
//
import UIKit
import Combine
final class SearchViewController: UIViewController, NeedsDependency {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var disposeBag = Set<AnyCancellable>()
private(set) lazy var viewModel = SearchViewModel(context: context)
let searchBar: UISearchBar = {
let searchBar = UISearchBar()
searchBar.placeholder = L10n.Scene.Search.Searchbar.placeholder
searchBar.tintColor = Asset.Colors.buttonDefault.color
searchBar.translatesAutoresizingMaskIntoConstraints = false
let micImage = UIImage(systemName: "mic.fill")
searchBar.setImage(micImage, for: .bookmark, state: .normal)
searchBar.showsBookmarkButton = true
return searchBar
}()
let recommendView: UICollectionView = {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .horizontal
let view = ControlContainableCollectionView(frame: .zero, collectionViewLayout: flowLayout)
view.backgroundColor = .clear
view.showsHorizontalScrollIndicator = false
view.showsVerticalScrollIndicator = false
view.layer.masksToBounds = false
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
}
extension SearchViewController {
override func viewDidLoad() {
super.viewDidLoad()
searchBar.delegate = self
navigationItem.titleView = searchBar
navigationItem.hidesBackButton = true
viewModel.requestRecommendData()
}
}
extension SearchViewController: UISearchBarDelegate {
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
searchBar.setShowsCancelButton(true, animated: true)
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
searchBar.setShowsCancelButton(false, animated: true)
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.setShowsCancelButton(false, animated: true)
searchBar.text = ""
searchBar.resignFirstResponder()
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
viewModel.searchText.send(searchText)
}
func searchBarBookmarkButtonClicked(_ searchBar: UISearchBar) {
}
}
extension SearchViewController {
}

View File

@ -0,0 +1,54 @@
//
// SearchViewModel.swift
// Mastodon
//
// Created by sxiaojian on 2021/3/31.
//
import Foundation
import Combine
import MastodonSDK
import UIKit
import OSLog
final class SearchViewModel {
var disposeBag = Set<AnyCancellable>()
// input
let context: AppContext
// output
let searchText = CurrentValueSubject<String, Never>("")
var recommendHashTags = [Mastodon.Entity.Tag]()
var recommendAccounts = [Mastodon.Entity.Account]()
init(context: AppContext) {
self.context = context
}
func requestRecommendData() {
guard let activeMastodonAuthenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
return
}
let trendsAPI = context.apiService.recommendTrends(domain: activeMastodonAuthenticationBox.domain, query: Mastodon.API.Trends.Query(limit: 5))
let accountsAPI = context.apiService.recommendAccount(domain: activeMastodonAuthenticationBox.domain, query: nil, mastodonAuthenticationBox: activeMastodonAuthenticationBox)
Publishers.Zip(trendsAPI,accountsAPI)
.sink { completion in
switch completion {
case .failure(let error):
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: zip request fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
case .finished:
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: zip request success", ((#file as NSString).lastPathComponent), #line, #function)
break
}
} receiveValue: { [weak self] (tags, accounts) in
guard let self = self else { return }
self.recommendAccounts = accounts.value
self.recommendHashTags = tags.value
}
.store(in: &disposeBag)
}
}

View File

@ -0,0 +1,30 @@
//
// APIService+Recommend.swift
// Mastodon
//
// Created by sxiaojian on 2021/3/31.
//
import Foundation
import MastodonSDK
import Combine
extension APIService {
func recommendAccount(
domain: String,
query: Mastodon.API.Suggestions.Query?,
mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> {
let authorization = mastodonAuthenticationBox.userAuthorization
return Mastodon.API.Suggestions.get(session: session, domain: domain, query: query, authorization: authorization)
}
func recommendTrends(
domain: String,
query: Mastodon.API.Trends.Query?
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Tag]>, Error> {
return Mastodon.API.Trends.get(session: session, domain: domain, query: query)
}
}

View File

@ -0,0 +1,23 @@
//
// APIService+Search.swift
// Mastodon
//
// Created by sxiaojian on 2021/3/31.
//
import Foundation
import MastodonSDK
import Combine
extension APIService {
func search(
domain: String,
query: Mastodon.API.Search.Query,
mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.SearchResult>, Error> {
let authorization = mastodonAuthenticationBox.userAuthorization
return Mastodon.API.Search.search(session: session, domain: domain, query: query, authorization: authorization)
}
}

View File

@ -0,0 +1,95 @@
//
// Mastodon+API+Search.swift
//
//
// Created by sxiaojian on 2021/3/31.
//
import Combine
import Foundation
extension Mastodon.API.Search {
static func searchURL(domain: String) -> URL {
Mastodon.API.endpointV2URL(domain: domain).appendingPathComponent("search")
}
/// Search results
///
/// Search for content in accounts, statuses and hashtags.
///
/// Version history:
/// 2.4.1 - added, limit hardcoded to 5
/// 2.8.0 - add type, limit, offset, min_id, max_id, account_id
/// 3.0.0 - add exclude_unreviewed param
/// # Reference
/// [Document](https://docs.joinmastodon.org/methods/search/)
/// - Parameters:
/// - session: `URLSession`
/// - domain: Mastodon instance domain. e.g. "example.com"
/// - query: search query
/// - authorization: User token
/// - Returns: `AnyPublisher` contains `Accounts,Hashtags,Status` nested in the response
public static func search(
session: URLSession,
domain: String,
query: Mastodon.API.Search.Query,
authorization: Mastodon.API.OAuth.Authorization
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.SearchResult>, Error> {
let request = Mastodon.API.get(
url: searchURL(domain: domain),
query: query,
authorization: authorization
)
return session.dataTaskPublisher(for: request)
.tryMap { data, response in
let value = try Mastodon.API.decode(type: Mastodon.Entity.SearchResult.self, from: data, response: response)
return Mastodon.Response.Content(value: value, response: response)
}
.eraseToAnyPublisher()
}
}
extension Mastodon.API.Search {
public struct Query: Codable, GetQuery {
public init(accountID: Mastodon.Entity.Account.ID?, maxID: Mastodon.Entity.Status.ID?, minID: Mastodon.Entity.Status.ID?, type: String?, excludeUnreviewed: Bool?, q: String, resolve: Bool?, limit: Int?, offset: Int?, following: Bool?) {
self.accountID = accountID
self.maxID = maxID
self.minID = minID
self.type = type
self.excludeUnreviewed = excludeUnreviewed
self.q = q
self.resolve = resolve
self.limit = limit
self.offset = offset
self.following = following
}
public let accountID: Mastodon.Entity.Account.ID?
public let maxID: Mastodon.Entity.Status.ID?
public let minID: Mastodon.Entity.Status.ID?
public let type: String?
public let excludeUnreviewed: Bool? // Filter out unreviewed tags? Defaults to false. Use true when trying to find trending tags.
public let q: String
public let resolve: Bool? // Attempt WebFinger lookup. Defaults to false.
public let limit: Int? // Maximum number of results to load, per type. Defaults to 20. Max 40.
public let offset: Int? // Offset in search results. Used for pagination. Defaults to 0.
public let following: Bool? // Only include accounts that the user is following. Defaults to false.
var queryItems: [URLQueryItem]? {
var items: [URLQueryItem] = []
accountID.flatMap { items.append(URLQueryItem(name: "account_id", value: $0)) }
maxID.flatMap { items.append(URLQueryItem(name: "max_id", value: $0)) }
minID.flatMap { items.append(URLQueryItem(name: "min_id", value: $0)) }
type.flatMap { items.append(URLQueryItem(name: "type", value: $0)) }
excludeUnreviewed.flatMap { items.append(URLQueryItem(name: "exclude_unreviewed", value: $0.queryItemValue)) }
items.append(URLQueryItem(name: "q", value: q))
resolve.flatMap { items.append(URLQueryItem(name: "resolve", value: $0.queryItemValue)) }
limit.flatMap { items.append(URLQueryItem(name: "limit", value: String($0))) }
offset.flatMap { items.append(URLQueryItem(name: "offset", value: String($0))) }
following.flatMap { items.append(URLQueryItem(name: "following", value: $0.queryItemValue)) }
guard !items.isEmpty else { return nil }
return items
}
}
}

View File

@ -0,0 +1,65 @@
//
// Mastodon+API+Suggestions.swift
//
//
// Created by sxiaojian on 2021/3/31.
//
import Combine
import Foundation
extension Mastodon.API.Suggestions {
static func suggestionsURL(domain: String) -> URL {
Mastodon.API.endpointURL(domain: domain).appendingPathComponent("suggestions")
}
/// Follow suggestions
///
/// Server-generated suggestions on who to follow, based on previous positive interactions.
///
/// Version history:
/// 2.4.3 - added
/// # Reference
/// [Document](https://docs.joinmastodon.org/methods/accounts/suggestions/)
/// - Parameters:
/// - session: `URLSession`
/// - domain: Mastodon instance domain. e.g. "example.com"
/// - query: query
/// - authorization: User token.
/// - Returns: `AnyPublisher` contains `Accounts` nested in the response
public static func get(
session: URLSession,
domain: String,
query: Mastodon.API.Suggestions.Query?,
authorization: Mastodon.API.OAuth.Authorization
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> {
let request = Mastodon.API.get(
url: suggestionsURL(domain: domain),
query: query,
authorization: authorization
)
return session.dataTaskPublisher(for: request)
.tryMap { data, response in
let value = try Mastodon.API.decode(type: [Mastodon.Entity.Account].self, from: data, response: response)
return Mastodon.Response.Content(value: value, response: response)
}
.eraseToAnyPublisher()
}
}
extension Mastodon.API.Suggestions {
public struct Query: Codable, GetQuery {
public init(limit: Int?) {
self.limit = limit
}
public let limit: Int? // Maximum number of results to return. Defaults to 40.
var queryItems: [URLQueryItem]? {
var items: [URLQueryItem] = []
limit.flatMap { items.append(URLQueryItem(name: "limit", value: String($0))) }
guard !items.isEmpty else { return nil }
return items
}
}
}

View File

@ -0,0 +1,64 @@
//
// Mastodon+API+Trends.swift
//
//
// Created by sxiaojian on 2021/3/31.
//
import Combine
import Foundation
extension Mastodon.API.Trends {
static func trendsURL(domain: String) -> URL {
Mastodon.API.endpointURL(domain: domain).appendingPathComponent("trends")
}
/// Trending tags
///
/// Tags that are being used more frequently within the past week.
///
/// Version history:
/// 3.0.0 - added
/// # Reference
/// [Document](https://docs.joinmastodon.org/methods/instance/trends/)
/// - Parameters:
/// - session: `URLSession`
/// - domain: Mastodon instance domain. e.g. "example.com"
/// - query: query
/// - Returns: `AnyPublisher` contains `Hashtags` nested in the response
public static func get(
session: URLSession,
domain: String,
query: Mastodon.API.Trends.Query?
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Tag]>, Error> {
let request = Mastodon.API.get(
url: trendsURL(domain: domain),
query: query,
authorization: nil
)
return session.dataTaskPublisher(for: request)
.tryMap { data, response in
let value = try Mastodon.API.decode(type: [Mastodon.Entity.Tag].self, from: data, response: response)
return Mastodon.Response.Content(value: value, response: response)
}
.eraseToAnyPublisher()
}
}
extension Mastodon.API.Trends {
public struct Query: Codable, GetQuery {
public init(limit: Int?) {
self.limit = limit
}
public let limit: Int? // Maximum number of results to return. Defaults to 10.
var queryItems: [URLQueryItem]? {
var items: [URLQueryItem] = []
limit.flatMap { items.append(URLQueryItem(name: "limit", value: String($0))) }
guard !items.isEmpty else { return nil }
return items
}
}
}

View File

@ -50,6 +50,9 @@ extension Mastodon.API {
if let date = fullDatePreciseISO8601Formatter.date(from: string) {
return date
}
if let timestamp = TimeInterval(string) {
return Date(timeIntervalSince1970: timestamp)
}
} catch {
// do nothing
}
@ -101,6 +104,9 @@ extension Mastodon.API {
public enum Reblog { }
public enum Statuses { }
public enum Timeline { }
public enum Search { }
public enum Trends { }
public enum Suggestions { }
}
extension Mastodon.API {

View File

@ -19,7 +19,7 @@ extension Mastodon.Entity {
public struct History: Codable {
/// UNIX timestamp on midnight of the given day
public let day: Date
public let uses: Int
public let accounts: Int
public let uses: String
public let accounts: String
}
}

View File

@ -0,0 +1,16 @@
//
// File.swift
//
//
// Created by sxiaojian on 2021/3/31.
//
import Foundation
extension Mastodon.Entity {
public struct SearchResult: Codable {
let accounts: [Mastodon.Entity.Account]
let statuses: [Mastodon.Entity.Status]
let hashtags: [Mastodon.Entity.Tag]
}
}