ios: group web access ui

This commit is contained in:
Evgeny @ SimpleX Chat committed 2026-05-31 18:09:46 +00:00
1 parent ac8503b75f
commit 3d0f5ff590
3 files changed
+156

No files matched your search

@@ -0,0 +1,132 @@
//
// ChannelWebAccessView.swift
// SimpleX (iOS)
//
// Created by simplex.chat on 31/05/2026.
// Copyright © 2026 SimpleX Chat. All rights reserved.
//
import SwiftUI
import SimpleXChat
struct ChannelWebAccessView: View {
@EnvironmentObject var theme: AppTheme
@Environment(\.dismiss) var dismiss: DismissAction
@Binding var groupInfo: GroupInfo
@State private var webPage: String
@State private var allowEmbedding: Bool
@State private var saving = false
@State private var groupRelays: [GroupRelay] = []
init(groupInfo: Binding<GroupInfo>) {
_groupInfo = groupInfo
let access = groupInfo.wrappedValue.groupProfile.publicGroup?.publicGroupAccess
_webPage = State(initialValue: access?.groupWebPage ?? "")
_allowEmbedding = State(initialValue: access?.allowEmbedding ?? false)
}
var body: some View {
List {
Section {
TextField("Web page URL", text: $webPage)
.keyboardType(.URL)
.autocapitalization(.none)
.disableAutocorrection(true)
Toggle("Allow embedding", isOn: $allowEmbedding)
} header: {
Text("Web access")
} footer: {
Text("Set a web page URL where your channel preview is hosted. Allow embedding to let any website embed the preview.")
}
if let code = embedCode {
Section {
Text(code)
.font(.system(.caption, design: .monospaced))
.textSelection(.enabled)
Button {
UIPasteboard.general.string = code
} label: {
Label("Copy embed code", systemImage: "doc.on.doc")
}
} header: {
Text("Embed code")
}
}
Section {
Button {
saveAccess()
} label: {
HStack {
Text("Save")
if saving { Spacer(); ProgressView() }
}
}
.disabled(!hasChanges || saving)
}
}
.modifier(ThemedBackground(grouped: true))
.onAppear {
Task {
let relays = await apiGetGroupRelays(groupInfo.groupId)
await MainActor.run { groupRelays = relays }
}
}
}
private var hasChanges: Bool {
let access = groupInfo.groupProfile.publicGroup?.publicGroupAccess
let currentWebPage = access?.groupWebPage ?? ""
let currentEmbedding = access?.allowEmbedding ?? false
return webPage != currentWebPage || allowEmbedding != currentEmbedding
}
private var relayUrls: [String] {
groupRelays.compactMap { $0.relayCap.baseWebUrl }
}
private var embedCode: String? {
guard let pg = groupInfo.groupProfile.publicGroup else { return nil }
guard !relayUrls.isEmpty else { return nil }
let publicGroupId = pg.publicGroupId
let groupLink = pg.groupLink
let urls = relayUrls.joined(separator: ",")
return """
<div data-simplex-group-preview
data-relay-urls="\(urls)"
data-public-group-id="\(publicGroupId)"
data-group-link="\(groupLink)"></div>
<script src="https://simplex.chat/js/channel-preview.js"></script>
"""
}
private func saveAccess() {
saving = true
Task {
do {
var gp = groupInfo.groupProfile
if var pg = gp.publicGroup {
let trimmedPage = webPage.trimmingCharacters(in: .whitespacesAndNewlines)
let existingAccess = pg.publicGroupAccess
pg.publicGroupAccess = PublicGroupAccess(
groupWebPage: trimmedPage.isEmpty ? nil : trimmedPage,
groupDomain: existingAccess?.groupDomain,
domainWebPage: existingAccess?.domainWebPage ?? false,
allowEmbedding: allowEmbedding
)
gp.publicGroup = pg
}
let gInfo = try await apiUpdateGroup(groupInfo.groupId, gp)
await MainActor.run {
groupInfo = gInfo
ChatModel.shared.updateGroup(gInfo)
saving = false
}
} catch {
logger.error("ChannelWebAccessView apiUpdateGroup error: \(responseError(error))")
await MainActor.run { saving = false }
}
}
}
}
@@ -112,6 +112,7 @@ struct GroupChatInfoView: View {
// TODO [relays] allow other owners to manage channel link (requires protocol changes to share link ownership)
if groupInfo.isOwner && groupLink != nil {
channelLinkButton()
channelWebAccessButton()
} else if let link = groupInfo.groupProfile.publicGroup?.groupLink {
SimpleXLinkQRCode(uri: link)
Button {
@@ -325,6 +326,15 @@ struct GroupChatInfoView: View {
.lineLimit(4)
.fixedSize(horizontal: false, vertical: true)
}
if let webPage = groupInfo.groupProfile.publicGroup?.publicGroupAccess?.groupWebPage,
let url = URL(string: webPage) {
Link(destination: url) {
Text(webPage)
.font(.subheadline)
.lineLimit(1)
.truncationMode(.tail)
}
}
if groupInfo.useRelays,
let count = groupInfo.groupSummary.publicMemberCount,
count > 0 {
@@ -648,6 +658,16 @@ struct GroupChatInfoView: View {
}
}
private func channelWebAccessButton() -> some View {
NavigationLink {
ChannelWebAccessView(groupInfo: $groupInfo)
.navigationBarTitle("Web access")
.navigationBarTitleDisplayMode(.large)
} label: {
Label("Web page", systemImage: "globe")
}
}
private func groupLinkDestinationView() -> some View {
GroupLinkView(
groupId: groupInfo.groupId,
@@ -169,6 +169,7 @@
648679AB2BC96A74006456E7 /* ChatItemForwardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648679AA2BC96A74006456E7 /* ChatItemForwardingView.swift */; };
6495D7042F48CFC50060512B /* ChannelMembersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6495D7032F48CFC50060512B /* ChannelMembersView.swift */; };
6495D7062F48CFFD0060512B /* ChannelRelaysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6495D7052F48CFFD0060512B /* ChannelRelaysView.swift */; };
E5E418022F83D2CA00252B9E /* ChannelWebAccessView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E418032F83D2CA00252B9E /* ChannelWebAccessView.swift */; };
6495D7082F48D0000060512B /* AddGroupRelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6495D7072F48D0000060512B /* AddGroupRelayView.swift */; };
649BCDA0280460FD00C3A862 /* ComposeImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */; };
649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCDA12805D6EF00C3A862 /* CIImageView.swift */; };
@@ -547,6 +548,7 @@
6493D667280ED77F007A76FB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
6495D7032F48CFC50060512B /* ChannelMembersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelMembersView.swift; sourceTree = "<group>"; };
6495D7052F48CFFD0060512B /* ChannelRelaysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelRelaysView.swift; sourceTree = "<group>"; };
E5E418032F83D2CA00252B9E /* ChannelWebAccessView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelWebAccessView.swift; sourceTree = "<group>"; };
6495D7072F48D0000060512B /* AddGroupRelayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupRelayView.swift; sourceTree = "<group>"; };
649BCD9F280460FD00C3A862 /* ComposeImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeImageView.swift; sourceTree = "<group>"; };
649BCDA12805D6EF00C3A862 /* CIImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIImageView.swift; sourceTree = "<group>"; };
@@ -1175,6 +1177,7 @@
64A779FD2DC3AFF200FDEF2F /* MemberSupportChatToolbar.swift */,
6495D7032F48CFC50060512B /* ChannelMembersView.swift */,
6495D7052F48CFFD0060512B /* ChannelRelaysView.swift */,
E5E418032F83D2CA00252B9E /* ChannelWebAccessView.swift */,
6495D7072F48D0000060512B /* AddGroupRelayView.swift */,
);
path = Group;
@@ -1636,6 +1639,7 @@
8C9BC2652C240D5200875A27 /* ThemeModeEditor.swift in Sources */,
647B15E82F4C8D2500EB431E /* AddChannelView.swift in Sources */,
6495D7062F48CFFD0060512B /* ChannelRelaysView.swift in Sources */,
E5E418022F83D2CA00252B9E /* ChannelWebAccessView.swift in Sources */,
6495D7082F48D0000060512B /* AddGroupRelayView.swift in Sources */,
5CB346E92869E8BA001FD2EF /* PushEnvironment.swift in Sources */,
5C55A91F283AD0E400C4E99E /* CallManager.swift in Sources */,