feat: Implement follow/unfollow tag functionality

This commit is contained in:
Marcus Kida 2022-11-23 22:38:25 +01:00
parent b020f566f4
commit b9e4c69576
No known key found for this signature in database
GPG Key ID: 19FF64E08013CA40
6 changed files with 163 additions and 8 deletions

View File

@ -28,6 +28,8 @@ final class HashtagTimelineHeaderView: UIView {
private var widthConstraint: NSLayoutConstraint!
var onButtonTapped: (() -> Void)?
let followButton: UIButton = {
let button = RoundedEdgesButton(type: .custom)
button.cornerRadius = 10
@ -69,7 +71,11 @@ private extension HashtagTimelineHeaderView {
postCountDescLabel.text = "posts"
participantsDescLabel.text = "participants"
postsTodayDescLabel.text = "posts today"
followButton.addAction(UIAction(handler: { [weak self] _ in
self?.onButtonTapped?()
}), for: .touchUpInside)
widthConstraint = widthAnchor.constraint(equalToConstant: 0)
NSLayoutConstraint.activate([
@ -133,7 +139,7 @@ extension HashtagTimelineHeaderView {
postsTodayLabel.text = history.first?.uses
}
}
func updateWidthConstraint(_ constant: CGFloat) {
widthConstraint.constant = constant
}

View File

@ -177,6 +177,16 @@ extension HashtagTimelineViewController {
tableView.tableHeaderView = headerView
}
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() {

View File

@ -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 {
func updateTagInformation() {
Task { @MainActor in

View File

@ -21,13 +21,57 @@ extension APIService {
let domain = authenticationBox.domain
let authorization = authenticationBox.userAuthorization
let response = try await Mastodon.API.Tags.tag(
let response = try await Mastodon.API.Tags.getTagInformation(
session: session,
domain: domain,
tagId: tag,
authorization: authorization
).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
try await managedObjectContext.performChanges {
let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user
@ -44,6 +88,5 @@ extension APIService {
}
return response
} // end func
}
}

View File

@ -14,9 +14,9 @@ extension Mastodon.API.Tags {
.appendingPathComponent("tags")
}
/// Followed Tags
/// Tags
///
/// View your followed hashtags.
/// View information about a single tag.
///
/// - Since: 4.0.0
/// - Version: 4.0.3
@ -28,7 +28,7 @@ extension Mastodon.API.Tags {
/// - authorization: User token
/// - tagId: The Hashtag
/// - Returns: `AnyPublisher` contains `Tag` nested in the response
public static func tag(
public static func getTagInformation(
session: URLSession,
domain: String,
tagId: String,
@ -46,4 +46,72 @@ extension Mastodon.API.Tags {
}
.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()
}
}

View File

@ -41,5 +41,9 @@ extension Mastodon.Entity {
hasher.combine(name)
hasher.combine(url)
}
public func copy(following: Bool?) -> Self {
Tag(name: name, url: url, history: history, following: following)
}
}
}