2022-07-14 16:40:32 +04:00
|
|
|
|
//
|
|
|
|
|
// AddGroupView.swift
|
|
|
|
|
// SimpleX (iOS)
|
|
|
|
|
//
|
|
|
|
|
// Created by JRoberts on 13.07.2022.
|
|
|
|
|
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import SwiftUI
|
|
|
|
|
import SimpleXChat
|
|
|
|
|
|
|
|
|
|
struct AddGroupView: View {
|
|
|
|
|
@EnvironmentObject var m: ChatModel
|
2024-07-03 22:42:13 +01:00
|
|
|
|
@EnvironmentObject var theme: AppTheme
|
2022-07-30 18:46:10 +01:00
|
|
|
|
@Environment(\.dismiss) var dismiss: DismissAction
|
2023-10-26 18:51:45 +04:00
|
|
|
|
@AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false
|
2022-07-30 18:46:10 +01:00
|
|
|
|
@State private var chat: Chat?
|
|
|
|
|
@State private var groupInfo: GroupInfo?
|
2022-07-28 11:49:36 +01:00
|
|
|
|
@State private var profile = GroupProfile(displayName: "", fullName: "")
|
2022-07-14 16:40:32 +04:00
|
|
|
|
@FocusState private var focusDisplayName
|
2022-07-28 11:49:36 +01:00
|
|
|
|
@State private var showChooseSource = false
|
|
|
|
|
@State private var showImagePicker = false
|
|
|
|
|
@State private var showTakePhoto = false
|
|
|
|
|
@State private var chosenImage: UIImage? = nil
|
2023-10-04 17:45:39 +01:00
|
|
|
|
@State private var showInvalidNameAlert = false
|
2025-04-14 21:25:32 +01:00
|
|
|
|
@State private var groupLink: CreatedConnLink?
|
2023-10-26 18:51:45 +04:00
|
|
|
|
@State private var groupLinkMemberRole: GroupMemberRole = .member
|
2022-07-14 16:40:32 +04:00
|
|
|
|
|
|
|
|
|
var body: some View {
|
2022-07-30 18:46:10 +01:00
|
|
|
|
if let chat = chat, let groupInfo = groupInfo {
|
2023-10-26 18:51:45 +04:00
|
|
|
|
if !groupInfo.membership.memberIncognito {
|
|
|
|
|
AddGroupMembersViewCommon(
|
|
|
|
|
chat: chat,
|
|
|
|
|
groupInfo: groupInfo,
|
|
|
|
|
creatingGroup: true,
|
|
|
|
|
showFooterCounter: false
|
|
|
|
|
) { _ in
|
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
2024-08-05 12:58:24 +01:00
|
|
|
|
dismissAllSheets(animated: true) {
|
2024-08-13 21:37:48 +03:00
|
|
|
|
ItemsModel.shared.loadOpenChat(groupInfo.id)
|
2024-08-05 12:58:24 +01:00
|
|
|
|
}
|
2023-10-26 18:51:45 +04:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-08-05 12:58:24 +01:00
|
|
|
|
.navigationBarTitleDisplayMode(.inline)
|
2023-10-26 18:51:45 +04:00
|
|
|
|
} else {
|
|
|
|
|
GroupLinkView(
|
|
|
|
|
groupId: groupInfo.groupId,
|
|
|
|
|
groupLink: $groupLink,
|
|
|
|
|
groupLinkMemberRole: $groupLinkMemberRole,
|
2024-08-05 12:58:24 +01:00
|
|
|
|
showTitle: false,
|
2023-10-26 18:51:45 +04:00
|
|
|
|
creatingGroup: true
|
|
|
|
|
) {
|
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
2024-08-05 12:58:24 +01:00
|
|
|
|
dismissAllSheets(animated: true) {
|
2024-08-13 21:37:48 +03:00
|
|
|
|
ItemsModel.shared.loadOpenChat(groupInfo.id)
|
2024-08-05 12:58:24 +01:00
|
|
|
|
}
|
2023-10-26 18:51:45 +04:00
|
|
|
|
}
|
2022-07-30 18:46:10 +01:00
|
|
|
|
}
|
2024-08-05 12:58:24 +01:00
|
|
|
|
.navigationBarTitle("Group link")
|
2022-07-30 18:46:10 +01:00
|
|
|
|
}
|
|
|
|
|
} else {
|
2024-08-15 20:43:30 +03:00
|
|
|
|
createGroupView()
|
2022-07-30 18:46:10 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func createGroupView() -> some View {
|
2023-10-26 18:51:45 +04:00
|
|
|
|
List {
|
|
|
|
|
Group {
|
|
|
|
|
ZStack(alignment: .center) {
|
|
|
|
|
ZStack(alignment: .topTrailing) {
|
2024-07-03 22:42:13 +01:00
|
|
|
|
ProfileImage(imageStr: profile.image, size: 128)
|
2023-10-26 18:51:45 +04:00
|
|
|
|
if profile.image != nil {
|
|
|
|
|
Button {
|
|
|
|
|
profile.image = nil
|
|
|
|
|
} label: {
|
|
|
|
|
Image(systemName: "multiply")
|
|
|
|
|
.resizable()
|
|
|
|
|
.aspectRatio(contentMode: .fit)
|
|
|
|
|
.frame(width: 12)
|
|
|
|
|
}
|
2022-07-28 11:49:36 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-26 18:51:45 +04:00
|
|
|
|
editImageButton { showChooseSource = true }
|
|
|
|
|
.buttonStyle(BorderlessButtonStyle()) // otherwise whole "list row" is clickable
|
2022-07-14 16:40:32 +04:00
|
|
|
|
}
|
2023-10-26 18:51:45 +04:00
|
|
|
|
.frame(maxWidth: .infinity, alignment: .center)
|
2022-07-14 16:40:32 +04:00
|
|
|
|
}
|
2023-10-26 18:51:45 +04:00
|
|
|
|
.listRowBackground(Color.clear)
|
|
|
|
|
.listRowSeparator(.hidden)
|
|
|
|
|
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
2022-07-14 16:40:32 +04:00
|
|
|
|
|
2023-10-26 18:51:45 +04:00
|
|
|
|
Section {
|
|
|
|
|
groupNameTextField()
|
|
|
|
|
Button(action: createGroup) {
|
2024-07-03 22:42:13 +01:00
|
|
|
|
settingsRow("checkmark", color: theme.colors.primary) { Text("Create group") }
|
2023-10-26 18:51:45 +04:00
|
|
|
|
}
|
|
|
|
|
.disabled(!canCreateProfile())
|
|
|
|
|
IncognitoToggle(incognitoEnabled: $incognitoDefault)
|
|
|
|
|
} footer: {
|
|
|
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
|
|
|
sharedGroupProfileInfo(incognitoDefault)
|
|
|
|
|
Text("Fully decentralized – visible only to members.")
|
|
|
|
|
}
|
2024-07-03 22:42:13 +01:00
|
|
|
|
.foregroundColor(theme.colors.secondary)
|
2023-10-26 18:51:45 +04:00
|
|
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
|
|
|
.onTapGesture(perform: hideKeyboard)
|
2022-07-14 16:40:32 +04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.onAppear() {
|
2022-07-28 11:49:36 +01:00
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
|
|
|
|
focusDisplayName = true
|
|
|
|
|
}
|
2022-07-14 16:40:32 +04:00
|
|
|
|
}
|
2022-07-28 11:49:36 +01:00
|
|
|
|
.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)
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-12-03 21:42:12 +00:00
|
|
|
|
.sheet(isPresented: $showImagePicker) {
|
2023-12-12 09:04:48 +00:00
|
|
|
|
LibraryImagePicker(image: $chosenImage) { _ in
|
|
|
|
|
await MainActor.run {
|
|
|
|
|
showImagePicker = false
|
|
|
|
|
}
|
2022-07-28 11:49:36 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-04 17:45:39 +01:00
|
|
|
|
.alert(isPresented: $showInvalidNameAlert) {
|
|
|
|
|
createInvalidNameAlert(mkValidName(profile.displayName), $profile.displayName)
|
|
|
|
|
}
|
2022-07-28 11:49:36 +01:00
|
|
|
|
.onChange(of: chosenImage) { image in
|
2024-10-05 22:11:57 +03:00
|
|
|
|
Task {
|
|
|
|
|
let resized: String? = if let image {
|
|
|
|
|
await resizeImageToStrSize(cropToSquare(image), maxDataSize: 12500)
|
|
|
|
|
} else {
|
|
|
|
|
nil
|
|
|
|
|
}
|
|
|
|
|
await MainActor.run { profile.image = resized }
|
2022-07-28 11:49:36 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-07-03 22:42:13 +01:00
|
|
|
|
.modifier(ThemedBackground(grouped: true))
|
2023-10-26 18:51:45 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func groupNameTextField() -> some View {
|
|
|
|
|
ZStack(alignment: .leading) {
|
|
|
|
|
let name = profile.displayName.trimmingCharacters(in: .whitespaces)
|
|
|
|
|
if name != mkValidName(name) {
|
|
|
|
|
Button {
|
|
|
|
|
showInvalidNameAlert = true
|
|
|
|
|
} label: {
|
|
|
|
|
Image(systemName: "exclamationmark.circle").foregroundColor(.red)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2024-07-03 22:42:13 +01:00
|
|
|
|
Image(systemName: "pencil").foregroundColor(theme.colors.secondary)
|
2023-10-26 18:51:45 +04:00
|
|
|
|
}
|
|
|
|
|
textField("Enter group name…", text: $profile.displayName)
|
|
|
|
|
.focused($focusDisplayName)
|
|
|
|
|
.submitLabel(.continue)
|
|
|
|
|
.onSubmit {
|
|
|
|
|
if canCreateProfile() { createGroup() }
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-07-14 16:40:32 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func textField(_ placeholder: LocalizedStringKey, text: Binding<String>) -> some View {
|
|
|
|
|
TextField(placeholder, text: text)
|
2023-10-26 18:51:45 +04:00
|
|
|
|
.padding(.leading, 36)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func sharedGroupProfileInfo(_ incognito: Bool) -> Text {
|
|
|
|
|
let name = ChatModel.shared.currentUser?.displayName ?? ""
|
|
|
|
|
return Text(
|
|
|
|
|
incognito
|
|
|
|
|
? "A new random profile will be shared."
|
|
|
|
|
: "Your profile **\(name)** will be shared."
|
|
|
|
|
)
|
2022-07-14 16:40:32 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func createGroup() {
|
|
|
|
|
hideKeyboard()
|
|
|
|
|
do {
|
2023-10-04 17:45:39 +01:00
|
|
|
|
profile.displayName = profile.displayName.trimmingCharacters(in: .whitespaces)
|
2023-12-29 21:46:28 +04:00
|
|
|
|
profile.groupPreferences = GroupPreferences(history: GroupPreference(enable: .on))
|
2023-10-26 18:51:45 +04:00
|
|
|
|
let gInfo = try apiNewGroup(incognito: incognitoDefault, groupProfile: profile)
|
2022-08-09 13:43:19 +04:00
|
|
|
|
Task {
|
2025-02-03 20:47:32 +00:00
|
|
|
|
await m.loadGroupMembers(gInfo)
|
2022-08-09 13:43:19 +04:00
|
|
|
|
}
|
2022-07-30 18:46:10 +01:00
|
|
|
|
let c = Chat(chatInfo: .group(groupInfo: gInfo), chatItems: [])
|
|
|
|
|
m.addChat(c)
|
|
|
|
|
withAnimation {
|
|
|
|
|
groupInfo = gInfo
|
|
|
|
|
chat = c
|
2022-07-14 16:40:32 +04:00
|
|
|
|
}
|
|
|
|
|
} catch {
|
2024-08-05 12:58:24 +01:00
|
|
|
|
dismissAllSheets(animated: true) {
|
|
|
|
|
AlertManager.shared.showAlert(
|
|
|
|
|
Alert(
|
|
|
|
|
title: Text("Error creating group"),
|
|
|
|
|
message: Text(responseError(error))
|
|
|
|
|
)
|
2022-07-27 11:16:07 +04:00
|
|
|
|
)
|
2024-08-05 12:58:24 +01:00
|
|
|
|
}
|
2022-07-14 16:40:32 +04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func canCreateProfile() -> Bool {
|
2023-10-06 04:49:18 +08:00
|
|
|
|
let name = profile.displayName.trimmingCharacters(in: .whitespaces)
|
|
|
|
|
return name != "" && validDisplayName(name)
|
2022-07-14 16:40:32 +04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-30 18:46:10 +01:00
|
|
|
|
func hideKeyboard() {
|
|
|
|
|
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-14 16:40:32 +04:00
|
|
|
|
struct AddGroupView_Previews: PreviewProvider {
|
|
|
|
|
static var previews: some View {
|
2022-07-30 18:46:10 +01:00
|
|
|
|
AddGroupView()
|
2022-07-14 16:40:32 +04:00
|
|
|
|
}
|
|
|
|
|
}
|