Add follow/unfollow tags
This commit is contained in:
parent
3be590e812
commit
221e128303
|
@ -20,5 +20,13 @@ extension Client {
|
|||
public func unfollow(tag: String) async throws -> Tag? {
|
||||
return try await pixelfedClient.unfollow(hashtag: tag)
|
||||
}
|
||||
|
||||
public func followed(maxId: MaxId? = nil,
|
||||
sinceId: SinceId? = nil,
|
||||
minId: MinId? = nil,
|
||||
limit: Int? = nil
|
||||
) async throws -> Linkable<[Tag]> {
|
||||
return try await pixelfedClient.followedTags(maxId: maxId, sinceId: sinceId, minId: minId, limit: limit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,10 +78,12 @@
|
|||
"trendingStatuses.title.noPhotos" = "Unfortunately, there are no photos here.";
|
||||
|
||||
// Mark: Trending tags.
|
||||
"trendingTags.navigationBar.title" = "Tags";
|
||||
"trendingTags.title.noTags" = "Unfortunately, there are no tags here.";
|
||||
"trendingTags.title.amountOfPosts" = "%d posts";
|
||||
"trendingTags.error.loadingTagsFailed" = "Loading tags failed.";
|
||||
"tags.navigationBar.trendingTitle" = "Tags";
|
||||
"tags.navigationBar.searchTitle" = "Tags";
|
||||
"tags.navigationBar.followedTitle" = "Followed tags";
|
||||
"tags.title.noTags" = "Unfortunately, there are no tags here.";
|
||||
"tags.title.amountOfPosts" = "%d posts";
|
||||
"tags.error.loadingTagsFailed" = "Loading tags failed.";
|
||||
|
||||
// Mark: Trending accounts.
|
||||
"trendingAccounts.navigationBar.title" = "Accounts";
|
||||
|
@ -97,6 +99,7 @@
|
|||
"userProfile.title.block" = "Block";
|
||||
"userProfile.title.favourites" = "Favourites";
|
||||
"userProfile.title.bookmarks" = "Bookmarks";
|
||||
"userProfile.title.followedTags" = "Followed tags";
|
||||
"userProfile.title.posts" = "Posts";
|
||||
"userProfile.title.followers" = "Followers";
|
||||
"userProfile.title.following" = "Following";
|
||||
|
|
|
@ -78,10 +78,12 @@
|
|||
"trendingStatuses.title.noPhotos" = "Argazkirik ez.";
|
||||
|
||||
// Mark: Trending tags.
|
||||
"trendingTags.navigationBar.title" = "Traolak";
|
||||
"trendingTags.title.noTags" = "Traolarik ez.";
|
||||
"trendingTags.title.amountOfPosts" = "%d bidalketa";
|
||||
"trendingTags.error.loadingTagsFailed" = "Traolak kargatzeak huts egin du.";
|
||||
"tags.navigationBar.trendingTitle" = "Traolak";
|
||||
"tags.navigationBar.searchTitle" = "Traolak";
|
||||
"tags.navigationBar.followedTitle" = "Traolak";
|
||||
"tags.title.noTags" = "Traolarik ez.";
|
||||
"tags.title.amountOfPosts" = "%d bidalketa";
|
||||
"tags.error.loadingTagsFailed" = "Traolak kargatzeak huts egin du.";
|
||||
|
||||
// Mark: Trending accounts.
|
||||
"trendingAccounts.navigationBar.title" = "Kontuak";
|
||||
|
@ -97,6 +99,7 @@
|
|||
"userProfile.title.block" = "Blokeatu";
|
||||
"userProfile.title.favourites" = "Gogokoak";
|
||||
"userProfile.title.bookmarks" = "Laster-markak";
|
||||
"userProfile.title.followedTags" = "Traolak";
|
||||
"userProfile.title.posts" = "Bidalketa";
|
||||
"userProfile.title.followers" = "Jarraitzaile";
|
||||
"userProfile.title.following" = "Jarraitzen";
|
||||
|
|
|
@ -78,10 +78,12 @@
|
|||
"trendingStatuses.title.noPhotos" = "Malheureusement, il n'y a pas de photos ici.";
|
||||
|
||||
// Mark: Trending tags.
|
||||
"trendingTags.navigationBar.title" = "Tags";
|
||||
"trendingTags.title.noTags" = "Malheureusement, il n'y a pas de tags ici.";
|
||||
"trendingTags.title.amountOfPosts" = "%d posts";
|
||||
"trendingTags.error.loadingTagsFailed" = "Chargement des tags échoué.";
|
||||
"tags.navigationBar.trendingTitle" = "Tags";
|
||||
"tags.navigationBar.searchTitle" = "Tags";
|
||||
"tags.navigationBar.followedTitle" = "Tags";
|
||||
"tags.title.noTags" = "Malheureusement, il n'y a pas de tags ici.";
|
||||
"tags.title.amountOfPosts" = "%d posts";
|
||||
"tags.error.loadingTagsFailed" = "Chargement des tags échoué.";
|
||||
|
||||
// Mark: Trending accounts.
|
||||
"trendingAccounts.navigationBar.title" = "Utilisateurs";
|
||||
|
@ -97,6 +99,7 @@
|
|||
"userProfile.title.block" = "Bloquer";
|
||||
"userProfile.title.favourites" = "Favoris";
|
||||
"userProfile.title.bookmarks" = "Marque-pages";
|
||||
"userProfile.title.followedTags" = "Tags";
|
||||
"userProfile.title.posts" = "Posts";
|
||||
"userProfile.title.followers" = "Abonnés";
|
||||
"userProfile.title.following" = "Abonnements";
|
||||
|
|
|
@ -78,10 +78,12 @@
|
|||
"trendingStatuses.title.noPhotos" = "Niestety nie ma jeszcze żadnych zdjęć.";
|
||||
|
||||
// Mark: Trending tags.
|
||||
"trendingTags.navigationBar.title" = "Tags";
|
||||
"trendingTags.title.noTags" = "Niestety nie ma jeszcze żadnych tagów.";
|
||||
"trendingTags.title.amountOfPosts" = "%d statusów";
|
||||
"trendingTags.error.loadingTagsFailed" = "Błąd podczas wczytywania tagów.";
|
||||
"tags.navigationBar.trendingTitle" = "Tagi";
|
||||
"tags.navigationBar.searchTitle" = "Tagi";
|
||||
"tags.navigationBar.followedTitle" = "Obserwowane tagi";
|
||||
"tags.title.noTags" = "Niestety nie ma jeszcze żadnych tagów.";
|
||||
"tags.title.amountOfPosts" = "%d statusów";
|
||||
"tags.error.loadingTagsFailed" = "Błąd podczas wczytywania tagów.";
|
||||
|
||||
// Mark: Trending accounts.
|
||||
"trendingAccounts.navigationBar.title" = "Użytkownicy";
|
||||
|
@ -97,6 +99,7 @@
|
|||
"userProfile.title.block" = "Zablokuj";
|
||||
"userProfile.title.favourites" = "Polubione";
|
||||
"userProfile.title.bookmarks" = "Zakładki";
|
||||
"userProfile.title.followedTags" = "Obserwowane tagi";
|
||||
"userProfile.title.posts" = "Statusy";
|
||||
"userProfile.title.followers" = "Obserwujący";
|
||||
"userProfile.title.following" = "Obserwowani";
|
||||
|
|
|
@ -37,4 +37,16 @@ public extension PixelfedClientAuthenticated {
|
|||
|
||||
return try await downloadJson(Tag.self, request: request)
|
||||
}
|
||||
|
||||
func followedTags(maxId: MaxId? = nil,
|
||||
sinceId: SinceId? = nil,
|
||||
minId: MinId? = nil,
|
||||
limit: Int? = nil
|
||||
) async throws -> Linkable<[Tag]> {
|
||||
let request = try Self.request(for: baseURL,
|
||||
target: Pixelfed.Tags.followed(maxId, sinceId, minId, limit),
|
||||
withBearerToken: token)
|
||||
|
||||
return try await downloadJsonWithLink([Tag].self, request: request)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ extension Pixelfed {
|
|||
case tag(Hashtag)
|
||||
case follow(Hashtag)
|
||||
case unfollow(Hashtag)
|
||||
case followed(MaxId?, SinceId?, MinId?, Limit?)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,13 +27,15 @@ extension Pixelfed.Tags: TargetType {
|
|||
return "\(apiPath)/\(hashtag)/follow"
|
||||
case .unfollow(let hashtag):
|
||||
return "\(apiPath)/\(hashtag)/unfollow"
|
||||
case .followed:
|
||||
return "/api/v1/followed_tags"
|
||||
}
|
||||
}
|
||||
|
||||
/// The HTTP method used in the request.
|
||||
public var method: Method {
|
||||
switch self {
|
||||
case .tag:
|
||||
case .tag, .followed(_, _, _, _):
|
||||
return .get
|
||||
case .follow, .unfollow:
|
||||
return .post
|
||||
|
@ -41,7 +44,36 @@ extension Pixelfed.Tags: TargetType {
|
|||
|
||||
/// The parameters to be incoded in the request.
|
||||
public var queryItems: [(String, String)]? {
|
||||
return nil
|
||||
var params: [(String, String)] = []
|
||||
|
||||
var maxId: MaxId?
|
||||
var sinceId: SinceId?
|
||||
var minId: MinId?
|
||||
var limit: Limit?
|
||||
|
||||
switch self {
|
||||
case .followed(let paramMaxId, let paramSinceId, let paramMinId, let paramLimit):
|
||||
maxId = paramMaxId
|
||||
sinceId = paramSinceId
|
||||
minId = paramMinId
|
||||
limit = paramLimit
|
||||
default: break
|
||||
}
|
||||
|
||||
if let maxId {
|
||||
params.append(("max_id", maxId))
|
||||
}
|
||||
if let sinceId {
|
||||
params.append(("since_id", sinceId))
|
||||
}
|
||||
if let minId {
|
||||
params.append(("min_id", minId))
|
||||
}
|
||||
if let limit {
|
||||
params.append(("limit", "\(limit)"))
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
public var headers: [String: String]? {
|
||||
|
|
|
@ -1321,7 +1321,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 211;
|
||||
CURRENT_PROJECT_VERSION = 213;
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = VernissageWidget/Info.plist;
|
||||
|
@ -1333,7 +1333,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.0;
|
||||
MARKETING_VERSION = 1.11.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage.widget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
|
@ -1352,7 +1352,7 @@
|
|||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 211;
|
||||
CURRENT_PROJECT_VERSION = 213;
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = VernissageWidget/Info.plist;
|
||||
|
@ -1364,7 +1364,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.0;
|
||||
MARKETING_VERSION = 1.11.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage.widget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
|
@ -1382,7 +1382,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = VernissageShare/VernissageShareExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 211;
|
||||
CURRENT_PROJECT_VERSION = 213;
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = VernissageShare/Info.plist;
|
||||
|
@ -1394,7 +1394,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.0;
|
||||
MARKETING_VERSION = 1.11.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage.share;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
|
@ -1411,7 +1411,7 @@
|
|||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = NO;
|
||||
CODE_SIGN_ENTITLEMENTS = VernissageShare/VernissageShareExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 211;
|
||||
CURRENT_PROJECT_VERSION = 213;
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = VernissageShare/Info.plist;
|
||||
|
@ -1423,7 +1423,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.0;
|
||||
MARKETING_VERSION = 1.11.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage.share;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
|
@ -1565,7 +1565,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 211;
|
||||
CURRENT_PROJECT_VERSION = 213;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
@ -1584,7 +1584,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.0;
|
||||
MARKETING_VERSION = 1.11.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
@ -1608,7 +1608,7 @@
|
|||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 211;
|
||||
CURRENT_PROJECT_VERSION = 213;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
@ -1627,7 +1627,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.0;
|
||||
MARKETING_VERSION = 1.11.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
|
|
|
@ -15,14 +15,17 @@ import WidgetsKit
|
|||
struct HashtagsView: View {
|
||||
public enum ListType: Hashable {
|
||||
case trending
|
||||
case followed
|
||||
case search(query: String)
|
||||
|
||||
public var title: LocalizedStringKey {
|
||||
switch self {
|
||||
case .trending:
|
||||
return "trendingTags.navigationBar.title"
|
||||
return "tags.navigationBar.trendingTitle"
|
||||
case .search:
|
||||
return "trendingTags.navigationBar.title"
|
||||
return "tags.navigationBar.searchTitle"
|
||||
case .followed:
|
||||
return "tags.navigationBar.followedTitle"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +54,7 @@ struct HashtagsView: View {
|
|||
}
|
||||
case .loaded:
|
||||
if self.tags.isEmpty {
|
||||
NoDataView(imageSystemName: "person.3.sequence", text: "trendingTags.title.noTags")
|
||||
NoDataView(imageSystemName: "person.3.sequence", text: "tags.title.noTags")
|
||||
} else {
|
||||
self.list()
|
||||
}
|
||||
|
@ -77,7 +80,7 @@ struct HashtagsView: View {
|
|||
Text(tag.name).font(.headline)
|
||||
Spacer()
|
||||
if let total = tag.total {
|
||||
Text(String(format: NSLocalizedString("trendingTags.title.amountOfPosts", comment: "Amount of posts"), total))
|
||||
Text(String(format: NSLocalizedString("tags.title.amountOfPosts", comment: "Amount of posts"), total))
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
|
@ -87,6 +90,9 @@ struct HashtagsView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.refreshable {
|
||||
await self.loadData()
|
||||
}
|
||||
}
|
||||
|
||||
private func loadData() async {
|
||||
|
@ -95,10 +101,10 @@ struct HashtagsView: View {
|
|||
self.state = .loaded
|
||||
} catch {
|
||||
if !Task.isCancelled {
|
||||
ErrorService.shared.handle(error, message: "trendingTags.error.loadingTagsFailed", showToastr: true)
|
||||
ErrorService.shared.handle(error, message: "tags.error.loadingTagsFailed", showToastr: true)
|
||||
self.state = .error(error)
|
||||
} else {
|
||||
ErrorService.shared.handle(error, message: "trendingTags.error.loadingTagsFailed", showToastr: false)
|
||||
ErrorService.shared.handle(error, message: "tags.error.loadingTagsFailed", showToastr: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -111,6 +117,9 @@ struct HashtagsView: View {
|
|||
case .search(let query):
|
||||
let results = try await self.client.search?.search(query: query, resultsType: .hashtags, limit: 40)
|
||||
return results?.hashtags.map({ tag in HashtagModel(tag: tag) }) ?? []
|
||||
case .followed:
|
||||
let tagsFromApi = try await self.client.tags?.followed(limit: 200)
|
||||
return tagsFromApi?.data.map({ tag in HashtagModel(tag: tag) }) ?? []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,8 +65,7 @@ struct StatusesView: View {
|
|||
self.mainBody()
|
||||
.navigationTitle(self.listType.title)
|
||||
.toolbar {
|
||||
// TODO: It seems like pixelfed is not supporting the endpoints.
|
||||
// self.getTrailingToolbar()
|
||||
self.getTrailingToolbar()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -292,15 +291,16 @@ struct StatusesView: View {
|
|||
Button {
|
||||
Task {
|
||||
if self.tag?.following == true {
|
||||
await self.follow(hashtag: hashtag)
|
||||
} else {
|
||||
await self.unfollow(hashtag: hashtag)
|
||||
} else {
|
||||
await self.follow(hashtag: hashtag)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: self.tag?.following == true ? "number.square.fill" : "number.square")
|
||||
.tint(.mainTextColor)
|
||||
}
|
||||
.disabled(self.tag == nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -215,6 +215,10 @@ struct UserProfileView: View {
|
|||
NavigationLink(value: RouteurDestinations.bookmarks) {
|
||||
Label(NSLocalizedString("userProfile.title.bookmarks", comment: "Bookmarks"), systemImage: "bookmark")
|
||||
}
|
||||
|
||||
NavigationLink(value: RouteurDestinations.hashtags(listType: .followed)) {
|
||||
Label(NSLocalizedString("userProfile.title.followedTags", comment: "Followed tags"), systemImage: "number.square")
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
|
|
|
@ -163,9 +163,16 @@ struct ImageRowItemAsync: View {
|
|||
private func imageView(image: Image) -> some View {
|
||||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: self.clipToRectangle ? .fill : .fit)
|
||||
.if(self.clipToRectangle) {
|
||||
$0.frame(width: self.containerWidth, height: self.containerWidth).clipped()
|
||||
.if(self.clipToRectangle == true) {
|
||||
$0
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: self.containerWidth, height: self.containerWidth)
|
||||
.clipped()
|
||||
// Fix issue with clickable content area outside of the visible image: https://developer.apple.com/forums/thread/123717.
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.if(self.clipToRectangle == false) {
|
||||
$0.aspectRatio(contentMode: .fit)
|
||||
}
|
||||
.onTapGesture(count: 2) {
|
||||
Task {
|
||||
|
|
|
@ -20,7 +20,7 @@ struct ImagesGrid: View {
|
|||
@EnvironmentObject var routerPath: RouterPath
|
||||
|
||||
private let maxImages = 5
|
||||
private let maxHeight = 120.0
|
||||
private let maxHeight = UIDevice.isIPad ? 240.0 : 120.0
|
||||
|
||||
@State public var gridType: GridType
|
||||
|
||||
|
|
Loading…
Reference in New Issue