1
0
mirror of https://github.com/mastodon/mastodon-ios.git synced 2025-02-01 18:07:22 +01:00

Implement "Followed Hashtags" (IOS-102)

This commit is contained in:
Marcus Kida 2024-07-15 15:22:36 +02:00
parent f03ef0dfc7
commit 0a887a4841
No known key found for this signature in database
GPG Key ID: 19FF64E08013CA40
12 changed files with 70 additions and 4 deletions

View File

@ -481,6 +481,9 @@
"lists": {
"title": "Lists",
"manage_lists": "Manage Lists"
},
"hashtags": {
"title": "Followed Hashtags"
}
},
"timeline_pill": {

View File

@ -127,7 +127,6 @@ extension HashtagTimelineViewModel.State {
Task {
do {
let response = try await viewModel.context.apiService.hashtagTimeline(
domain: viewModel.authContext.mastodonAuthenticationBox.domain,
maxID: maxID,
hashtag: viewModel.hashtag,
authenticationBox: viewModel.authContext.mastodonAuthenticationBox

View File

@ -143,6 +143,9 @@ final class HomeTimelineViewController: UIViewController, NeedsDependency, Media
case .list:
showLocalTimelineAction.state = .off
showFollowingAction.state = .off
case .hashtag:
showLocalTimelineAction.state = .off
showFollowingAction.state = .off
}
}
@ -190,7 +193,43 @@ final class HomeTimelineViewController: UIViewController, NeedsDependency, Media
children: [listsSubmenu]
)
let listsDivider = UIMenu(title: "", options: .displayInline, children: [listsMenu])
let hashtagsSubmenu = UIDeferredMenuElement.uncached { [weak self] callback in
guard let self else { return callback([]) }
Task { @MainActor in
let lists = (try? await Mastodon.API.Account.followedTags(
session: .shared,
domain: self.authContext.mastodonAuthenticationBox.domain,
query: .init(limit: nil),
authorization: self.authContext.mastodonAuthenticationBox.userAuthorization
).singleOutput().value) ?? []
let listEntries = lists.map { entry in
return LabeledAction(title: entry.name, image: nil, handler: { [weak self] in
guard let self, let viewModel = self.viewModel else { return }
viewModel.timelineContext = .hashtag(entry.name)
viewModel.loadLatestStateMachine.enter(HomeTimelineViewModel.LoadLatestState.ContextSwitch.self)
timelineSelectorButton.setAttributedTitle(
.init(string: entry.name, attributes: [
.font: UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 20, weight: .semibold))
]),
for: .normal)
timelineSelectorButton.sizeToFit()
timelineSelectorButton.menu = generateTimelineSelectorMenu()
}).menuElement
}
callback(listEntries)
}
}
let hashtagsMenu = UIMenu(
title: L10n.Scene.HomeTimeline.TimelineMenu.Hashtags.title,
image: UIImage(systemName: "number"),
children: [hashtagsSubmenu]
)
let listsDivider = UIMenu(title: "", options: .displayInline, children: [listsMenu, hashtagsMenu])
return UIMenu(children: [showFollowingAction, showLocalTimelineAction, listsDivider])
}

View File

@ -138,6 +138,11 @@ extension HomeTimelineViewModel.LoadLatestState {
query: .init(sinceID: sinceID),
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
)
case let .hashtag(tag):
response = try await viewModel.context.apiService.hashtagTimeline(
hashtag: tag,
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
)
}
enter(state: Idle.self)

View File

@ -80,6 +80,11 @@ extension HomeTimelineViewModel.LoadOldestState {
query: .init(local: true, maxID: maxID),
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
)
case let .hashtag(tag):
response = try await viewModel.context.apiService.hashtagTimeline(
hashtag: tag,
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
)
}
let statuses = response.value

View File

@ -180,6 +180,11 @@ extension HomeTimelineViewModel {
query: .init(local: true, maxID: status.id),
authenticationBox: authContext.mastodonAuthenticationBox
)
case let .hashtag(tag):
response = try? await context.apiService.hashtagTimeline(
hashtag: tag,
authenticationBox: authContext.mastodonAuthenticationBox
)
}
// insert missing items

View File

@ -190,6 +190,11 @@ private extension FeedDataController {
query: .init(maxID: maxID),
authenticationBox: authContext.mastodonAuthenticationBox
)
case let .hashtag(tag):
response = try await context.apiService.hashtagTimeline(
hashtag: tag,
authenticationBox: authContext.mastodonAuthenticationBox
)
}
return response.value.map { .fromStatus(.fromEntity($0), kind: .home) }

View File

@ -14,7 +14,6 @@ import MastodonSDK
extension APIService {
public func hashtagTimeline(
domain: String,
sinceID: Mastodon.Entity.Status.ID? = nil,
maxID: Mastodon.Entity.Status.ID? = nil,
limit: Int = onceRequestStatusMaxCount,

View File

@ -862,6 +862,10 @@ public enum L10n {
public static let following = L10n.tr("Localizable", "Scene.HomeTimeline.TimelineMenu.Following", fallback: "Following")
/// Local
public static let localCommunity = L10n.tr("Localizable", "Scene.HomeTimeline.TimelineMenu.LocalCommunity", fallback: "Local")
public enum Hashtags {
/// Followed Hashtags
public static let title = L10n.tr("Localizable", "Scene.HomeTimeline.TimelineMenu.Hashtags.Title", fallback: "Followed Hashtags")
}
public enum Lists {
/// Manage Lists
public static let manageLists = L10n.tr("Localizable", "Scene.HomeTimeline.TimelineMenu.Lists.ManageLists", fallback: "Manage Lists")

View File

@ -307,6 +307,7 @@ uploaded to Mastodon.";
"Scene.HomeTimeline.TimelineMenu.LocalCommunity" = "Local";
"Scene.HomeTimeline.TimelineMenu.Lists.Title" = "Lists";
"Scene.HomeTimeline.TimelineMenu.Lists.ManageLists" = "Manage Lists";
"Scene.HomeTimeline.TimelineMenu.Hashtags.Title" = "Followed Hashtags";
"Scene.HomeTimeline.TimelinePill.NewPosts" = "New Posts";
"Scene.HomeTimeline.TimelinePill.Offline" = "Offline";
"Scene.HomeTimeline.TimelinePill.PostSent" = "Post Sent";

View File

@ -15,6 +15,7 @@ public final class MastodonFeed {
case home
case `public`
case list(String)
case hashtag(String)
}
}

View File

@ -53,7 +53,7 @@ extension HashtagWidgetProvider {
do {
let mostRecentStatuses = try await WidgetExtension.appContext
.apiService
.hashtagTimeline(domain: authBox.domain, limit: 40, hashtag: desiredHashtag, authenticationBox: authBox)
.hashtagTimeline(limit: 40, hashtag: desiredHashtag, authenticationBox: authBox)
.value
let filteredStatuses: [Mastodon.Entity.Status]