ios: create group with profile image (#849)
* ios: create group with profile image * update libs
This commit is contained in:
committed by
GitHub
parent
7b9164f95a
commit
ee6f6462cf
@@ -559,8 +559,8 @@ private func sendCommandOkResp(_ cmd: ChatCommand) async throws {
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiNewGroup(_ gp: GroupProfile) throws -> GroupInfo {
|
||||
let r = chatSendCmdSync(.newGroup(groupProfile: gp))
|
||||
func apiNewGroup(_ p: GroupProfile) throws -> GroupInfo {
|
||||
let r = chatSendCmdSync(.newGroup(groupProfile: p))
|
||||
if case let .groupCreated(groupInfo) = r { return groupInfo }
|
||||
throw r
|
||||
}
|
||||
|
||||
@@ -12,25 +12,50 @@ import SimpleXChat
|
||||
struct AddGroupView: View {
|
||||
@Binding var openedSheet: NewChatAction?
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@State private var displayName: String = ""
|
||||
@State private var fullName: String = ""
|
||||
@State private var profile = GroupProfile(displayName: "", fullName: "")
|
||||
@FocusState private var focusDisplayName
|
||||
@FocusState private var focusFullName
|
||||
@State private var showChooseSource = false
|
||||
@State private var showImagePicker = false
|
||||
@State private var showTakePhoto = false
|
||||
@State private var chosenImage: UIImage? = nil
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Create group")
|
||||
Text("Create secret group")
|
||||
.font(.largeTitle)
|
||||
.padding(.vertical, 4)
|
||||
Text("The group is fully decentralized – it is visible only to the members.")
|
||||
.padding(.bottom, 4)
|
||||
Text("Enter group details")
|
||||
.padding(.bottom)
|
||||
|
||||
ZStack(alignment: .center) {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
profileImageView(profile.image)
|
||||
if profile.image != nil {
|
||||
Button {
|
||||
profile.image = nil
|
||||
} label: {
|
||||
Image(systemName: "multiply")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 12)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
editImageButton { showChooseSource = true }
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.padding(.bottom, 4)
|
||||
|
||||
ZStack(alignment: .topLeading) {
|
||||
if !validDisplayName(displayName) {
|
||||
if !validDisplayName(profile.displayName) {
|
||||
Image(systemName: "exclamationmark.circle")
|
||||
.foregroundColor(.red)
|
||||
.padding(.top, 4)
|
||||
}
|
||||
textField("Display name", text: $displayName)
|
||||
textField("Group display name", text: $profile.displayName)
|
||||
.focused($focusDisplayName)
|
||||
.submitLabel(.next)
|
||||
.onSubmit {
|
||||
@@ -38,7 +63,7 @@ struct AddGroupView: View {
|
||||
else { focusDisplayName = true }
|
||||
}
|
||||
}
|
||||
textField("Full name (optional)", text: $fullName)
|
||||
textField("Group full name (optional)", text: $profile.fullName)
|
||||
.focused($focusFullName)
|
||||
.submitLabel(.go)
|
||||
.onSubmit {
|
||||
@@ -58,9 +83,39 @@ struct AddGroupView: View {
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
}
|
||||
.onAppear() {
|
||||
focusDisplayName = true
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
focusDisplayName = true
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.confirmationDialog("Group image", isPresented: $showChooseSource, titleVisibility: .visible) {
|
||||
Button("Take picture") {
|
||||
showTakePhoto = true
|
||||
}
|
||||
Button("Choose from library") {
|
||||
showImagePicker = true
|
||||
}
|
||||
}
|
||||
.fullScreenCover(isPresented: $showTakePhoto) {
|
||||
ZStack {
|
||||
Color.black.edgesIgnoringSafeArea(.all)
|
||||
CameraImagePicker(image: $chosenImage)
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showImagePicker) {
|
||||
LibraryImagePicker(image: $chosenImage) {
|
||||
didSelectItem in showImagePicker = false
|
||||
}
|
||||
}
|
||||
.onChange(of: chosenImage) { image in
|
||||
if let image = image {
|
||||
profile.image = resizeImageToStrSize(cropToSquare(image), maxDataSize: 12500)
|
||||
} else {
|
||||
profile.image = nil
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture { hideKeyboard() }
|
||||
}
|
||||
|
||||
func textField(_ placeholder: LocalizedStringKey, text: Binding<String>) -> some View {
|
||||
@@ -73,12 +128,8 @@ struct AddGroupView: View {
|
||||
|
||||
func createGroup() {
|
||||
hideKeyboard()
|
||||
let groupProfile = GroupProfile(
|
||||
displayName: displayName,
|
||||
fullName: fullName
|
||||
)
|
||||
do {
|
||||
let groupInfo = try apiNewGroup(groupProfile)
|
||||
let groupInfo = try apiNewGroup(profile)
|
||||
m.addChat(Chat(chatInfo: .group(groupInfo: groupInfo), chatItems: []))
|
||||
openedSheet = nil
|
||||
DispatchQueue.main.async {
|
||||
@@ -88,7 +139,7 @@ struct AddGroupView: View {
|
||||
openedSheet = nil
|
||||
AlertManager.shared.showAlert(
|
||||
Alert(
|
||||
title: Text("Failed to create group"),
|
||||
title: Text("Error creating group"),
|
||||
message: Text(responseError(error))
|
||||
)
|
||||
)
|
||||
@@ -100,7 +151,7 @@ struct AddGroupView: View {
|
||||
}
|
||||
|
||||
func canCreateProfile() -> Bool {
|
||||
displayName != "" && validDisplayName(displayName)
|
||||
profile.displayName != "" && validDisplayName(profile.displayName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ struct NewChatButton: View {
|
||||
Button("Create link / QR code") { addContactAction() }
|
||||
Button("Paste received link") { actionSheet = .pasteLink }
|
||||
Button("Scan QR code") { actionSheet = .scanQRCode }
|
||||
Button("Create group") { actionSheet = .createGroup }
|
||||
Button("Create secret group") { actionSheet = .createGroup }
|
||||
}
|
||||
.sheet(item: $actionSheet) { sheet in
|
||||
switch sheet {
|
||||
|
||||
@@ -113,7 +113,7 @@ struct CreateProfile: View {
|
||||
}
|
||||
|
||||
func validDisplayName(_ name: String) -> Bool {
|
||||
name.firstIndex(of: " ") == nil
|
||||
name.firstIndex(of: " ") == nil && name.first != "@" && name.first != "#"
|
||||
}
|
||||
|
||||
struct CreateProfile_Previews: PreviewProvider {
|
||||
|
||||
@@ -130,23 +130,6 @@ struct UserProfile: View {
|
||||
.padding(.bottom)
|
||||
}
|
||||
|
||||
func profileImageView(_ imageStr: String?) -> some View {
|
||||
ProfileImage(imageStr: imageStr)
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.frame(maxWidth: 192, maxHeight: 192)
|
||||
}
|
||||
|
||||
func editImageButton(action: @escaping () -> Void) -> some View {
|
||||
Button {
|
||||
action()
|
||||
} label: {
|
||||
Image(systemName: "camera")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 48)
|
||||
}
|
||||
}
|
||||
|
||||
func startEditingImage(_ user: User) {
|
||||
profile = user.profile
|
||||
editProfile = true
|
||||
@@ -170,6 +153,23 @@ struct UserProfile: View {
|
||||
}
|
||||
}
|
||||
|
||||
func profileImageView(_ imageStr: String?) -> some View {
|
||||
ProfileImage(imageStr: imageStr)
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.frame(maxWidth: 192, maxHeight: 192)
|
||||
}
|
||||
|
||||
func editImageButton(action: @escaping () -> Void) -> some View {
|
||||
Button {
|
||||
action()
|
||||
} label: {
|
||||
Image(systemName: "camera")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 48)
|
||||
}
|
||||
}
|
||||
|
||||
struct UserProfile_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let chatModel1 = ChatModel()
|
||||
|
||||
@@ -50,6 +50,11 @@
|
||||
5C971E1D27AEBEF600C8A3CE /* ChatInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C971E1C27AEBEF600C8A3CE /* ChatInfoView.swift */; };
|
||||
5C971E2127AEBF8300C8A3CE /* ChatInfoImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C971E2027AEBF8300C8A3CE /* ChatInfoImage.swift */; };
|
||||
5C9A5BDB2871E05400A5B906 /* SetNotificationsMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9A5BDA2871E05400A5B906 /* SetNotificationsMode.swift */; };
|
||||
5C9C2D9F28929B6900CC63B1 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9C2D9A28929B6900CC63B1 /* libffi.a */; };
|
||||
5C9C2DA028929B6900CC63B1 /* libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9C2D9B28929B6900CC63B1 /* libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw.a */; };
|
||||
5C9C2DA128929B6900CC63B1 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9C2D9C28929B6900CC63B1 /* libgmp.a */; };
|
||||
5C9C2DA228929B6900CC63B1 /* libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9C2D9D28929B6900CC63B1 /* libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw-ghc8.10.7.a */; };
|
||||
5C9C2DA328929B6900CC63B1 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9C2D9E28929B6900CC63B1 /* libgmpxx.a */; };
|
||||
5C9D13A3282187BB00AB8B43 /* WebRTC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9D13A2282187BB00AB8B43 /* WebRTC.swift */; };
|
||||
5C9FD96E27A5D6ED0075386C /* SendMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9FD96D27A5D6ED0075386C /* SendMessageView.swift */; };
|
||||
5CA059DC279559F40002BEB4 /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA059DB279559F40002BEB4 /* Tests_iOS.swift */; };
|
||||
@@ -57,11 +62,6 @@
|
||||
5CA059EB279559F40002BEB4 /* SimpleXApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA059C3279559F40002BEB4 /* SimpleXApp.swift */; };
|
||||
5CA059ED279559F40002BEB4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA059C4279559F40002BEB4 /* ContentView.swift */; };
|
||||
5CA059EF279559F40002BEB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5CA059C5279559F40002BEB4 /* Assets.xcassets */; };
|
||||
5CAB35F828870432009BAA9E /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CAB35F328870432009BAA9E /* libgmpxx.a */; };
|
||||
5CAB35F928870432009BAA9E /* libHSsimplex-chat-3.0.0-LLJFvekvxJh78JPfTQjcLF-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CAB35F428870432009BAA9E /* libHSsimplex-chat-3.0.0-LLJFvekvxJh78JPfTQjcLF-ghc8.10.7.a */; };
|
||||
5CAB35FA28870432009BAA9E /* libHSsimplex-chat-3.0.0-LLJFvekvxJh78JPfTQjcLF.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CAB35F528870432009BAA9E /* libHSsimplex-chat-3.0.0-LLJFvekvxJh78JPfTQjcLF.a */; };
|
||||
5CAB35FB28870432009BAA9E /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CAB35F628870432009BAA9E /* libffi.a */; };
|
||||
5CAB35FC28870432009BAA9E /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CAB35F728870432009BAA9E /* libgmp.a */; };
|
||||
5CB0BA882826CB3A00B3292C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CB0BA862826CB3A00B3292C /* InfoPlist.strings */; };
|
||||
5CB0BA8B2826CB3A00B3292C /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CB0BA892826CB3A00B3292C /* Localizable.strings */; };
|
||||
5CB0BA8E2827126500B3292C /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB0BA8D2827126500B3292C /* OnboardingView.swift */; };
|
||||
@@ -229,6 +229,11 @@
|
||||
5C971E1C27AEBEF600C8A3CE /* ChatInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatInfoView.swift; sourceTree = "<group>"; };
|
||||
5C971E2027AEBF8300C8A3CE /* ChatInfoImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatInfoImage.swift; sourceTree = "<group>"; };
|
||||
5C9A5BDA2871E05400A5B906 /* SetNotificationsMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetNotificationsMode.swift; sourceTree = "<group>"; };
|
||||
5C9C2D9A28929B6900CC63B1 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5C9C2D9B28929B6900CC63B1 /* libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw.a"; sourceTree = "<group>"; };
|
||||
5C9C2D9C28929B6900CC63B1 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5C9C2D9D28929B6900CC63B1 /* libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw-ghc8.10.7.a"; sourceTree = "<group>"; };
|
||||
5C9C2D9E28929B6900CC63B1 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5C9D13A2282187BB00AB8B43 /* WebRTC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRTC.swift; sourceTree = "<group>"; };
|
||||
5C9FD96A27A56D4D0075386C /* JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = "<group>"; };
|
||||
5C9FD96D27A5D6ED0075386C /* SendMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageView.swift; sourceTree = "<group>"; };
|
||||
@@ -239,11 +244,6 @@
|
||||
5CA059D7279559F40002BEB4 /* Tests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
5CA059DB279559F40002BEB4 /* Tests_iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_iOS.swift; sourceTree = "<group>"; };
|
||||
5CA059DD279559F40002BEB4 /* Tests_iOSLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_iOSLaunchTests.swift; sourceTree = "<group>"; };
|
||||
5CAB35F328870432009BAA9E /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5CAB35F428870432009BAA9E /* libHSsimplex-chat-3.0.0-LLJFvekvxJh78JPfTQjcLF-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-3.0.0-LLJFvekvxJh78JPfTQjcLF-ghc8.10.7.a"; sourceTree = "<group>"; };
|
||||
5CAB35F528870432009BAA9E /* libHSsimplex-chat-3.0.0-LLJFvekvxJh78JPfTQjcLF.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-3.0.0-LLJFvekvxJh78JPfTQjcLF.a"; sourceTree = "<group>"; };
|
||||
5CAB35F628870432009BAA9E /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5CAB35F728870432009BAA9E /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5CB0BA872826CB3A00B3292C /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
5CB0BA8A2826CB3A00B3292C /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
5CB0BA8D2827126500B3292C /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
|
||||
@@ -337,13 +337,13 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5CAB35F828870432009BAA9E /* libgmpxx.a in Frameworks */,
|
||||
5CAB35FC28870432009BAA9E /* libgmp.a in Frameworks */,
|
||||
5CAB35FB28870432009BAA9E /* libffi.a in Frameworks */,
|
||||
5C9C2DA028929B6900CC63B1 /* libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw.a in Frameworks */,
|
||||
5C9C2DA128929B6900CC63B1 /* libgmp.a in Frameworks */,
|
||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||
5CAB35FA28870432009BAA9E /* libHSsimplex-chat-3.0.0-LLJFvekvxJh78JPfTQjcLF.a in Frameworks */,
|
||||
5C9C2DA228929B6900CC63B1 /* libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw-ghc8.10.7.a in Frameworks */,
|
||||
5C9C2D9F28929B6900CC63B1 /* libffi.a in Frameworks */,
|
||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||
5CAB35F928870432009BAA9E /* libHSsimplex-chat-3.0.0-LLJFvekvxJh78JPfTQjcLF-ghc8.10.7.a in Frameworks */,
|
||||
5C9C2DA328929B6900CC63B1 /* libgmpxx.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -399,11 +399,11 @@
|
||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5CAB35F628870432009BAA9E /* libffi.a */,
|
||||
5CAB35F728870432009BAA9E /* libgmp.a */,
|
||||
5CAB35F328870432009BAA9E /* libgmpxx.a */,
|
||||
5CAB35F428870432009BAA9E /* libHSsimplex-chat-3.0.0-LLJFvekvxJh78JPfTQjcLF-ghc8.10.7.a */,
|
||||
5CAB35F528870432009BAA9E /* libHSsimplex-chat-3.0.0-LLJFvekvxJh78JPfTQjcLF.a */,
|
||||
5C9C2D9A28929B6900CC63B1 /* libffi.a */,
|
||||
5C9C2D9C28929B6900CC63B1 /* libgmp.a */,
|
||||
5C9C2D9E28929B6900CC63B1 /* libgmpxx.a */,
|
||||
5C9C2D9D28929B6900CC63B1 /* libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw-ghc8.10.7.a */,
|
||||
5C9C2D9B28929B6900CC63B1 /* libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
|
||||
@@ -95,7 +95,7 @@ public enum ChatCommand {
|
||||
case let .apiVerifyToken(token, nonce, code): return "/_ntf verify \(token.cmdString) \(nonce) \(code)"
|
||||
case let .apiDeleteToken(token): return "/_ntf delete \(token.cmdString)"
|
||||
case let .apiGetNtfMessage(nonce, encNtfInfo): return "/_ntf message \(nonce) \(encNtfInfo)"
|
||||
case let .newGroup(groupProfile): return "/group \(groupProfile.displayName) \(groupProfile.fullName)"
|
||||
case let .newGroup(groupProfile): return "/_group \(encodeJSON(groupProfile))"
|
||||
case let .apiAddMember(groupId, contactId, memberRole): return "/_add #\(groupId) \(contactId) \(memberRole)"
|
||||
case let .apiJoinGroup(groupId): return "/_join #\(groupId)"
|
||||
case let .apiRemoveMember(groupId, memberId): return "/_remove #\(groupId) \(memberId)"
|
||||
|
||||
Reference in New Issue
Block a user