diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ea5e2e..beb7ed1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,9 @@ All notable changes to this project will be documented in this file. ## [Unreleased] ### Added -- Added a warning to notify users about errors performing actions outside of home instance + +- Blocking users via a post +- Warning to notify users about errors performing actions outside of home instance ### Misc - Removed `Shiny` package due to lag diff --git a/Lunar.xcodeproj/project.pbxproj b/Lunar.xcodeproj/project.pbxproj index b7e38c8..0ed0d36 100644 --- a/Lunar.xcodeproj/project.pbxproj +++ b/Lunar.xcodeproj/project.pbxproj @@ -80,6 +80,8 @@ 3C3E75F42B210AF900F25F7D /* PulseWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3E75F32B210AF900F25F7D /* PulseWriter.swift */; }; 3C3E75F62B21102200F25F7D /* RealmWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3E75F52B21102200F25F7D /* RealmWriter.swift */; }; 3C3E75F82B21158100F25F7D /* GenerateHeaders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3E75F72B21158100F25F7D /* GenerateHeaders.swift */; }; + 3C3E75FA2B21239400F25F7D /* BlockUserSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3E75F92B21239400F25F7D /* BlockUserSender.swift */; }; + 3C3E75FC2B212F2600F25F7D /* BlockUserResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3E75FB2B212F2600F25F7D /* BlockUserResponseModel.swift */; }; 3C41EEC82AFECEF200F47931 /* CheckBiometricType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C41EEC72AFECEF200F47931 /* CheckBiometricType.swift */; }; 3C42AD1A2A8038300056AFBC /* KbinCommentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C42AD192A8038300056AFBC /* KbinCommentsView.swift */; }; 3C42AD1C2A803AE50056AFBC /* KbinCommentsFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C42AD1B2A803AE50056AFBC /* KbinCommentsFetcher.swift */; }; @@ -365,6 +367,8 @@ 3C3E75F32B210AF900F25F7D /* PulseWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PulseWriter.swift; sourceTree = ""; }; 3C3E75F52B21102200F25F7D /* RealmWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmWriter.swift; sourceTree = ""; }; 3C3E75F72B21158100F25F7D /* GenerateHeaders.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenerateHeaders.swift; sourceTree = ""; }; + 3C3E75F92B21239400F25F7D /* BlockUserSender.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockUserSender.swift; sourceTree = ""; }; + 3C3E75FB2B212F2600F25F7D /* BlockUserResponseModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockUserResponseModel.swift; sourceTree = ""; }; 3C41EEC72AFECEF200F47931 /* CheckBiometricType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckBiometricType.swift; sourceTree = ""; }; 3C42AD192A8038300056AFBC /* KbinCommentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KbinCommentsView.swift; sourceTree = ""; }; 3C42AD1B2A803AE50056AFBC /* KbinCommentsFetcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KbinCommentsFetcher.swift; sourceTree = ""; }; @@ -668,6 +672,7 @@ 3C4D42A72A8D0FD1003B6F1D /* VoteSender.swift */, 3CC3E4222ADC4A2600F7F9A9 /* ImageSender.swift */, 3C6BBF712A910F6100312E2B /* SubscriptionActionSender.swift */, + 3C3E75F92B21239400F25F7D /* BlockUserSender.swift */, 3C9E548B2A94A3DC0093DDC7 /* CommentSender.swift */, 3CC3E42C2ADD6EBA00F7F9A9 /* PostSender.swift */, 3CA8A0C22AE570E200EEABD3 /* PostsFetcher.swift */, @@ -682,13 +687,6 @@ path = Fetchers; sourceTree = ""; }; - 3C4D42A62A8D0FB3003B6F1D /* DataSenders */ = { - isa = PBXGroup; - children = ( - ); - path = DataSenders; - sourceTree = ""; - }; 3C4F09F32A54399F009DF8AB = { isa = PBXGroup; children = ( @@ -735,7 +733,6 @@ 3CCAE1842A6ADDE20040EE12 /* Comment View */, 3C3E75F12B20FF1300F25F7D /* Fetchers */, 3CE237332AA4D157000F1D20 /* Common Views */, - 3C4D42A62A8D0FB3003B6F1D /* DataSenders */, 3CA5C4082A57443200BF8F1B /* DataModels */, 3C96EA582A79147C00753199 /* Debugging Views */, 3CC3E4102ADBE39700F7F9A9 /* Defaults */, @@ -860,6 +857,7 @@ 3CC3E4302ADDCE1200F7F9A9 /* CreatePostResponseModel.swift */, 3CC3E4242ADC556300F7F9A9 /* ImageUploadResponseModel.swift */, 3C6BBF732A91108A00312E2B /* SubscribeResponseModel.swift */, + 3C3E75FB2B212F2600F25F7D /* BlockUserResponseModel.swift */, 3C9E54992A954C060093DDC7 /* CommentResponseModel.swift */, 3C1A3F5C2A73BC9F00898FC6 /* CredentialsModel.swift */, 3CC3E4322ADDCF7E00F7F9A9 /* ErrorResponseModel.swift */, @@ -1397,6 +1395,7 @@ 3CECCE252AAE68E80017A605 /* LocalUserProperties.swift in Sources */, 3C87A36A2A9F9C4E0029C18F /* ForceAppearance.swift in Sources */, 3C5ECB2C2A7676B5002BA561 /* PasswordFieldView.swift in Sources */, + 3C3E75FA2B21239400F25F7D /* BlockUserSender.swift in Sources */, 3CB046F82ADB2A220011BFEC /* PersonModel.swift in Sources */, 3CA5C40A2A574FF500BF8F1B /* PostModel.swift in Sources */, 3CB9C72B2AA2423400FB5A10 /* CommentObject.swift in Sources */, @@ -1476,6 +1475,7 @@ 3CECCE1C2AADBFFA0017A605 /* ColorTesterView.swift in Sources */, 3CC3E4232ADC4A2600F7F9A9 /* ImageSender.swift in Sources */, 3C808E242A9B6DAD008FA62E /* SettingsDevOptionsView.swift in Sources */, + 3C3E75FC2B212F2600F25F7D /* BlockUserResponseModel.swift in Sources */, 3C42AD1A2A8038300056AFBC /* KbinCommentsView.swift in Sources */, 3C6299D12A751A5900BE2A9F /* TextExtension.swift in Sources */, 3CF3B8FD2A90188D001C08B5 /* CommentMetadataView.swift in Sources */, diff --git a/Lunar/Common Views/AllSymbols.swift b/Lunar/Common Views/AllSymbols.swift index 8c44dcb..fe4ec22 100644 --- a/Lunar/Common Views/AllSymbols.swift +++ b/Lunar/Common Views/AllSymbols.swift @@ -28,6 +28,10 @@ class AllSymbols { SFSafeSymbols.SFSymbol.slashCircle } + var blockContextIcon: SFSafeSymbols.SFSymbol { + SFSafeSymbols.SFSymbol.xmarkCircle + } + var upvoteContextIcon: SFSafeSymbols.SFSymbol { SFSafeSymbols.SFSymbol.arrowUpCircle } diff --git a/Lunar/DataModels/AccountModel.swift b/Lunar/DataModels/AccountModel.swift index 67a5300..1adbfd0 100644 --- a/Lunar/DataModels/AccountModel.swift +++ b/Lunar/DataModels/AccountModel.swift @@ -19,4 +19,5 @@ struct AccountModel: Codable, Hashable, Defaults.Serializable { var postCount: Int = 0 var commentScore: Int = 0 var commentCount: Int = 0 + var instance: String { URLParser.extractDomain(from: actorID) } } diff --git a/Lunar/DataModels/BlockUserResponseModel.swift b/Lunar/DataModels/BlockUserResponseModel.swift new file mode 100644 index 0000000..59d7df4 --- /dev/null +++ b/Lunar/DataModels/BlockUserResponseModel.swift @@ -0,0 +1,18 @@ +// +// BlockUserResponseModel.swift +// Lunar +// +// Created by Mani on 19/08/2023. +// + +import Foundation + +struct BlockUserResponseModel: Codable { + let person: PersonObject? + let blocked: Bool + + enum CodingKeys: String, CodingKey { + case person = "person_view" + case blocked + } +} diff --git a/Lunar/Fetchers/BlockUserSender.swift b/Lunar/Fetchers/BlockUserSender.swift new file mode 100644 index 0000000..4163982 --- /dev/null +++ b/Lunar/Fetchers/BlockUserSender.swift @@ -0,0 +1,75 @@ +// +// BlockUserSender.swift +// Lunar +// +// Created by Mani on 16/08/2023. +// + +import Alamofire +import Defaults +import Foundation +import Pulse +import SwiftUI + +class BlockUserSender: ObservableObject { + @Default(.activeAccount) var activeAccount + @Default(.appBundleID) var appBundleID + + private var personID: Int + private var block: Bool + + init( + personID: Int, + block: Bool + ) { + self.personID = personID + self.block = block + } + + func blockUser(completion: @escaping (Int?, Bool?, String?) -> Void) { + let JSONparameters = + [ + "block": block, + "person_id": personID, + "auth": JWT().getJWTForActiveAccount() as Any, + ] as [String: Any] + + let endpoint = "https://\(activeAccount.instance)/api/v3/user/block" + + AF.request( + endpoint, + method: .post, + parameters: JSONparameters, + encoding: JSONEncoding.default, + headers: GenerateHeaders().generate() + ) + .validate(statusCode: 200 ..< 300) + .responseDecodable(of: BlockUserResponseModel.self) { response in + + PulseWriter().write(response, EndpointParameters(endpointPath: endpoint), .post) + + print(response.request ?? "") + switch response.result { + case let .success(result): + print(result.blocked) + let response = String(response.response?.statusCode ?? 0) + let userIsBlockedResponse = result.blocked + let personID = result.person?.person.id + + completion(personID, userIsBlockedResponse, response) + + case let .failure(error): + if let data = response.data, + let fetchError = try? JSONDecoder().decode(ErrorResponseModel.self, from: data) + { + print("BlockUserSender ERROR: \(fetchError.error)") + completion(nil, nil, fetchError.error) + } else { + let errorDescription = String(describing: error.errorDescription) + print("BlockUserSender JSON DECODE ERROR: \(error): \(errorDescription)") + completion(nil, nil, error.errorDescription) + } + } + } + } +} diff --git a/Lunar/Fetchers/PulseWriter.swift b/Lunar/Fetchers/PulseWriter.swift index 9a6b84a..f130e4b 100644 --- a/Lunar/Fetchers/PulseWriter.swift +++ b/Lunar/Fetchers/PulseWriter.swift @@ -13,6 +13,7 @@ import SwiftUI class PulseWriter { @Default(.networkInspectorEnabled) var networkInspectorEnabled + @Default(.selectedInstance) var selectedInstance let pulse = Pulse.LoggerStore.shared @@ -23,14 +24,26 @@ class PulseWriter { ) { guard networkInspectorEnabled else { return } - pulse.storeRequest( - try! URLRequest( - url: EndpointBuilder(parameters: parameters).build(redact: true), - method: method - ), - response: response.response, - error: response.error, - data: response.data - ) + if method == .get { + pulse.storeRequest( + try! URLRequest( + url: EndpointBuilder(parameters: parameters).build(redact: true), + method: method + ), + response: response.response, + error: response.error, + data: response.data + ) + } else if method == .post { + pulse.storeRequest( + try! URLRequest( + url: URL(string: "https://\(selectedInstance)\(parameters.endpointPath)")!, + method: method + ), + response: response.response, + error: response.error, + data: response.data + ) + } } } diff --git a/Lunar/Fetchers/RealmWriter.swift b/Lunar/Fetchers/RealmWriter.swift index 1710882..a109e5b 100644 --- a/Lunar/Fetchers/RealmWriter.swift +++ b/Lunar/Fetchers/RealmWriter.swift @@ -19,9 +19,8 @@ class RealmWriter { ) { try! realm.write { for post in posts { - guard !post.creatorBlocked else { return } - + let realmPost = RealmPost( postID: post.post.id, postName: post.post.name, diff --git a/Lunar/Fetchers/SubscriptionActionSender.swift b/Lunar/Fetchers/SubscriptionActionSender.swift index 8256531..2b6a0aa 100644 --- a/Lunar/Fetchers/SubscriptionActionSender.swift +++ b/Lunar/Fetchers/SubscriptionActionSender.swift @@ -54,7 +54,7 @@ class SubscriptionActionSender: ObservableObject { ) .validate(statusCode: 200 ..< 300) .responseDecodable(of: SubscribeResponseModel.self) { response in - + PulseWriter().write(response, EndpointParameters(endpointPath: endpoint), .post) print(response.request ?? "") diff --git a/Lunar/Post Views/PostItem.swift b/Lunar/Post Views/PostItem.swift index 349ece5..85dc217 100644 --- a/Lunar/Post Views/PostItem.swift +++ b/Lunar/Post Views/PostItem.swift @@ -19,6 +19,7 @@ struct PostItem: View { @State var showSafari: Bool = false @State var subscribeAlertPresented: Bool = false + @State var blockUserAlertPresented: Bool = false let hapticsLight = UIImpactFeedbackGenerator(style: .light) @@ -110,6 +111,15 @@ struct PostItem: View { hideButton minimiseButton shareButton + blockUserButton + } + } + + var blockUserButton: some View { + Button { + blockUserAlertPresented = true + } label: { + Label("Block User", systemSymbol: AllSymbols().blockContextIcon) } } @@ -210,6 +220,25 @@ struct PostItem: View { } message: { Text("\(post.communityName)@\(URLParser.extractDomain(from: post.communityActorID))") } + .confirmationDialog("", isPresented: $blockUserAlertPresented) { + blockUserConfirmationDialogButtons + } message: { + Text("Block User \(URLParser.buildFullUsername(from: post.personActorID))") + } + } + + @ViewBuilder + var blockUserConfirmationDialogButtons: some View { + Button("Block") { + blockUserAction() + } + Button("Block and Report") { + blockUserAction() +// reportUserAction() + } + Button("Dismiss", role: .cancel) { + blockUserAlertPresented = false + } } var postTitle: some View { @@ -385,4 +414,14 @@ struct PostItem: View { } } } + + func blockUserAction() { + if let personID = post.personID { + BlockUserSender(personID: personID, block: true).blockUser { _, userIsBlockedResponse, _ in + if userIsBlockedResponse == true { + RealmThawFunctions().deleteAction(post: post) + } + } + } + } } diff --git a/Lunar/Settings Views/SettingsAccountView.swift b/Lunar/Settings Views/SettingsAccountView.swift index e18406d..003d4e5 100644 --- a/Lunar/Settings Views/SettingsAccountView.swift +++ b/Lunar/Settings Views/SettingsAccountView.swift @@ -11,7 +11,7 @@ import SwiftUI struct SettingsAccountView: View { @Default(.activeAccount) var activeAccount @Default(.selectedInstance) var selectedInstance - + @State var showingPopover: Bool = false @State var isPresentingConfirm: Bool = false @State var logoutAllUsersButtonClicked: Bool = false @@ -24,7 +24,7 @@ struct SettingsAccountView: View { var body: some View { List { - if !activeAccount.actorID.isEmpty && selectedInstance != URLParser.extractDomain(from: activeAccount.actorID) { + if !activeAccount.actorID.isEmpty, selectedInstance != URLParser.extractDomain(from: activeAccount.actorID) { Section { VStack(spacing: 10) { Text("Note: If the current user's home instance differs from the selected instance, errors may occur while attempting actions such as voting, replying, or blocking.") @@ -42,7 +42,7 @@ struct SettingsAccountView: View { .font(.caption) .foregroundStyle(.gray) } - + Section { if isLoginFlowComplete { LoggedInUsersListView() diff --git a/Lunar/Tools/RealmThawFunctions.swift b/Lunar/Tools/RealmThawFunctions.swift index 4911def..e1d6b43 100644 --- a/Lunar/Tools/RealmThawFunctions.swift +++ b/Lunar/Tools/RealmThawFunctions.swift @@ -22,6 +22,19 @@ class RealmThawFunctions { } } + func deleteAction(post: RealmPost) { + let thawedPost = post.thaw() + if thawedPost?.isInvalidated == false { + let thawedRealm = thawedPost!.realm! + try! thawedRealm.write { + if let thawedPost { + thawedRealm.delete(thawedPost) + } + } + } + hapticsSoft.impactOccurred(intensity: 0.5) + } + func hideAction(post: RealmPost) { let realm = try! Realm() try! realm.write { diff --git a/Lunar/Tools/Utilities/URLParser.swift b/Lunar/Tools/Utilities/URLParser.swift index 8f2b8f7..64bfca5 100644 --- a/Lunar/Tools/Utilities/URLParser.swift +++ b/Lunar/Tools/Utilities/URLParser.swift @@ -54,4 +54,9 @@ enum URLParser { return components.penultimate() ?? "" } + + /// "https://lemmy.world/c/mani" ==> _mani@lemmy.world_ + static func buildFullUsername(from url: String) -> String { + "\(extractUsername(from: url))@\(extractDomain(from: url))" + } }