feat: Implement follow/unfollow tag functionality
This commit is contained in:
parent
b020f566f4
commit
b9e4c69576
|
@ -28,6 +28,8 @@ final class HashtagTimelineHeaderView: UIView {
|
||||||
|
|
||||||
private var widthConstraint: NSLayoutConstraint!
|
private var widthConstraint: NSLayoutConstraint!
|
||||||
|
|
||||||
|
var onButtonTapped: (() -> Void)?
|
||||||
|
|
||||||
let followButton: UIButton = {
|
let followButton: UIButton = {
|
||||||
let button = RoundedEdgesButton(type: .custom)
|
let button = RoundedEdgesButton(type: .custom)
|
||||||
button.cornerRadius = 10
|
button.cornerRadius = 10
|
||||||
|
@ -70,6 +72,10 @@ private extension HashtagTimelineHeaderView {
|
||||||
participantsDescLabel.text = "participants"
|
participantsDescLabel.text = "participants"
|
||||||
postsTodayDescLabel.text = "posts today"
|
postsTodayDescLabel.text = "posts today"
|
||||||
|
|
||||||
|
followButton.addAction(UIAction(handler: { [weak self] _ in
|
||||||
|
self?.onButtonTapped?()
|
||||||
|
}), for: .touchUpInside)
|
||||||
|
|
||||||
widthConstraint = widthAnchor.constraint(equalToConstant: 0)
|
widthConstraint = widthAnchor.constraint(equalToConstant: 0)
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
|
|
|
@ -177,6 +177,16 @@ extension HashtagTimelineViewController {
|
||||||
tableView.tableHeaderView = headerView
|
tableView.tableHeaderView = headerView
|
||||||
}
|
}
|
||||||
headerView.update(tag)
|
headerView.update(tag)
|
||||||
|
headerView.onButtonTapped = { [weak self] in
|
||||||
|
switch tag.following {
|
||||||
|
case .some(false):
|
||||||
|
self?.viewModel.followTag()
|
||||||
|
case .some(true):
|
||||||
|
self?.viewModel.unfollowTag()
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLayoutSubviews() {
|
override func viewDidLayoutSubviews() {
|
||||||
|
|
|
@ -72,6 +72,30 @@ final class HashtagTimelineViewModel {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension HashtagTimelineViewModel {
|
||||||
|
func followTag() {
|
||||||
|
self.hashtagDetails.send(hashtagDetails.value?.copy(following: true))
|
||||||
|
Task { @MainActor in
|
||||||
|
let tag = try? await context.apiService.followTag(
|
||||||
|
for: hashtag,
|
||||||
|
authenticationBox: authContext.mastodonAuthenticationBox
|
||||||
|
).value
|
||||||
|
self.hashtagDetails.send(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unfollowTag() {
|
||||||
|
self.hashtagDetails.send(hashtagDetails.value?.copy(following: false))
|
||||||
|
Task { @MainActor in
|
||||||
|
let tag = try? await context.apiService.unfollowTag(
|
||||||
|
for: hashtag,
|
||||||
|
authenticationBox: authContext.mastodonAuthenticationBox
|
||||||
|
).value
|
||||||
|
self.hashtagDetails.send(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private extension HashtagTimelineViewModel {
|
private extension HashtagTimelineViewModel {
|
||||||
func updateTagInformation() {
|
func updateTagInformation() {
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
|
|
|
@ -21,13 +21,57 @@ extension APIService {
|
||||||
let domain = authenticationBox.domain
|
let domain = authenticationBox.domain
|
||||||
let authorization = authenticationBox.userAuthorization
|
let authorization = authenticationBox.userAuthorization
|
||||||
|
|
||||||
let response = try await Mastodon.API.Tags.tag(
|
let response = try await Mastodon.API.Tags.getTagInformation(
|
||||||
session: session,
|
session: session,
|
||||||
domain: domain,
|
domain: domain,
|
||||||
tagId: tag,
|
tagId: tag,
|
||||||
authorization: authorization
|
authorization: authorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
|
return try await persistTag(from: response, domain: domain, authenticationBox: authenticationBox)
|
||||||
|
} // end func
|
||||||
|
|
||||||
|
public func followTag(
|
||||||
|
for tag: String,
|
||||||
|
authenticationBox: MastodonAuthenticationBox
|
||||||
|
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Tag> {
|
||||||
|
let domain = authenticationBox.domain
|
||||||
|
let authorization = authenticationBox.userAuthorization
|
||||||
|
|
||||||
|
let response = try await Mastodon.API.Tags.followTag(
|
||||||
|
session: session,
|
||||||
|
domain: domain,
|
||||||
|
tagId: tag,
|
||||||
|
authorization: authorization
|
||||||
|
).singleOutput()
|
||||||
|
|
||||||
|
return try await persistTag(from: response, domain: domain, authenticationBox: authenticationBox)
|
||||||
|
} // end func
|
||||||
|
|
||||||
|
public func unfollowTag(
|
||||||
|
for tag: String,
|
||||||
|
authenticationBox: MastodonAuthenticationBox
|
||||||
|
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Tag> {
|
||||||
|
let domain = authenticationBox.domain
|
||||||
|
let authorization = authenticationBox.userAuthorization
|
||||||
|
|
||||||
|
let response = try await Mastodon.API.Tags.unfollowTag(
|
||||||
|
session: session,
|
||||||
|
domain: domain,
|
||||||
|
tagId: tag,
|
||||||
|
authorization: authorization
|
||||||
|
).singleOutput()
|
||||||
|
|
||||||
|
return try await persistTag(from: response, domain: domain, authenticationBox: authenticationBox)
|
||||||
|
} // end func
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate extension APIService {
|
||||||
|
func persistTag(
|
||||||
|
from response: Mastodon.Response.Content<Mastodon.Entity.Tag>,
|
||||||
|
domain: String,
|
||||||
|
authenticationBox: MastodonAuthenticationBox
|
||||||
|
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Tag> {
|
||||||
let managedObjectContext = self.backgroundManagedObjectContext
|
let managedObjectContext = self.backgroundManagedObjectContext
|
||||||
try await managedObjectContext.performChanges {
|
try await managedObjectContext.performChanges {
|
||||||
let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user
|
let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user
|
||||||
|
@ -44,6 +88,5 @@ extension APIService {
|
||||||
}
|
}
|
||||||
|
|
||||||
return response
|
return response
|
||||||
} // end func
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,9 +14,9 @@ extension Mastodon.API.Tags {
|
||||||
.appendingPathComponent("tags")
|
.appendingPathComponent("tags")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Followed Tags
|
/// Tags
|
||||||
///
|
///
|
||||||
/// View your followed hashtags.
|
/// View information about a single tag.
|
||||||
///
|
///
|
||||||
/// - Since: 4.0.0
|
/// - Since: 4.0.0
|
||||||
/// - Version: 4.0.3
|
/// - Version: 4.0.3
|
||||||
|
@ -28,7 +28,7 @@ extension Mastodon.API.Tags {
|
||||||
/// - authorization: User token
|
/// - authorization: User token
|
||||||
/// - tagId: The Hashtag
|
/// - tagId: The Hashtag
|
||||||
/// - Returns: `AnyPublisher` contains `Tag` nested in the response
|
/// - Returns: `AnyPublisher` contains `Tag` nested in the response
|
||||||
public static func tag(
|
public static func getTagInformation(
|
||||||
session: URLSession,
|
session: URLSession,
|
||||||
domain: String,
|
domain: String,
|
||||||
tagId: String,
|
tagId: String,
|
||||||
|
@ -46,4 +46,72 @@ extension Mastodon.API.Tags {
|
||||||
}
|
}
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tags
|
||||||
|
///
|
||||||
|
/// Follow a hashtag.
|
||||||
|
///
|
||||||
|
/// - Since: 4.0.0
|
||||||
|
/// - Version: 4.0.3
|
||||||
|
/// # Reference
|
||||||
|
/// [Document](https://docs.joinmastodon.org/methods/tags/)
|
||||||
|
/// - Parameters:
|
||||||
|
/// - session: `URLSession`
|
||||||
|
/// - domain: Mastodon instance domain. e.g. "example.com"
|
||||||
|
/// - authorization: User token
|
||||||
|
/// - tagId: The Hashtag
|
||||||
|
/// - Returns: `AnyPublisher` contains `Tag` nested in the response
|
||||||
|
public static func followTag(
|
||||||
|
session: URLSession,
|
||||||
|
domain: String,
|
||||||
|
tagId: String,
|
||||||
|
authorization: Mastodon.API.OAuth.Authorization
|
||||||
|
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Tag>, Error> {
|
||||||
|
let request = Mastodon.API.post(
|
||||||
|
url: tagsEndpointURL(domain: domain).appendingPathComponent(tagId)
|
||||||
|
.appendingPathComponent("follow"),
|
||||||
|
query: nil,
|
||||||
|
authorization: authorization
|
||||||
|
)
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tags
|
||||||
|
///
|
||||||
|
/// Unfollow a hashtag.
|
||||||
|
///
|
||||||
|
/// - Since: 4.0.0
|
||||||
|
/// - Version: 4.0.3
|
||||||
|
/// # Reference
|
||||||
|
/// [Document](https://docs.joinmastodon.org/methods/tags/)
|
||||||
|
/// - Parameters:
|
||||||
|
/// - session: `URLSession`
|
||||||
|
/// - domain: Mastodon instance domain. e.g. "example.com"
|
||||||
|
/// - authorization: User token
|
||||||
|
/// - tagId: The Hashtag
|
||||||
|
/// - Returns: `AnyPublisher` contains `Tag` nested in the response
|
||||||
|
public static func unfollowTag(
|
||||||
|
session: URLSession,
|
||||||
|
domain: String,
|
||||||
|
tagId: String,
|
||||||
|
authorization: Mastodon.API.OAuth.Authorization
|
||||||
|
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Tag>, Error> {
|
||||||
|
let request = Mastodon.API.post(
|
||||||
|
url: tagsEndpointURL(domain: domain).appendingPathComponent(tagId)
|
||||||
|
.appendingPathComponent("unfollow"),
|
||||||
|
query: nil,
|
||||||
|
authorization: authorization
|
||||||
|
)
|
||||||
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,5 +41,9 @@ extension Mastodon.Entity {
|
||||||
hasher.combine(name)
|
hasher.combine(name)
|
||||||
hasher.combine(url)
|
hasher.combine(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func copy(following: Bool?) -> Self {
|
||||||
|
Tag(name: name, url: url, history: history, following: following)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue