Private account request

This commit is contained in:
lumaa-dev 2024-10-04 00:27:52 +02:00
parent e89282704a
commit d1067bdd5b
2 changed files with 208 additions and 92 deletions

View File

@ -82,13 +82,13 @@
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
"value" : "Bubble is a very simple Mastodon client, that is meant to look clean and expurgated. It integrates perfectly with your Mastodon account, and matches your vibe, while having Mastodon-only features.\n\nBubble is mainly free, made in France using SwiftUI, [open-source](https://github.com/lumaa-dev/BubbleApp), and doesn't violate [your privacy](https://apps.lumaa.fr/legal/privacy).\n\nBubble is not related or affiliated to Mastodon gGmbH." "value" : "Bubble is a very simple Mastodon client, that is meant to look clean and expurgated. It integrates perfectly with your Mastodon account, and matches your vibe, while having Mastodon-only features.\n\nBubble is mainly free, made in France using SwiftUI, [open-source](https://github.com/lumaa-dev/BubbleApp), and doesn't violate [your privacy](https://apps.lumaa.fr/legal/privacy?app=bubble).\n\nBubble is not related or affiliated to Mastodon gGmbH."
} }
}, },
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
"value" : "Bubble is a very simple Mastodon client, that is meant to look clean and expurgated. It integrates perfectly with your Mastodon account, and matches your vibe, while having Mastodon-only features.\n\nBubble is mainly free, made in France using SwiftUI, [open-source](https://github.com/lumaa-dev/BubbleApp), and doesn't violate [your privacy](https://apps.lumaa.fr/legal/privacy).\n\nBubble is not related or affiliated to Mastodon gGmbH." "value" : "Bubble is a very simple Mastodon client, that is meant to look clean and expurgated. It integrates perfectly with your Mastodon account, and matches your vibe, while having Mastodon-only features.\n\nBubble is mainly free, made in France using SwiftUI, [open-source](https://github.com/lumaa-dev/BubbleApp), and doesn't violate [your privacy](https://apps.lumaa.fr/legal/privacy?app=bubble).\n\nBubble is not related or affiliated to Mastodon gGmbH."
} }
} }
} }
@ -589,6 +589,22 @@
} }
} }
}, },
"account.hide-reblog" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Hide reblogs"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Cacher les republications"
}
}
}
},
"account.mention" : { "account.mention" : {
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -653,6 +669,39 @@
} }
} }
}, },
"account.request-follow" : {
"comment" : "The action to request the selected user to follow their account",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Request Follow"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Demander pour suivre"
}
}
}
},
"account.show-reblog" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Show reblogs"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Afficher les republications"
}
}
}
},
"account.subclub.subscribe" : { "account.subclub.subscribe" : {
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -1669,6 +1718,23 @@
} }
} }
}, },
"generic.more" : {
"comment" : "A generic word for \"More actions\"",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "More actions"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Plus d'actions"
}
}
}
},
"info.subclub.collaboration" : { "info.subclub.collaboration" : {
"comment" : "sub.club is a COLLAB, precisely", "comment" : "sub.club is a COLLAB, precisely",
"localizations" : { "localizations" : {
@ -5216,4 +5282,4 @@
} }
}, },
"version" : "1.0" "version" : "1.0"
} }

View File

@ -15,13 +15,17 @@ struct ProfileView: View {
@State private var canFollow: Bool? = nil @State private var canFollow: Bool? = nil
@State private var initialFollowing: Bool = false @State private var initialFollowing: Bool = false
@State private var hasRequested: Bool = false
@State private var isFollowing: Bool = false @State private var isFollowing: Bool = false
@State private var accountFollows: Bool = false @State private var accountFollows: Bool = false
@State private var accountReblogging: Bool = false
@State private var accountNotify: Bool = false
@State private var accountMuted: Bool = false @State private var accountMuted: Bool = false
@State private var accountBlocked: Bool = false @State private var accountBlocked: Bool = false
@State private var instanceBlocked: Bool = false @State private var instanceBlocked: Bool = false
@State private var loadingStatuses: Bool = false @State private var loadingStatuses: Bool = false
@State private var statuses: [Status]? = [] @State private var statuses: [Status]? = []
@State private var statusesPinned: [Status]? = [] @State private var statusesPinned: [Status]? = []
@ -58,70 +62,7 @@ struct ProfileView: View {
.toolbar { .toolbar {
if !isCurrent { if !isCurrent {
ToolbarItem(placement: .primaryAction) { ToolbarItem(placement: .primaryAction) {
Menu { accountMenu
if accountMuted {
Button {
guard let client = accountManager.getClient() else { return }
Task {
do {
_ = try await client.post(endpoint: Accounts.unmute(id: account.id))
accountMuted = false
HapticManager.playHaptics(haptics: Haptic.success)
} catch {
HapticManager.playHaptics(haptics: Haptic.error)
print(error)
}
}
} label: {
Label("account.unmute", systemImage: "speaker.wave.2.fill")
}
} else {
Menu {
ForEach(MuteData.MuteDuration.allCases, id: \.self) { duration in
Button {
guard let client = accountManager.getClient() else { return }
Task {
do {
_ = try await client.post(endpoint: Accounts.mute(id: account.id, json: .init(duration: duration.rawValue)))
accountMuted = true
HapticManager.playHaptics(haptics: Haptic.success)
} catch {
HapticManager.playHaptics(haptics: Haptic.error)
print(error)
}
}
} label: {
Text(duration.description)
}
}
} label: {
Label("account.mute", systemImage: "speaker.slash")
}
}
Button(role: accountBlocked ? .cancel : .destructive) {
guard let client = accountManager.getClient() else { return }
Task {
do {
let endp: Endpoint = accountBlocked ? Accounts.unblock(id: account.id) : Accounts.block(id: account.id)
_ = try await client.post(endpoint: endp)
accountBlocked.toggle()
HapticManager.playHaptics(haptics: Haptic.success)
} catch {
HapticManager.playHaptics(haptics: Haptic.error)
print(error)
}
}
} label: {
Label(accountBlocked ? "account.unblock" : "account.block", systemImage: accountBlocked ? "person.fill.badge.plus" : "person.slash.fill")
}
} label: {
Image(systemName: "shield.righthalf.filled")
.font(.title2)
}
} }
} }
} }
@ -173,7 +114,7 @@ struct ProfileView: View {
.toolbarBackground(Color.appBackground, for: .navigationBar) .toolbarBackground(Color.appBackground, for: .navigationBar)
.toolbarBackground(.automatic, for: .navigationBar) .toolbarBackground(.automatic, for: .navigationBar)
} }
// MARK: - Headers // MARK: - Headers
var wholeSmall: some View { var wholeSmall: some View {
@ -261,6 +202,7 @@ struct ProfileView: View {
.disabled(isSubscribed) .disabled(isSubscribed)
} }
// MARK: Follow button
if (canFollow ?? true) == true { if (canFollow ?? true) == true {
HStack (spacing: 5) { HStack (spacing: 5) {
Button { Button {
@ -269,33 +211,39 @@ struct ProfileView: View {
} }
} label: { } label: {
HStack { HStack {
let localized: LocalizedStringKey = account.locked ? hasRequested || isFollowing ? "account.unfollow" : "account.request-follow" : (
isFollowing ? "account.unfollow" : accountFollows ? "account.follow-back" : "account.follow"
)
Spacer() Spacer()
Text(isFollowing ? "account.unfollow" : accountFollows ? "account.follow-back" : "account.follow") Text(localized)
.font(.callout) .font(.callout)
Spacer() Spacer()
} }
} }
.buttonStyle(LargeButton(filled: true, height: 10)) .buttonStyle(LargeButton(filled: true, height: 10))
Button { if !account.locked || isFollowing {
if let server = account.acct.split(separator: "@").last { Button {
uniNav.presentedSheet = .post(content: "@\(account.username)@\(server)") if let server = account.acct.split(separator: "@").last {
} else { uniNav.presentedSheet = .post(content: "@\(account.username)@\(server)")
let client = accountManager.getClient() } else {
uniNav.presentedSheet = .post(content: "@\(account.username)@\(client?.server ?? "???")") let client = accountManager.getClient()
} uniNav.presentedSheet = .post(content: "@\(account.username)@\(client?.server ?? "???")")
} label: { }
HStack { } label: {
Spacer() HStack {
Text("account.mention") Spacer()
.font(.callout) Text("account.mention")
Spacer() .font(.callout)
Spacer()
}
} }
.buttonStyle(LargeButton(filled: false, height: 10))
} }
.buttonStyle(LargeButton(filled: false, height: 10))
} }
} }
} }
.padding(.vertical, 3.5)
if isCurrent { if isCurrent {
Button { Button {
@ -326,7 +274,8 @@ struct ProfileView: View {
.padding(.horizontal) .padding(.horizontal)
} }
} }
// MARK: Fields
var fields: some View { var fields: some View {
VStack(alignment: .leading, spacing: 7.5) { VStack(alignment: .leading, spacing: 7.5) {
ForEach(account.fields) { field in ForEach(account.fields) { field in
@ -344,7 +293,8 @@ struct ProfileView: View {
TextEmoji(field.value, emojis: account.emojis) TextEmoji(field.value, emojis: account.emojis)
} }
.onTapGesture { .onTapGesture {
if let url = URL(string: field.value.asRawText) { if let url = URL(string: field.value.asRawText), url.absoluteString
.matches(of: #"\b((http|https):\/\/)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,6}(\/[a-zA-Z0-9\-._~:\/?#[\]@!$&'()*+,;=%]*)?\b"#).count > 0 {
HapticManager.playHaptics(haptics: Haptic.success) HapticManager.playHaptics(haptics: Haptic.success)
openURL(url) openURL(url)
} else { } else {
@ -404,14 +354,25 @@ struct ProfileView: View {
} }
} }
} }
// MARK: - Functions & Other
func followAccount() async { func followAccount() async {
if let client = accountManager.getClient() { if let client = accountManager.getClient() {
Task { Task {
let endpoint: Endpoint = isFollowing ? Accounts.unfollow(id: account.id) : Accounts.follow(id: account.id, notify: false, reblogs: true) let endpoint: Endpoint = isFollowing || hasRequested ? Accounts.unfollow(id: account.id) : Accounts.follow(
id: account.id,
notify: false,
reblogs: true
)
HapticManager.playHaptics(haptics: Haptic.tap) HapticManager.playHaptics(haptics: Haptic.tap)
_ = try await client.post(endpoint: endpoint) // Notify off until APNs? | Reblogs on by default (later changeable) _ = try await client.post(endpoint: endpoint) // Notify off until APNs? | Reblogs on by default (later changeable)
isFollowing = !isFollowing
if !account.locked {
isFollowing = !isFollowing
} else {
hasRequested = !hasRequested
}
} }
} }
} }
@ -431,7 +392,6 @@ struct ProfileView: View {
await updateRelationship() await updateRelationship()
} }
loadingStatuses = true loadingStatuses = true
statuses = try? await client.get(endpoint: Accounts.statuses(id: accId, sinceId: nil, tag: nil, onlyMedia: nil, excludeReplies: nil, pinned: nil)) statuses = try? await client.get(endpoint: Accounts.statuses(id: accId, sinceId: nil, tag: nil, onlyMedia: nil, excludeReplies: nil, pinned: nil))
statusesPinned = try? await client.get(endpoint: Accounts.statuses(id: accId, sinceId: nil, tag: nil, onlyMedia: nil, excludeReplies: nil, pinned: true)) statusesPinned = try? await client.get(endpoint: Accounts.statuses(id: accId, sinceId: nil, tag: nil, onlyMedia: nil, excludeReplies: nil, pinned: true))
@ -439,7 +399,94 @@ struct ProfileView: View {
} }
} }
} }
var accountMenu: some View {
Menu {
Button {
guard let client = accountManager.getClient() else { return }
Task {
do {
_ = try await client.post(endpoint: Accounts.follow(id: account.id, notify: accountNotify, reblogs: !accountReblogging))
accountReblogging.toggle()
HapticManager.playHaptics(haptics: Haptic.success)
} catch {
HapticManager.playHaptics(haptics: Haptic.error)
print(error)
}
}
} label: {
Label(accountReblogging ? "account.hide-reblog" : "account.show-reblog", systemImage: accountReblogging ? "bolt.horizontal.fill" : "bolt.horizontal")
}
.disabled(!isFollowing)
Divider()
if accountMuted {
Button {
guard let client = accountManager.getClient() else { return }
Task {
do {
_ = try await client.post(endpoint: Accounts.unmute(id: account.id))
accountMuted = false
HapticManager.playHaptics(haptics: Haptic.success)
} catch {
HapticManager.playHaptics(haptics: Haptic.error)
print(error)
}
}
} label: {
Label("account.unmute", systemImage: "speaker.wave.2.fill")
}
} else {
Menu {
ForEach(MuteData.MuteDuration.allCases, id: \.self) { duration in
Button {
guard let client = accountManager.getClient() else { return }
Task {
do {
_ = try await client.post(endpoint: Accounts.mute(id: account.id, json: .init(duration: duration.rawValue)))
accountMuted = true
HapticManager.playHaptics(haptics: Haptic.success)
} catch {
HapticManager.playHaptics(haptics: Haptic.error)
print(error)
}
}
} label: {
Text(duration.description)
}
}
} label: {
Label("account.mute", systemImage: "speaker.slash")
}
}
Button(role: accountBlocked ? .cancel : .destructive) {
guard let client = accountManager.getClient() else { return }
Task {
do {
let endp: Endpoint = accountBlocked ? Accounts.unblock(id: account.id) : Accounts.block(id: account.id)
_ = try await client.post(endpoint: endp)
accountBlocked.toggle()
HapticManager.playHaptics(haptics: Haptic.success)
} catch {
HapticManager.playHaptics(haptics: Haptic.error)
print(error)
}
}
} label: {
Label(accountBlocked ? "account.unblock" : "account.block", systemImage: accountBlocked ? "person.fill.badge.plus" : "person.slash.fill")
}
} label: {
Label("generic.more", systemImage: "ellipsis")
}
}
func updateRelationship(with other: [String] = [], subClubId: String? = nil) async { func updateRelationship(with other: [String] = [], subClubId: String? = nil) async {
if let client = accountManager.getClient() { if let client = accountManager.getClient() {
if let currentAccount: Account = try? await client.get(endpoint: Accounts.verifyCredentials) { if let currentAccount: Account = try? await client.get(endpoint: Accounts.verifyCredentials) {
@ -452,7 +499,10 @@ struct ProfileView: View {
if let relationship: [Relationship] = try? await client.get(endpoint: Accounts.relationships(ids: relsId)) { if let relationship: [Relationship] = try? await client.get(endpoint: Accounts.relationships(ids: relsId)) {
let rel: Relationship = relationship.first! // the searched up account let rel: Relationship = relationship.first! // the searched up account
isFollowing = rel.following isFollowing = rel.following
hasRequested = rel.requested
accountFollows = rel.followedBy accountFollows = rel.followedBy
accountReblogging = rel.showingReblogs
accountNotify = rel.notifying
accountMuted = rel.muting accountMuted = rel.muting
accountBlocked = rel.blocking accountBlocked = rel.blocking