mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-11-11 13:57:42 +00:00
* ios, android, desktop: previews draw fixed sample data instead of calling the core * desktop: database error preview passes the keychain setting * ios: snapshot fixtures use fixed parsed markdown instead of the core * ios: snapshot fixtures: raw string delimiters past the # in markdown JSON * desktop: snapshot test sets the app bar height as the app does * docs: plan to replace preview-only parameters with test measures * ios, desktop: previews run master's code; badges and version views split Preview-only parameters are removed. BadgesCheckOrderView, BadgesHowLongView and VersionView are split into a loader and a layout, which their previews draw. Snapshot tests set a fixed QR picture: the environment value fixedQRCode on iOS, LocalFixedQRCode on desktop. * ios: snapshot fixtures build parsed markdown with initializers The table held the JSON of the Linux core, which the iOS app does not decode. The values are those the core returns for each text. * ci: references of fixed QR codes and version; ios: call overlay preview as on master iOS references from https://github.com/evgeny-simplex/simplex-chat/actions/runs/40944118370, desktop references from https://github.com/evgeny-simplex/simplex-chat/actions/runs/40932566864. ActiveCallOverlay_Previews, its scene and its references are restored as on master. * ios, desktop: snapshot tests render every preview, no exclusion lists BadgesChooseBadgeView, ServersSummaryView and TerminalView are split into a loader and a layout. AddChannelView, AddGroupView, GroupProfileView and UserProfileView layouts take the name and description validators, so desktop previews render without the core library. NewChatView's preview opens the Connect tab, UserProfilesView's preview has its own users. AppearanceSettings, MigrateFromDevice and the duplicate UserProfileLayoutEditOn previews are deleted. * ios, desktop: previews show short links; tests draw the fixed QR code with each app's generator The snapshot tests draw the fixed QR picture for a short address link with generateImage on iOS and qrCodeBitmap on desktop, in place of committed images. Previews of group links, addresses, a pending connection and QR codes pass short links, as the apps show them. The fixture markdown table equals the output of master's parser. * ios, desktop: excluded previews and their views as on master * ci: references of short-link QR codes
258 lines
10 KiB
Swift
258 lines
10 KiB
Swift
//
|
|
// GroupLinkView.swift
|
|
// SimpleX (iOS)
|
|
//
|
|
// Created by JRoberts on 15.10.2022.
|
|
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
|
//
|
|
// Spec: spec/client/chat-view.md
|
|
|
|
import SwiftUI
|
|
import SimpleXChat
|
|
|
|
struct GroupLinkView: View {
|
|
@EnvironmentObject var theme: AppTheme
|
|
var groupId: Int64
|
|
@Binding var groupLink: GroupLink?
|
|
@Binding var groupLinkMemberRole: GroupMemberRole
|
|
var showTitle: Bool = false
|
|
var creatingGroup: Bool = false
|
|
var isChannel: Bool = false
|
|
var groupInfo: GroupInfo? = nil
|
|
var composeState: Binding<ComposeState>? = nil
|
|
var linkCreatedCb: (() -> Void)? = nil
|
|
@State private var showSharePicker = false
|
|
@State private var showShortLink = true
|
|
@State private var creatingLink = false
|
|
@State private var alert: GroupLinkAlert?
|
|
@State private var shouldCreate = true
|
|
|
|
private enum GroupLinkAlert: Identifiable {
|
|
case deleteLink
|
|
case error(title: LocalizedStringKey, error: LocalizedStringKey?)
|
|
|
|
var id: String {
|
|
switch self {
|
|
case .deleteLink: return "deleteLink"
|
|
case let .error(title, _): return "error \(title)"
|
|
}
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
if creatingGroup {
|
|
groupLinkView()
|
|
.navigationBarBackButtonHidden()
|
|
.toolbar {
|
|
ToolbarItem(placement: .navigationBarTrailing) {
|
|
Button ("Continue") { linkCreatedCb?() }
|
|
}
|
|
}
|
|
} else {
|
|
groupLinkView()
|
|
}
|
|
if creatingLink {
|
|
ProgressView()
|
|
.scaleEffect(2)
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func groupLinkView() -> some View {
|
|
List {
|
|
Group {
|
|
if showTitle {
|
|
Text(isChannel ? "Channel link" : "Group link")
|
|
.font(.largeTitle)
|
|
.bold()
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
}
|
|
if isChannel {
|
|
Text("You can share a link or a QR code - anybody will be able to join the channel.")
|
|
} else {
|
|
Text("You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it.")
|
|
}
|
|
}
|
|
.listRowBackground(Color.clear)
|
|
.listRowSeparator(.hidden)
|
|
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
|
|
|
Section {
|
|
if let groupLink = groupLink {
|
|
if !isChannel {
|
|
Picker("Initial role", selection: $groupLinkMemberRole) {
|
|
ForEach([GroupMemberRole.member, GroupMemberRole.observer]) { role in
|
|
Text(role.text(isChannel: isChannel))
|
|
}
|
|
}
|
|
.frame(height: 36)
|
|
}
|
|
SimpleXCreatedLinkQRCode(link: groupLink.connLinkContact, short: $showShortLink)
|
|
.id("simplex-qrcode-view-for-\(groupLink.connLinkContact.simplexChatUri(short: showShortLink))")
|
|
if !isChannel && groupLink.shouldBeUpgraded {
|
|
Button {
|
|
upgradeAndShareLinkAlert()
|
|
} label: {
|
|
Label("Upgrade link", systemImage: "arrow.up")
|
|
}
|
|
}
|
|
Button {
|
|
if !isChannel && groupLink.shouldBeUpgraded {
|
|
upgradeAndShareLinkAlert(groupLink: groupLink)
|
|
} else {
|
|
groupLink.shareAddress(short: showShortLink)
|
|
}
|
|
} label: {
|
|
Label("Share link", systemImage: "square.and.arrow.up")
|
|
}
|
|
if groupInfo?.groupProfile.publicGroup != nil {
|
|
Button { showSharePicker = true } label: {
|
|
Label("Share via chat", systemImage: "arrowshape.turn.up.forward")
|
|
}
|
|
}
|
|
|
|
if !creatingGroup && !isChannel {
|
|
Button(role: .destructive) { alert = .deleteLink } label: {
|
|
Label("Delete link", systemImage: "trash")
|
|
}
|
|
}
|
|
} else {
|
|
Button(action: createGroupLink) {
|
|
Label("Create link", systemImage: "link.badge.plus")
|
|
}
|
|
.disabled(creatingLink)
|
|
}
|
|
} header: {
|
|
if !isChannel, let groupLink, groupLink.connLinkContact.connShortLink != nil {
|
|
ToggleShortLinkHeader(text: Text(""), link: groupLink.connLinkContact, short: $showShortLink)
|
|
}
|
|
}
|
|
.alert(item: $alert) { alert in
|
|
switch alert {
|
|
case .deleteLink:
|
|
return Alert(
|
|
title: Text("Delete link?"),
|
|
message: Text("All group members will remain connected."),
|
|
primaryButton: .destructive(Text("Delete")) {
|
|
Task {
|
|
do {
|
|
try await apiDeleteGroupLink(groupId)
|
|
await MainActor.run { groupLink = nil }
|
|
} catch let error {
|
|
logger.error("GroupLinkView apiDeleteGroupLink: \(responseError(error))")
|
|
}
|
|
}
|
|
}, secondaryButton: .cancel()
|
|
)
|
|
case let .error(title, error):
|
|
return mkAlert(title: title, message: error)
|
|
}
|
|
}
|
|
.onChange(of: groupLinkMemberRole) { _ in
|
|
Task {
|
|
do {
|
|
groupLink = try await apiGroupLinkMemberRole(groupId, memberRole: groupLinkMemberRole)
|
|
} catch let error {
|
|
await MainActor.run {
|
|
showErrorAlert(error, NSLocalizedString("Error updating group link", comment: ""))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.onAppear {
|
|
if groupLink == nil && !creatingLink && shouldCreate {
|
|
createGroupLink()
|
|
}
|
|
shouldCreate = false
|
|
}
|
|
}
|
|
.modifier(ThemedBackground(grouped: true))
|
|
.sheet(isPresented: $showSharePicker) {
|
|
if let gInfo = groupInfo {
|
|
shareChannelPicker(groupInfo: gInfo, composeState: composeState)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func createGroupLink() {
|
|
Task {
|
|
do {
|
|
creatingLink = true
|
|
let gLink = try await apiCreateGroupLink(groupId)
|
|
await MainActor.run {
|
|
creatingLink = false
|
|
groupLink = gLink
|
|
}
|
|
} catch let error {
|
|
logger.error("GroupLinkView apiCreateGroupLink: \(responseError(error))")
|
|
await MainActor.run {
|
|
creatingLink = false
|
|
showErrorAlert(error, NSLocalizedString("Error creating group link", comment: ""))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func upgradeAndShareLinkAlert(groupLink: GroupLink? = nil) {
|
|
showAlert(
|
|
NSLocalizedString("Upgrade group link?", comment: "alert message"),
|
|
message: NSLocalizedString("The link will be short, and group profile will be shared via the link.", comment: "alert message"),
|
|
actions: {
|
|
var actions = [UIAlertAction(title: NSLocalizedString("Upgrade", comment: "alert button"), style: .default) { _ in
|
|
addShortLink(shareOnCompletion: groupLink != nil)
|
|
}]
|
|
if let groupLink {
|
|
actions.append(UIAlertAction(title: NSLocalizedString("Share old link", comment: "alert button"), style: .default) { _ in
|
|
groupLink.shareAddress(short: showShortLink)
|
|
})
|
|
}
|
|
actions.append(cancelAlertAction)
|
|
return actions
|
|
}
|
|
)
|
|
}
|
|
|
|
private func addShortLink(shareOnCompletion: Bool = false) {
|
|
Task {
|
|
do {
|
|
creatingLink = true
|
|
let gLink = try await apiAddGroupShortLink(groupId)
|
|
await MainActor.run {
|
|
creatingLink = false
|
|
groupLink = gLink
|
|
if shareOnCompletion, let gLink {
|
|
gLink.shareAddress(short: showShortLink)
|
|
}
|
|
}
|
|
} catch let error {
|
|
logger.error("apiAddGroupShortLink: \(responseError(error))")
|
|
await MainActor.run {
|
|
creatingLink = false
|
|
showErrorAlert(error, NSLocalizedString("Error adding short link", comment: ""))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct GroupLinkView_Previews: PreviewProvider {
|
|
static var previews: some View {
|
|
@State var groupLink: GroupLink? = GroupLink(
|
|
userContactLinkId: 1,
|
|
connLinkContact: CreatedConnLink(connFullLink: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", connShortLink: "https://smp6.simplex.im/g#cL3iklYlt_Tws1B_1aGylbT_3HowPMQhuCFoBiCjZ1M"),
|
|
shortLinkDataSet: true,
|
|
shortLinkLargeDataSet: true,
|
|
groupLinkId: "abc",
|
|
acceptMemberRole: .member
|
|
)
|
|
@State var noGroupLink: GroupLink? = nil
|
|
|
|
return Group {
|
|
GroupLinkView(groupId: 1, groupLink: $groupLink, groupLinkMemberRole: Binding.constant(.member))
|
|
GroupLinkView(groupId: 1, groupLink: $noGroupLink, groupLinkMemberRole: Binding.constant(.member))
|
|
}
|
|
}
|
|
}
|