mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-04-27 15:06:12 +00:00
035a2f954c
* ui: additional images, views for making connections and creating groups (#6750) * ios: setup for additional assets * ios build config * header * fix * update layout * more views with images * layout * layout * android images and view * fix path * fix desktop * fix desktop build * smaller image * layout * more layout * more kotlin views * group layout * padding * create group layout * more create group layout * layout * tweak layout * more tweak * config --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> * ios: connecting as part of onboarding (#6754) * ios: implementation of "connecting" cards * ios: revision * fix flip * fixes * fix frame * replace nav stack with tab view * rename * update gradient and card label material * fix gradient * debug * remove debug code * update card labels * card label layout * landscape cards * layout * safe area * less bold * debug landscape * refactor titles, back inline with title in landscape * remove ignoreSafeArea * remove extra padding * refactor * clean * layout spec added to plan --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> * android, desktop: connecting during onboarding - new cards (#6757) * android, desktop: connecting during onboarding - new cards * fix * change layout * fixes * fix * fix * layout * fix layout * animation * import * paddings * 350ms * font * fonts * layout * box * more layout * layout * simpler * hide toolbar heading in onboarding mode * simpler desktop layout * better desktop * revert desktop toolbar * bigger font, landscape * fix desktop * cap width * refactor, simplify * qr code scanner icon * use icon without assets * cleaner * fix * fix --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> * android, desktop: connect banner after onboarding (#6761) * android, desktop: connect banner after onboarding * improve * smaller button * bigger icon, same string * fallback gradients * improve build * simpler connect screens during onboarding * left-align * update strings * improve state machine * text, padding * strings * primary color for tap to paste link * fix race condition * fix loading race --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> * ios: banner and connect screens (#6767) * ios: banner and connect screens * fix * return nav * remove padding * refactor * refactor * refactor 2 * refactor 3 * refactor 4 * header * xcode files * improve * fix toolbar * toolbar 2 * no assets * no assets 2 * padding * android padding * simplify * layout * fix --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> * fix refreshable * text * fix toolbar color * rework address share logic * padding --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
241 lines
9.0 KiB
Swift
241 lines
9.0 KiB
Swift
//
|
||
// 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 {
|
||
@Environment(\.colorScheme) var colorScheme
|
||
@EnvironmentObject var m: ChatModel
|
||
@EnvironmentObject var theme: AppTheme
|
||
@Environment(\.dismiss) var dismiss: DismissAction
|
||
@AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false
|
||
@State private var chat: Chat?
|
||
@State private var groupInfo: GroupInfo?
|
||
@State private var profile = GroupProfile(displayName: "", fullName: "")
|
||
@FocusState private var focusDisplayName
|
||
@State private var showChooseSource = false
|
||
@State private var showImagePicker = false
|
||
@State private var showTakePhoto = false
|
||
@State private var chosenImage: UIImage? = nil
|
||
@State private var showInvalidNameAlert = false
|
||
@State private var groupLink: GroupLink?
|
||
@State private var groupLinkMemberRole: GroupMemberRole = .member
|
||
|
||
var body: some View {
|
||
if let chat = chat, let groupInfo = groupInfo {
|
||
if !groupInfo.membership.memberIncognito {
|
||
AddGroupMembersViewCommon(
|
||
chat: chat,
|
||
groupInfo: groupInfo,
|
||
creatingGroup: true,
|
||
showFooterCounter: false
|
||
) { _ in
|
||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||
dismissAllSheets(animated: true) {
|
||
ItemsModel.shared.loadOpenChat(groupInfo.id)
|
||
}
|
||
}
|
||
}
|
||
.navigationBarTitleDisplayMode(.inline)
|
||
} else {
|
||
GroupLinkView(
|
||
groupId: groupInfo.groupId,
|
||
groupLink: $groupLink,
|
||
groupLinkMemberRole: $groupLinkMemberRole,
|
||
showTitle: false,
|
||
creatingGroup: true
|
||
) {
|
||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||
dismissAllSheets(animated: true) {
|
||
ItemsModel.shared.loadOpenChat(groupInfo.id)
|
||
}
|
||
}
|
||
}
|
||
.navigationBarTitle("Group link")
|
||
}
|
||
} else {
|
||
createGroupView()
|
||
}
|
||
}
|
||
|
||
func createGroupView() -> some View {
|
||
List {
|
||
Group {
|
||
HStack(spacing: 0) {
|
||
ZStack(alignment: .center) {
|
||
ZStack(alignment: .topTrailing) {
|
||
ProfileImage(imageStr: profile.image, iconName: "person.2.circle.fill", size: 128)
|
||
if profile.image != nil {
|
||
Button {
|
||
profile.image = nil
|
||
} label: {
|
||
Image(systemName: "multiply")
|
||
.resizable()
|
||
.aspectRatio(contentMode: .fit)
|
||
.frame(width: 12)
|
||
}
|
||
}
|
||
}
|
||
|
||
editImageButton { showChooseSource = true }
|
||
.buttonStyle(BorderlessButtonStyle()) // otherwise whole "list row" is clickable
|
||
}
|
||
.frame(maxWidth: .infinity)
|
||
#if SIMPLEX_ASSETS
|
||
Image(colorScheme == .light ? "create-group" : "create-group-light")
|
||
.resizable()
|
||
.scaledToFit()
|
||
.frame(height: 140)
|
||
.frame(maxWidth: .infinity)
|
||
#endif
|
||
}
|
||
.frame(maxWidth: .infinity)
|
||
}
|
||
.listRowBackground(Color.clear)
|
||
.listRowSeparator(.hidden)
|
||
.listRowInsets(EdgeInsets(top: 8, leading: 0, bottom: 0, trailing: 0))
|
||
|
||
Section {
|
||
groupNameTextField()
|
||
Button(action: createGroup) {
|
||
settingsRow("checkmark", color: theme.colors.primary) { Text("Create group") }
|
||
}
|
||
.disabled(!canCreateProfile())
|
||
IncognitoToggle(incognitoEnabled: $incognitoDefault)
|
||
} footer: {
|
||
VStack(alignment: .leading, spacing: 4) {
|
||
sharedGroupProfileInfo(incognitoDefault)
|
||
Text("Fully decentralized – visible only to members.")
|
||
}
|
||
.foregroundColor(theme.colors.secondary)
|
||
.frame(maxWidth: .infinity, alignment: .leading)
|
||
.onTapGesture {
|
||
focusDisplayName = false
|
||
}
|
||
}
|
||
.compactSectionSpacing()
|
||
}
|
||
.onAppear() {
|
||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||
focusDisplayName = true
|
||
}
|
||
}
|
||
.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) { _ in
|
||
await MainActor.run {
|
||
showImagePicker = false
|
||
}
|
||
}
|
||
}
|
||
.alert(isPresented: $showInvalidNameAlert) {
|
||
createInvalidNameAlert(mkValidName(profile.displayName), $profile.displayName)
|
||
}
|
||
.onChange(of: chosenImage) { image in
|
||
Task {
|
||
let resized: String? = if let image {
|
||
await resizeImageToStrSize(cropToSquare(image), maxDataSize: 12500)
|
||
} else {
|
||
nil
|
||
}
|
||
await MainActor.run { profile.image = resized }
|
||
}
|
||
}
|
||
.modifier(ThemedBackground(grouped: true))
|
||
}
|
||
|
||
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 {
|
||
Image(systemName: "pencil").foregroundColor(theme.colors.secondary)
|
||
}
|
||
TextField("Enter group name…", text: $profile.displayName)
|
||
.padding(.leading, 36)
|
||
.focused($focusDisplayName)
|
||
.submitLabel(.continue)
|
||
.onSubmit {
|
||
if canCreateProfile() { createGroup() }
|
||
}
|
||
}
|
||
}
|
||
|
||
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."
|
||
)
|
||
}
|
||
|
||
func createGroup() {
|
||
focusDisplayName = false
|
||
do {
|
||
profile.displayName = profile.displayName.trimmingCharacters(in: .whitespaces)
|
||
profile.groupPreferences = GroupPreferences(history: GroupPreference(enable: .on))
|
||
let gInfo = try apiNewGroup(incognito: incognitoDefault, groupProfile: profile)
|
||
Task {
|
||
await m.loadGroupMembers(gInfo)
|
||
}
|
||
let c = Chat(chatInfo: .group(groupInfo: gInfo, groupChatScope: nil), chatItems: [])
|
||
m.addChat(c)
|
||
withAnimation {
|
||
groupInfo = gInfo
|
||
chat = c
|
||
}
|
||
} catch {
|
||
dismissAllSheets(animated: true) {
|
||
AlertManager.shared.showAlert(
|
||
Alert(
|
||
title: Text("Error creating group"),
|
||
message: Text(responseError(error))
|
||
)
|
||
)
|
||
}
|
||
}
|
||
}
|
||
|
||
func canCreateProfile() -> Bool {
|
||
let name = profile.displayName.trimmingCharacters(in: .whitespaces)
|
||
return name != "" && validDisplayName(name)
|
||
}
|
||
}
|
||
|
||
// Using this method may freeze the app in some cases, so it should be avoided when possible, especially when combined with .focussed modifier.
|
||
// It also must only be called from background thread.
|
||
func hideKeyboard() {
|
||
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
||
}
|
||
|
||
struct AddGroupView_Previews: PreviewProvider {
|
||
static var previews: some View {
|
||
AddGroupView()
|
||
}
|
||
}
|