#49 Approve/reject follow requests
This commit is contained in:
parent
71278400a8
commit
b4f7b02d78
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the Apache License 2.0.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import PixelfedKit
|
||||
|
||||
extension Client {
|
||||
public class FollowRequests: BaseClient {
|
||||
public func followRequests(limit: Int = 10,
|
||||
page: Int? = nil) async throws -> [Account] {
|
||||
return try await pixelfedClient.followRequests(limit: limit, page: page)
|
||||
}
|
||||
|
||||
public func authorizeRequest(id: EntityId) async throws -> Relationship {
|
||||
return try await pixelfedClient.authorizeRequest(id: id)
|
||||
}
|
||||
|
||||
public func rejectRequest(id: EntityId) async throws -> Relationship {
|
||||
return try await pixelfedClient.rejectRequest(id: id)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -39,6 +39,7 @@ extension Client {
|
|||
public var blocks: Blocks? { return Blocks(pixelfedClient: self.pixelfedClient) }
|
||||
public var mutes: Mutes? { return Mutes(pixelfedClient: self.pixelfedClient) }
|
||||
public var reports: Reports? { return Reports(pixelfedClient: self.pixelfedClient) }
|
||||
public var followRequests: FollowRequests? { return FollowRequests(pixelfedClient: self.pixelfedClient) }
|
||||
public var instances: Instances { return Instances() }
|
||||
}
|
||||
|
||||
|
|
|
@ -100,7 +100,6 @@
|
|||
"userProfile.title.following" = "Following";
|
||||
"userProfile.title.joined" = "Joined %@";
|
||||
"userProfile.title.unfollow" = "Unfollow";
|
||||
"userProfile.title.followBack" = "Follow back";
|
||||
"userProfile.title.follow" = "Follow";
|
||||
"userProfile.title.instance" = "Instance information";
|
||||
"userProfile.title.blocks" = "Blocked accounts";
|
||||
|
@ -110,6 +109,12 @@
|
|||
"userProfile.title.blocked" = "Account blocked";
|
||||
"userProfile.title.unblocked" = "Account unblocked";
|
||||
"userProfile.title.report" = "Report";
|
||||
"userProfile.title.followsYou" = "Follows you";
|
||||
"userProfile.title.requestFollow" = "Request follow";
|
||||
"userProfile.title.cancelRequestFollow" = "Cancel request";
|
||||
"userProfile.title.followRequests" = "Follow requests";
|
||||
"userProfile.title.privateProfileTitle" = "This profile is private.";
|
||||
"userProfile.title.privateProfileSubtitle" = "Only approved followers can see photos.";
|
||||
"userProfile.error.notExists" = "Account does not exists.";
|
||||
"userProfile.error.loadingAccountFailed" = "Error during download account from server.";
|
||||
"userProfile.error.muting" = "Muting/unmuting action failed.";
|
||||
|
@ -350,3 +355,10 @@
|
|||
"report.title.scam" = "Bullying or harassment";
|
||||
"report.title.terrorism" = "Terrorism";
|
||||
"report.error.notReported" = "Error during sending report.";
|
||||
|
||||
// Mark: Following requests.
|
||||
"followingRequests.navigationBar.title" = "Following requests";
|
||||
"followingRequests.title.approve" = "Approve";
|
||||
"followingRequests.title.reject" = "Reject";
|
||||
"followingRequests.error.approve" = "Error during approving request.";
|
||||
"followingRequests.error.reject" = "Error during rejecting request.";
|
||||
|
|
|
@ -100,7 +100,6 @@
|
|||
"userProfile.title.following" = "Jarraitzen";
|
||||
"userProfile.title.joined" = "%@ egin zuen bat";
|
||||
"userProfile.title.unfollow" = "Utzi jarraitzeari";
|
||||
"userProfile.title.followBack" = "Jarraitu bera ere";
|
||||
"userProfile.title.follow" = "Jarraitu";
|
||||
"userProfile.title.instance" = "Instantziari buruzko informazioa";
|
||||
"userProfile.title.blocks" = "Blokeatutako kontuak";
|
||||
|
@ -110,6 +109,12 @@
|
|||
"userProfile.title.blocked" = "Kontua blokeatu da";
|
||||
"userProfile.title.unblocked" = "Kontua blokeatzeari utzi zaio";
|
||||
"userProfile.title.report" = "Salatu";
|
||||
"userProfile.title.followsYou" = "Jarraitzen dizu";
|
||||
"userProfile.title.requestFollow" = "Request follow";
|
||||
"userProfile.title.cancelRequestFollow" = "Cancel request";
|
||||
"userProfile.title.followRequests" = "Follow requests";
|
||||
"userProfile.title.privateProfileTitle" = "This profile is private.";
|
||||
"userProfile.title.privateProfileSubtitle" = "Only approved followers can see photos.";
|
||||
"userProfile.error.notExists" = "Kontua ez da existitzen.";
|
||||
"userProfile.error.loadingAccountFailed" = "Errorea zerbitzaritik kontua eskuratzean.";
|
||||
"userProfile.error.muting" = "Mututu/Mututzeari uzteak huts egin du.";
|
||||
|
@ -350,3 +355,10 @@
|
|||
"report.title.scam" = "Bullyinga edo jazarpena";
|
||||
"report.title.terrorism" = "Terrorismoa";
|
||||
"report.error.notReported" = "Errorea salaketa bidaltzerakoan.";
|
||||
|
||||
// Mark: Following requests.
|
||||
"followingRequests.navigationBar.title" = "Following requests";
|
||||
"followingRequests.title.approve" = "Approve";
|
||||
"followingRequests.title.reject" = "Reject";
|
||||
"followingRequests.error.approve" = "Error during approving request.";
|
||||
"followingRequests.error.reject" = "Error during rejecting request.";
|
||||
|
|
|
@ -100,7 +100,6 @@
|
|||
"userProfile.title.following" = "Suivis";
|
||||
"userProfile.title.joined" = "Joint %@";
|
||||
"userProfile.title.unfollow" = "Ne plus suivre";
|
||||
"userProfile.title.followBack" = "Suivre en retour";
|
||||
"userProfile.title.follow" = "Suivre";
|
||||
"userProfile.title.instance" = "Information sur l'instance";
|
||||
"userProfile.title.blocks" = "Comptes bloqués";
|
||||
|
@ -110,6 +109,12 @@
|
|||
"userProfile.title.blocked" = "Compte bloqué";
|
||||
"userProfile.title.unblocked" = "Compte débloqué";
|
||||
"userProfile.title.report" = "Rapport";
|
||||
"userProfile.title.followsYou" = "Vous suit";
|
||||
"userProfile.title.requestFollow" = "Request follow";
|
||||
"userProfile.title.cancelRequestFollow" = "Cancel request";
|
||||
"userProfile.title.followRequests" = "Follow requests";
|
||||
"userProfile.title.privateProfileTitle" = "This profile is private.";
|
||||
"userProfile.title.privateProfileSubtitle" = "Only approved followers can see photos.";
|
||||
"userProfile.error.notExists" = "Le compte n'existe pas.";
|
||||
"userProfile.error.loadingAccountFailed" = "Erreur pendant le téléchargement du compte depuis le serveur.";
|
||||
"userProfile.error.muting" = "L'action sourdine / réactivation a échoué.";
|
||||
|
@ -350,3 +355,10 @@
|
|||
"report.title.scam" = "Intimidation ou harcèlement";
|
||||
"report.title.terrorism" = "Le terrorisme";
|
||||
"report.error.notReported" = "Erreur lors de l'envoi du rapport.";
|
||||
|
||||
// Mark: Following requests.
|
||||
"followingRequests.navigationBar.title" = "Following requests";
|
||||
"followingRequests.title.approve" = "Approve";
|
||||
"followingRequests.title.reject" = "Reject";
|
||||
"followingRequests.error.approve" = "Error during approving request.";
|
||||
"followingRequests.error.reject" = "Error during rejecting request.";
|
||||
|
|
|
@ -100,10 +100,8 @@
|
|||
"userProfile.title.following" = "Obserwowani";
|
||||
"userProfile.title.joined" = "Dołączył(a) %@";
|
||||
"userProfile.title.unfollow" = "Przestań obserwować";
|
||||
"userProfile.title.followBack" = "Również obserwuj";
|
||||
"userProfile.title.follow" = "Obserwuj";
|
||||
"userProfile.title.instance" = "Informacje o instancji";
|
||||
"userProfile.error.notExists" = "Konto nie istnieje.";
|
||||
"userProfile.title.blocks" = "Zablokowane konta";
|
||||
"userProfile.title.mutes" = "Wyciszone konta";
|
||||
"userProfile.title.muted" = "Konto wyciszone";
|
||||
|
@ -111,6 +109,13 @@
|
|||
"userProfile.title.blocked" = "Konto zablokowane";
|
||||
"userProfile.title.unblocked" = "Konto odblokowane";
|
||||
"userProfile.title.report" = "Zgłoś";
|
||||
"userProfile.title.followsYou" = "Obserwuje ciebie";
|
||||
"userProfile.title.requestFollow" = "Poproś o obserwowanie";
|
||||
"userProfile.title.cancelRequestFollow" = "Anuluj prośbę";
|
||||
"userProfile.title.followRequests" = "Prośby o obserwowanie";
|
||||
"userProfile.title.privateProfileTitle" = "To konto jest prywatne.";
|
||||
"userProfile.title.privateProfileSubtitle" = "Tylko zaakceptowani użytkownicy mogą przeglądać zdjęcia.";
|
||||
"userProfile.error.notExists" = "Konto nie istnieje.";
|
||||
"userProfile.error.notExists" = "Błąd podczas pobierania danych użytkownika.";
|
||||
"userProfile.error.mute" = "Błąd podczas wyciszania użytkownika.";
|
||||
"userProfile.error.block" = "Błąd podczas blokowania/odblokowywania użytkownika.";
|
||||
|
@ -350,3 +355,10 @@
|
|||
"report.title.scam" = "Znęcanie się lub nękanie";
|
||||
"report.title.terrorism" = "Terroryzm";
|
||||
"report.error.notReported" = "Błąd podczas wysyłania zgłoszenia.";
|
||||
|
||||
// Mark: Following requests.
|
||||
"followingRequests.navigationBar.title" = "Prośby o obserwowanie";
|
||||
"followingRequests.title.approve" = "Zaakceptuj";
|
||||
"followingRequests.title.reject" = "Odrzuć";
|
||||
"followingRequests.error.approve" = "Błąd podczas akceptowania prośby.";
|
||||
"followingRequests.error.reject" = "Błąd podczas odrzucania prośby.";
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the Apache License 2.0.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public extension PixelfedClientAuthenticated {
|
||||
func followRequests(limit: Int? = nil,
|
||||
page: Page? = nil) async throws -> [Account] {
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Pixelfed.FollowRequests.followRequests(limit, page),
|
||||
withBearerToken: token
|
||||
)
|
||||
|
||||
return try await downloadJson([Account].self, request: request)
|
||||
}
|
||||
|
||||
func authorizeRequest(id: EntityId) async throws -> Relationship {
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Pixelfed.FollowRequests.authorize(id),
|
||||
withBearerToken: token
|
||||
)
|
||||
|
||||
return try await downloadJson(Relationship.self, request: request)
|
||||
}
|
||||
|
||||
func rejectRequest(id: EntityId) async throws -> Relationship {
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Pixelfed.FollowRequests.reject(id),
|
||||
withBearerToken: token
|
||||
)
|
||||
|
||||
return try await downloadJson(Relationship.self, request: request)
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ import Foundation
|
|||
|
||||
extension Pixelfed {
|
||||
public enum FollowRequests {
|
||||
case followRequests
|
||||
case followRequests(Limit?, Page?)
|
||||
case authorize(String)
|
||||
case reject(String)
|
||||
}
|
||||
|
@ -22,10 +22,10 @@ extension Pixelfed.FollowRequests: TargetType {
|
|||
switch self {
|
||||
case .followRequests:
|
||||
return "\(apiPath)"
|
||||
case .authorize:
|
||||
return "\(apiPath)/authorize"
|
||||
case .reject:
|
||||
return "\(apiPath)/reject"
|
||||
case .authorize(let id):
|
||||
return "\(apiPath)/\(id)/authorize"
|
||||
case .reject(let id):
|
||||
return "\(apiPath)/\(id)/reject"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,9 +39,29 @@ extension Pixelfed.FollowRequests: TargetType {
|
|||
}
|
||||
}
|
||||
|
||||
/// The parameters to be incoded in the request.
|
||||
public var queryItems: [(String, String)]? {
|
||||
nil
|
||||
var params: [(String, String)] = []
|
||||
|
||||
var limit: Limit?
|
||||
var page: Page?
|
||||
|
||||
switch self {
|
||||
case .followRequests(let paramLimit, let paramPage):
|
||||
limit = paramLimit
|
||||
page = paramPage
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
if let limit {
|
||||
params.append(("limit", "\(limit)"))
|
||||
}
|
||||
|
||||
if let page {
|
||||
params.append(("page", "\(page)"))
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
public var headers: [String: String]? {
|
||||
|
@ -49,15 +69,6 @@ extension Pixelfed.FollowRequests: TargetType {
|
|||
}
|
||||
|
||||
public var httpBody: Data? {
|
||||
switch self {
|
||||
case .followRequests:
|
||||
return nil
|
||||
case .authorize(let id):
|
||||
return try? JSONEncoder().encode(
|
||||
["id": id]
|
||||
)
|
||||
case .reject:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
F8210DDD2966CF17001D9973 /* StatusData+Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DDC2966CF17001D9973 /* StatusData+Status.swift */; };
|
||||
F8210DDF2966CFC7001D9973 /* AttachmentData+Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DDE2966CFC7001D9973 /* AttachmentData+Attachment.swift */; };
|
||||
F8210DEA2966E4F9001D9973 /* AnimatePlaceholderModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DE92966E4F9001D9973 /* AnimatePlaceholderModifier.swift */; };
|
||||
F825F0C929F7A562008BD204 /* UserProfilePrivateAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F825F0C829F7A562008BD204 /* UserProfilePrivateAccountView.swift */; };
|
||||
F825F0CB29F7CFC4008BD204 /* FollowRequestsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F825F0CA29F7CFC4008BD204 /* FollowRequestsView.swift */; };
|
||||
F835082329BEF9C400DE3247 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F835082629BEF9C400DE3247 /* Localizable.strings */; };
|
||||
F835082429BEF9C400DE3247 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F835082629BEF9C400DE3247 /* Localizable.strings */; };
|
||||
F83CBEFB298298A1002972C8 /* ImageCarouselPicture.swift in Sources */ = {isa = PBXBuildFile; fileRef = F83CBEFA298298A1002972C8 /* ImageCarouselPicture.swift */; };
|
||||
|
@ -231,6 +233,8 @@
|
|||
F8210DDC2966CF17001D9973 /* StatusData+Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusData+Status.swift"; sourceTree = "<group>"; };
|
||||
F8210DDE2966CFC7001D9973 /* AttachmentData+Attachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttachmentData+Attachment.swift"; sourceTree = "<group>"; };
|
||||
F8210DE92966E4F9001D9973 /* AnimatePlaceholderModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatePlaceholderModifier.swift; sourceTree = "<group>"; };
|
||||
F825F0C829F7A562008BD204 /* UserProfilePrivateAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfilePrivateAccountView.swift; sourceTree = "<group>"; };
|
||||
F825F0CA29F7CFC4008BD204 /* FollowRequestsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowRequestsView.swift; sourceTree = "<group>"; };
|
||||
F835082529BEF9C400DE3247 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
F835082729BEFA1E00DE3247 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
F837269429A221420098D3C4 /* PixelfedKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = PixelfedKit; sourceTree = "<group>"; };
|
||||
|
@ -477,6 +481,7 @@
|
|||
F89B5CC129D01BF700549F2F /* InstanceView.swift */,
|
||||
F805DCF029DBEF83006A1FD9 /* ReportView.swift */,
|
||||
F86BC9EA29EBDA2E009415EC /* ActivityView.swift */,
|
||||
F825F0CA29F7CFC4008BD204 /* FollowRequestsView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
|
@ -607,6 +612,7 @@
|
|||
children = (
|
||||
F86B7213296BFDCE00EE59EC /* UserProfileHeaderView.swift */,
|
||||
F86B7215296BFFDA00EE59EC /* UserProfileStatusesView.swift */,
|
||||
F825F0C829F7A562008BD204 /* UserProfilePrivateAccountView.swift */,
|
||||
);
|
||||
path = Subviews;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1136,6 +1142,8 @@
|
|||
F8FB8ABA29EB2ED400342C04 /* NavigationMenuButtons.swift in Sources */,
|
||||
F88BC51D29E0377B00CE6141 /* AccountData+AccountModel.swift in Sources */,
|
||||
F89B5CC229D01BF700549F2F /* InstanceView.swift in Sources */,
|
||||
F825F0CB29F7CFC4008BD204 /* FollowRequestsView.swift in Sources */,
|
||||
F825F0C929F7A562008BD204 /* UserProfilePrivateAccountView.swift in Sources */,
|
||||
F89F57B029D1C11200001EE3 /* RelationshipModel.swift in Sources */,
|
||||
F88AB05829B36B8200345EDE /* AccountsPhotoView.swift in Sources */,
|
||||
F85D4971296402DC00751DF7 /* AuthorizationService.swift in Sources */,
|
||||
|
|
|
@ -50,6 +50,8 @@ extension View {
|
|||
EditProfileView()
|
||||
case .instance:
|
||||
InstanceView()
|
||||
case .followRequests:
|
||||
FollowRequestsView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,12 @@ import Foundation
|
|||
import PixelfedKit
|
||||
|
||||
public class RelationshipModel: ObservableObject {
|
||||
enum RelationshipAction {
|
||||
case follow
|
||||
case unfollow
|
||||
case requestFollow
|
||||
case cancelRequestFollow
|
||||
}
|
||||
|
||||
/// The account ID.
|
||||
@Published public var id: EntityId
|
||||
|
@ -122,3 +128,25 @@ extension RelationshipModel {
|
|||
self.note = relationship.note
|
||||
}
|
||||
}
|
||||
|
||||
extension RelationshipModel {
|
||||
func haveAccessToPhotos(account: Account) -> Bool {
|
||||
return !account.locked || (account.locked && self.following)
|
||||
}
|
||||
|
||||
func getRelationshipAction(account: Account) -> RelationshipAction {
|
||||
if self.following {
|
||||
return .unfollow
|
||||
}
|
||||
|
||||
if self.requested {
|
||||
return .cancelRequestFollow
|
||||
}
|
||||
|
||||
if account.locked {
|
||||
return .requestFollow
|
||||
}
|
||||
|
||||
return .follow
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ enum RouteurDestinations: Hashable {
|
|||
case search
|
||||
case editProfile
|
||||
case instance
|
||||
case followRequests
|
||||
}
|
||||
|
||||
enum SheetDestinations: Identifiable {
|
||||
|
|
|
@ -0,0 +1,179 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the Apache License 2.0.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import PixelfedKit
|
||||
import ClientKit
|
||||
import Foundation
|
||||
import ServicesKit
|
||||
import EnvironmentKit
|
||||
import WidgetsKit
|
||||
|
||||
struct FollowRequestsView: View {
|
||||
@EnvironmentObject var applicationState: ApplicationState
|
||||
@EnvironmentObject var routerPath: RouterPath
|
||||
@EnvironmentObject var client: Client
|
||||
|
||||
@State private var accounts: [Account] = []
|
||||
@State private var downloadedPage = 1
|
||||
@State private var allItemsLoaded = false
|
||||
@State private var state: ViewState = .loading
|
||||
|
||||
var body: some View {
|
||||
self.mainBody()
|
||||
.navigationTitle("followingRequests.navigationBar.title")
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func mainBody() -> some View {
|
||||
switch state {
|
||||
case .loading:
|
||||
LoadingIndicator()
|
||||
.task {
|
||||
await self.loadData(page: self.downloadedPage)
|
||||
}
|
||||
case .loaded:
|
||||
if self.accounts.isEmpty {
|
||||
NoDataView(imageSystemName: "person.3.sequence", text: "accounts.title.noAccounts")
|
||||
} else {
|
||||
self.list()
|
||||
}
|
||||
case .error(let error):
|
||||
ErrorView(error: error) {
|
||||
self.state = .loading
|
||||
|
||||
self.downloadedPage = 1
|
||||
self.allItemsLoaded = false
|
||||
self.accounts = []
|
||||
await self.loadData(page: self.downloadedPage)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func list() -> some View {
|
||||
List {
|
||||
ForEach(accounts, id: \.id) { account in
|
||||
NavigationLink(value: RouteurDestinations.userProfile(
|
||||
accountId: account.id,
|
||||
accountDisplayName: account.displayNameWithoutEmojis,
|
||||
accountUserName: account.acct)
|
||||
) {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .center) {
|
||||
UserAvatar(accountAvatar: account.avatar, size: .list)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(account.displayName ?? account.username)
|
||||
.foregroundColor(.mainTextColor)
|
||||
Text("@\(account.acct)")
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.font(.footnote)
|
||||
}
|
||||
.padding(.leading, 8)
|
||||
}
|
||||
|
||||
if let note = account.note, !note.asMarkdown.isEmpty {
|
||||
MarkdownFormattedText(note.asMarkdown)
|
||||
.font(.footnote)
|
||||
.environment(\.openURL, OpenURLAction { url in
|
||||
routerPath.handle(url: url)
|
||||
})
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
}
|
||||
.swipeActions {
|
||||
Button {
|
||||
} label: {
|
||||
Label("followingRequests.title.approve", systemImage: "checkmark")
|
||||
}
|
||||
|
||||
Button(role: .destructive) {
|
||||
} label: {
|
||||
Label("followingRequests.title.reject", systemImage: "xmark")
|
||||
}
|
||||
.tint(.dangerColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if allItemsLoaded == false {
|
||||
HStack {
|
||||
Spacer()
|
||||
LoadingIndicator()
|
||||
.task {
|
||||
self.downloadedPage = self.downloadedPage + 1
|
||||
await self.loadData(page: self.downloadedPage)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
}
|
||||
|
||||
private func loadData(page: Int) async {
|
||||
do {
|
||||
try await self.loadAccounts(page: page)
|
||||
self.state = .loaded
|
||||
} catch {
|
||||
if !Task.isCancelled {
|
||||
ErrorService.shared.handle(error, message: "accounts.error.loadingAccountsFailed", showToastr: true)
|
||||
self.state = .error(error)
|
||||
} else {
|
||||
ErrorService.shared.handle(error, message: "accounts.error.loadingAccountsFailed", showToastr: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func loadAccounts(page: Int) async throws {
|
||||
let accountsFromApi = try await self.loadFromApi(page: page)
|
||||
|
||||
if accountsFromApi.isEmpty {
|
||||
self.allItemsLoaded = true
|
||||
return
|
||||
}
|
||||
|
||||
await self.downloadAvatars(accounts: accountsFromApi)
|
||||
self.accounts.append(contentsOf: accountsFromApi)
|
||||
}
|
||||
|
||||
private func loadFromApi(page: Int) async throws -> [Account] {
|
||||
// TODO: Workaround for not working paging for favourites/reblogged issues: https://github.com/pixelfed/pixelfed/issues/4182.
|
||||
if page == 1 {
|
||||
let results = try await self.client.followRequests?.followRequests(limit: 100, page: page)
|
||||
return results ?? []
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
private func approve(account: Account) async {
|
||||
do {
|
||||
_ = try await self.client.followRequests?.authorizeRequest(id: account.id)
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "followingRequests.error.approve", showToastr: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func reject(account: Account) async {
|
||||
do {
|
||||
_ = try await self.client.followRequests?.rejectRequest(id: account.id)
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "followingRequests.error.reject", showToastr: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func downloadAvatars(accounts: [Account]) async {
|
||||
await withTaskGroup(of: Void.self) { group in
|
||||
for account in accounts {
|
||||
group.addTask { await CacheImageService.shared.download(url: account.avatar) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,18 +21,6 @@ struct UserProfileHeaderView: View {
|
|||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .top) {
|
||||
Spacer()
|
||||
|
||||
if self.relationship.muting == true {
|
||||
TagWidget(value: "userProfile.title.muted", color: .accentColor, systemImage: "message.and.waveform.fill")
|
||||
}
|
||||
|
||||
if self.relationship.blocking == true {
|
||||
TagWidget(value: "userProfile.title.blocked", color: .dangerColor, systemImage: "hand.raised.fill")
|
||||
}
|
||||
}
|
||||
|
||||
HStack(alignment: .center) {
|
||||
UserAvatar(accountAvatar: account.avatar, size: .profile)
|
||||
|
||||
|
@ -108,6 +96,8 @@ struct UserProfileHeaderView: View {
|
|||
.font(.footnote)
|
||||
}
|
||||
|
||||
self.accountRelationshipPanel()
|
||||
|
||||
Text(String(format: NSLocalizedString("userProfile.title.joined", comment: "Joined"), account.createdAt.toRelative(.isoDateTimeMilliSec)))
|
||||
.foregroundColor(.lightGrayColor.opacity(0.5))
|
||||
.font(.footnote)
|
||||
|
@ -115,6 +105,27 @@ struct UserProfileHeaderView: View {
|
|||
.padding()
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func accountRelationshipPanel() -> some View {
|
||||
if self.relationship.followedBy || self.relationship.muting || self.relationship.blocking {
|
||||
HStack(alignment: .top) {
|
||||
if self.relationship.followedBy {
|
||||
TagWidget(value: "userProfile.title.followsYou", color: .secondary, systemImage: "person.crop.circle.badge.checkmark")
|
||||
}
|
||||
|
||||
if self.relationship.muting {
|
||||
TagWidget(value: "userProfile.title.muted", color: .accentColor, systemImage: "message.and.waveform.fill")
|
||||
}
|
||||
|
||||
if self.relationship.blocking {
|
||||
TagWidget(value: "userProfile.title.blocked", color: .dangerColor, systemImage: "hand.raised.fill")
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func otherAccountActionButtons() -> some View {
|
||||
ActionButton {
|
||||
|
@ -122,24 +133,51 @@ struct UserProfileHeaderView: View {
|
|||
} label: {
|
||||
HStack {
|
||||
Image(systemName: relationship.following == true ? "person.badge.minus" : "person.badge.plus")
|
||||
Text(relationship.following == true
|
||||
? "userProfile.title.unfollow"
|
||||
: (relationship.followedBy == true ? "userProfile.title.followBack" : "userProfile.title.follow"), comment: "Follow/unfollow actions")
|
||||
Text(self.getRelationshipActionText(), comment: "Follow/unfollow actions")
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.tint(relationship.following == true ? .dangerColor : .accentColor)
|
||||
.tint(self.getTintColor())
|
||||
}
|
||||
|
||||
private func getRelationshipActionText() -> LocalizedStringKey {
|
||||
let relationshipAction = self.relationship.getRelationshipAction(account: self.account)
|
||||
|
||||
switch relationshipAction {
|
||||
case .follow:
|
||||
return "userProfile.title.follow"
|
||||
case .cancelRequestFollow:
|
||||
return "userProfile.title.cancelRequestFollow"
|
||||
case .requestFollow:
|
||||
return "userProfile.title.requestFollow"
|
||||
case .unfollow:
|
||||
return "userProfile.title.unfollow"
|
||||
}
|
||||
}
|
||||
|
||||
private func getTintColor() -> Color {
|
||||
let relationshipAction = self.relationship.getRelationshipAction(account: self.account)
|
||||
|
||||
switch relationshipAction {
|
||||
case .follow, .requestFollow:
|
||||
return .accentColor
|
||||
case .cancelRequestFollow, .unfollow:
|
||||
return .dangerColor
|
||||
}
|
||||
}
|
||||
|
||||
private func onRelationshipButtonTap() async {
|
||||
do {
|
||||
if self.relationship.following == true {
|
||||
if let relationship = try await self.client.accounts?.unfollow(account: self.account.id) {
|
||||
self.relationship.following = relationship.following
|
||||
}
|
||||
} else {
|
||||
let relationshipAction = self.relationship.getRelationshipAction(account: self.account)
|
||||
|
||||
switch relationshipAction {
|
||||
case .follow, .requestFollow:
|
||||
if let relationship = try await self.client.accounts?.follow(account: self.account.id) {
|
||||
self.relationship.following = relationship.following
|
||||
self.relationship.update(relationship: relationship)
|
||||
}
|
||||
case .cancelRequestFollow, .unfollow:
|
||||
if let relationship = try await self.client.accounts?.unfollow(account: self.account.id) {
|
||||
self.relationship.update(relationship: relationship)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the Apache License 2.0.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct UserProfilePrivateAccountView: View {
|
||||
var body: some View {
|
||||
VStack(alignment: .center) {
|
||||
Spacer()
|
||||
Text("userProfile.title.privateProfileTitle", comment: "This profile is private.")
|
||||
.fontWeight(.bold)
|
||||
.font(.headline)
|
||||
Text("userProfile.title.privateProfileSubtitle", comment: "Only approved followers can see photos.")
|
||||
.fontWeight(.light)
|
||||
.font(.subheadline)
|
||||
Spacer()
|
||||
}.padding(.top, 60)
|
||||
}
|
||||
}
|
|
@ -66,7 +66,12 @@ struct UserProfileView: View {
|
|||
ScrollView {
|
||||
UserProfileHeaderView(account: account, relationship: relationship)
|
||||
.id(self.viewId)
|
||||
|
||||
if self.applicationState.account?.id == account.id || self.relationship.haveAccessToPhotos(account: account) {
|
||||
UserProfileStatusesView(accountId: account.id)
|
||||
} else {
|
||||
UserProfilePrivateAccountView()
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if let updatedProfile = self.applicationState.updatedProfile {
|
||||
|
@ -189,13 +194,7 @@ struct UserProfileView: View {
|
|||
|
||||
Divider()
|
||||
|
||||
NavigationLink(value: RouteurDestinations.accounts(listType: .blocks)) {
|
||||
Label(NSLocalizedString("userProfile.title.blocks", comment: "Blocked accounts"), systemImage: "hand.raised.fill")
|
||||
}
|
||||
|
||||
NavigationLink(value: RouteurDestinations.accounts(listType: .mutes)) {
|
||||
Label(NSLocalizedString("userProfile.title.mutes", comment: "Muted accounts"), systemImage: "message.and.waveform.fill")
|
||||
}
|
||||
self.accountsMenuView(account: account)
|
||||
|
||||
Divider()
|
||||
|
||||
|
@ -220,6 +219,23 @@ struct UserProfileView: View {
|
|||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func accountsMenuView(account: Account) -> some View {
|
||||
NavigationLink(value: RouteurDestinations.accounts(listType: .blocks)) {
|
||||
Label(NSLocalizedString("userProfile.title.blocks", comment: "Blocked accounts"), systemImage: "hand.raised.fill")
|
||||
}
|
||||
|
||||
NavigationLink(value: RouteurDestinations.accounts(listType: .mutes)) {
|
||||
Label(NSLocalizedString("userProfile.title.mutes", comment: "Muted accounts"), systemImage: "message.and.waveform.fill")
|
||||
}
|
||||
|
||||
if account.locked {
|
||||
NavigationLink(value: RouteurDestinations.followRequests) {
|
||||
Label(NSLocalizedString("userProfile.title.followRequests", comment: "FollowRequests"), systemImage: "person.crop.circle.badge.checkmark")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func onMuteAccount(account: Account) async {
|
||||
do {
|
||||
if self.relationship.muting == true {
|
||||
|
|
|
@ -30,8 +30,8 @@ public struct TagWidget: View {
|
|||
.font(.footnote)
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 2)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 6)
|
||||
.background(Capsule().foregroundColor(self.color))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue