mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-23 23:46:41 +00:00
use domain without prefix in profiles and in database, rename fields and types
This commit is contained in:
56 files changed
+816
-884
No files matched your search
@@ -156,9 +156,9 @@ enum ChatCommand: ChatCmdProtocol {
|
||||
case apiAddMyAddressShortLink(userId: Int64)
|
||||
case apiSetProfileAddress(userId: Int64, on: Bool)
|
||||
case apiSetAddressSettings(userId: Int64, addressSettings: AddressSettings)
|
||||
case apiSetUserName(userId: Int64, name: String?)
|
||||
case apiVerifyContactName(contactId: Int64)
|
||||
case apiVerifyPublicGroupName(groupId: Int64)
|
||||
case apiSetUserDomain(userId: Int64, simplexDomain: String?)
|
||||
case apiVerifyContactDomain(contactId: Int64)
|
||||
case apiVerifyGroupDomain(groupId: Int64)
|
||||
case apiAcceptContact(incognito: Bool, contactReqId: Int64)
|
||||
case apiRejectContact(contactReqId: Int64)
|
||||
// WebRTC calls
|
||||
@@ -374,10 +374,10 @@ enum ChatCommand: ChatCmdProtocol {
|
||||
case let .apiAddMyAddressShortLink(userId): return "/_short_link_address \(userId)"
|
||||
case let .apiSetProfileAddress(userId, on): return "/_profile_address \(userId) \(onOff(on))"
|
||||
case let .apiSetAddressSettings(userId, addressSettings): return "/_address_settings \(userId) \(encodeJSON(addressSettings))"
|
||||
case let .apiSetUserName(userId, name): return "/_set_name \(userId)" + (name.map { " " + $0 } ?? "")
|
||||
case let .apiSetUserDomain(userId, simplexDomain): return "/_set domain \(userId)" + (simplexDomain.map { " " + $0 } ?? "")
|
||||
case let .apiSetPublicGroupAccess(groupId, access): return "/_public group access #\(groupId) \(encodeJSON(access))"
|
||||
case let .apiVerifyContactName(contactId): return "/_verify name @\(contactId)"
|
||||
case let .apiVerifyPublicGroupName(groupId): return "/_verify name #\(groupId)"
|
||||
case let .apiVerifyContactDomain(contactId): return "/_verify domain @\(contactId)"
|
||||
case let .apiVerifyGroupDomain(groupId): return "/_verify domain #\(groupId)"
|
||||
case let .apiAcceptContact(incognito, contactReqId): return "/_accept incognito=\(onOff(incognito)) \(contactReqId)"
|
||||
case let .apiRejectContact(contactReqId): return "/_reject \(contactReqId)"
|
||||
case let .apiSendCallInvitation(contact, callType): return "/_call invite @\(contact.apiId) \(encodeJSON(callType))"
|
||||
@@ -560,9 +560,9 @@ enum ChatCommand: ChatCmdProtocol {
|
||||
case .apiAddMyAddressShortLink: return "apiAddMyAddressShortLink"
|
||||
case .apiSetProfileAddress: return "apiSetProfileAddress"
|
||||
case .apiSetAddressSettings: return "apiSetAddressSettings"
|
||||
case .apiSetUserName: return "apiSetUserName"
|
||||
case .apiVerifyContactName: return "apiVerifyContactName"
|
||||
case .apiVerifyPublicGroupName: return "apiVerifyPublicGroupName"
|
||||
case .apiSetUserDomain: return "apiSetUserDomain"
|
||||
case .apiVerifyContactDomain: return "apiVerifyContactDomain"
|
||||
case .apiVerifyGroupDomain: return "apiVerifyGroupDomain"
|
||||
case .apiAcceptContact: return "apiAcceptContact"
|
||||
case .apiRejectContact: return "apiRejectContact"
|
||||
case .apiSendCallInvitation: return "apiSendCallInvitation"
|
||||
|
||||
@@ -1083,17 +1083,17 @@ private func apiConnectResponseAlert<R>(_ r: APIResult<R>) -> Alert {
|
||||
title: "Unsupported connection link",
|
||||
message: "This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link."
|
||||
)
|
||||
case let .error(.simplexName(name, err)):
|
||||
case let .error(.simplexDomain(domain, err)):
|
||||
switch err {
|
||||
case .noValidLink:
|
||||
mkAlert(
|
||||
title: "No valid link",
|
||||
message: "The SimpleX name \(name.shortName) is registered, but it has no valid link."
|
||||
message: "The SimpleX name \(domain.fullDomainName) is registered, but it has no valid link."
|
||||
)
|
||||
case .unknownName:
|
||||
case .unknownDomain:
|
||||
mkAlert(
|
||||
title: "Unconfirmed name",
|
||||
message: "The SimpleX name \(name.shortName) is registered, but not added to profile. Please add it to your address or channel profile, if you are the owner."
|
||||
message: "The SimpleX name \(domain.fullDomainName) is registered, but not added to profile. Please add it to your address or channel profile, if you are the owner."
|
||||
)
|
||||
}
|
||||
case .errorAgent(.NO_NAME_SERVERS):
|
||||
@@ -1368,19 +1368,19 @@ func apiSetProfileAddress(on: Bool) async throws -> User? {
|
||||
// name is the encoded SimplexName (e.g. "@alice.simplex"); nil clears it
|
||||
// owner-specific SNENoValidLink wording; everything else reuses the general apiConnectResponseAlert
|
||||
func showSetSimplexNameError<R>(_ r: APIResult<R>, isChannel: Bool) {
|
||||
if case let .error(.simplexName(name, .noValidLink)) = r.unexpected {
|
||||
if case let .error(.simplexDomain(domain, .noValidLink)) = r.unexpected {
|
||||
let format = isChannel
|
||||
? NSLocalizedString("The SimpleX name %@ is registered without channel link. Add channel link to the name via the registration page.", comment: "alert message")
|
||||
: NSLocalizedString("The SimpleX name %@ is registered without SimpleX address. Add your SimpleX address to the name via the registration page.", comment: "alert message")
|
||||
showAlert(NSLocalizedString("Error saving name", comment: "alert title"), message: String.localizedStringWithFormat(format, name.shortName))
|
||||
? NSLocalizedString("The SimpleX name #%@ is registered without channel link. Add channel link to the name via the registration page.", comment: "alert message")
|
||||
: NSLocalizedString("The SimpleX name @%@ is registered without SimpleX address. Add your SimpleX address to the name via the registration page.", comment: "alert message")
|
||||
showAlert(NSLocalizedString("Error saving name", comment: "alert title"), message: String.localizedStringWithFormat(format, domain.fullDomainName))
|
||||
} else {
|
||||
AlertManager.shared.showAlert(apiConnectResponseAlert(r))
|
||||
}
|
||||
}
|
||||
|
||||
func apiSetUserName(_ name: String?) async throws -> User {
|
||||
let userId = try currentUserId("apiSetUserName")
|
||||
let r: APIResult<ChatResponse1> = await chatApiSendCmd(.apiSetUserName(userId: userId, name: name))
|
||||
func apiSetUserDomain(_ simplexDomain: String?) async throws -> User {
|
||||
let userId = try currentUserId("apiSetUserDomain")
|
||||
let r: APIResult<ChatResponse1> = await chatApiSendCmd(.apiSetUserDomain(userId: userId, simplexDomain: simplexDomain))
|
||||
switch r {
|
||||
case let .result(.userProfileUpdated(user, _, _, _)): return user
|
||||
case let .result(.userProfileNoChange(user)): return user
|
||||
@@ -1390,14 +1390,14 @@ func apiSetUserName(_ name: String?) async throws -> User {
|
||||
}
|
||||
}
|
||||
|
||||
func apiVerifyContactName(_ contactId: Int64) async throws -> (Contact, String?) {
|
||||
let r: ChatResponse2 = try await chatSendCmd(.apiVerifyContactName(contactId: contactId))
|
||||
func apiVerifyContactDomain(_ contactId: Int64) async throws -> (Contact, String?) {
|
||||
let r: ChatResponse2 = try await chatSendCmd(.apiVerifyContactDomain(contactId: contactId))
|
||||
if case let .contactNameVerified(_, contact, verificationFailure) = r { return (contact, verificationFailure) }
|
||||
throw r.unexpected
|
||||
}
|
||||
|
||||
func apiVerifyPublicGroupName(_ groupId: Int64) async throws -> (GroupInfo, String?) {
|
||||
let r: ChatResponse2 = try await chatSendCmd(.apiVerifyPublicGroupName(groupId: groupId))
|
||||
func apiVerifyGroupDomain(_ groupId: Int64) async throws -> (GroupInfo, String?) {
|
||||
let r: ChatResponse2 = try await chatSendCmd(.apiVerifyGroupDomain(groupId: groupId))
|
||||
if case let .groupNameVerified(_, groupInfo, verificationFailure) = r { return (groupInfo, verificationFailure) }
|
||||
throw r.unexpected
|
||||
}
|
||||
|
||||
@@ -392,21 +392,21 @@ struct ChatInfoView: View {
|
||||
.lineLimit(3)
|
||||
.padding(.bottom, 2)
|
||||
}
|
||||
if let claim = contact.profile.simplexName, contact.profile.simplexNameVerification != nil || claim.proof != nil {
|
||||
if let domain = contact.profile.contactDomain,
|
||||
contact.profile.contactDomainVerified != nil || domain.proof != nil {
|
||||
SimplexNameView(
|
||||
name: claim.shortName,
|
||||
verification: contact.profile.simplexNameVerification,
|
||||
autoVerify: UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_VERIFY_SIMPLEX_NAMES),
|
||||
simplexName: "@\(domain.shortName)",
|
||||
verified: contact.profile.contactDomainVerified,
|
||||
verify: {
|
||||
do {
|
||||
let (ct, reason) = try await apiVerifyContactName(contact.contactId)
|
||||
let (ct, reason) = try await apiVerifyContactDomain(contact.contactId)
|
||||
await MainActor.run {
|
||||
chatModel.updateContact(ct)
|
||||
contact = ct
|
||||
}
|
||||
return (ct.profile.simplexNameVerification, reason)
|
||||
return (ct.profile.contactDomainVerified, reason)
|
||||
} catch {
|
||||
logger.error("apiVerifyContactName: \(responseError(error))")
|
||||
logger.error("apiVerifyContactDomain: \(responseError(error))")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1382,31 +1382,31 @@ private func deleteNotReadyContact(
|
||||
}
|
||||
|
||||
struct SimplexNameView: View {
|
||||
let name: String
|
||||
let verification: Bool?
|
||||
let autoVerify: Bool
|
||||
let verify: () async -> (Bool?, String?)?
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@AppStorage(DEFAULT_PRIVACY_VERIFY_SIMPLEX_NAMES) var autoVerify = false
|
||||
let simplexName: String
|
||||
let verified: Bool?
|
||||
let verify: () async -> (Bool?, String?)?
|
||||
@State private var inFlight = false
|
||||
@State private var showSpinner = false
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 6) {
|
||||
Text(name)
|
||||
.font(verification == true ? .subheadline : .system(.subheadline, design: .monospaced))
|
||||
.foregroundColor(verification == true ? theme.colors.primary : theme.colors.secondary)
|
||||
Text(simplexName)
|
||||
.font(verified == true ? .subheadline : .system(.subheadline, design: .monospaced))
|
||||
.foregroundColor(verified == true ? theme.colors.primary : theme.colors.secondary)
|
||||
indicator()
|
||||
}
|
||||
.padding(.bottom, 2)
|
||||
.onAppear { if autoVerify && verification == nil { runVerify(manual: false) } }
|
||||
.onAppear { if autoVerify && verified == nil { runVerify(manual: false) } }
|
||||
}
|
||||
|
||||
@ViewBuilder private func indicator() -> some View {
|
||||
if showSpinner {
|
||||
ProgressView()
|
||||
} else if verification == true {
|
||||
} else if verified == true {
|
||||
Image(systemName: "checkmark")
|
||||
} else if verification == false {
|
||||
} else if verified == false {
|
||||
Image(systemName: "xmark")
|
||||
.foregroundColor(.red)
|
||||
.onTapGesture { runVerify(manual: true) }
|
||||
|
||||
@@ -148,7 +148,7 @@ struct ChannelWebAccessView: View {
|
||||
let existingAccess = pg.publicGroupAccess
|
||||
pg.publicGroupAccess = PublicGroupAccess(
|
||||
groupWebPage: trimmedPage.isEmpty ? nil : trimmedPage,
|
||||
simplexName: existingAccess?.simplexName,
|
||||
groupDomainClaim: existingAccess?.groupDomainClaim,
|
||||
domainWebPage: existingAccess?.domainWebPage ?? false,
|
||||
allowEmbedding: allowEmbedding
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
channelSimplexNameButton()
|
||||
} else if let link = groupInfo.groupProfile.publicGroup?.groupLink {
|
||||
SimpleXLinkQRCode(uri: link)
|
||||
Button {
|
||||
@@ -247,32 +248,6 @@ struct GroupChatInfoView: View {
|
||||
if groupInfo.useRelays && groupInfo.isOwner {
|
||||
Section(header: Text("Advanced options").foregroundColor(theme.colors.secondary)) {
|
||||
channelWebAccessButton()
|
||||
if groupInfo.groupProfile.publicGroup != nil {
|
||||
NavigationLink {
|
||||
SetSimplexNameView(
|
||||
titleKey: "SimpleX name",
|
||||
footer: "Let people join via name registered with this channel link.",
|
||||
prefix: "#",
|
||||
nameText: groupInfo.groupProfile.publicGroup?.publicGroupAccess?.simplexName?.shortName ?? "",
|
||||
save: { name in
|
||||
do {
|
||||
var access = groupInfo.groupProfile.publicGroup?.publicGroupAccess ?? PublicGroupAccess()
|
||||
access.simplexName = name.map { SimplexNameClaim(name: $0) }
|
||||
let gInfo = try await apiSetPublicGroupAccess(groupInfo.groupId, access: access)
|
||||
await MainActor.run {
|
||||
chatModel.updateGroup(gInfo)
|
||||
groupInfo = gInfo
|
||||
}
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
)
|
||||
} label: {
|
||||
Label("SimpleX name", systemImage: "number")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,22 +333,21 @@ struct GroupChatInfoView: View {
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
if let access = groupInfo.groupProfile.publicGroup?.publicGroupAccess,
|
||||
let groupName = access.simplexName?.shortName,
|
||||
groupInfo.simplexNameVerification != nil || access.simplexName?.proof != nil {
|
||||
let domain = access.groupDomainClaim?.shortName,
|
||||
groupInfo.groupDomainVerified != nil || access.groupDomainClaim?.proof != nil {
|
||||
SimplexNameView(
|
||||
name: groupName,
|
||||
verification: groupInfo.simplexNameVerification,
|
||||
autoVerify: UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_VERIFY_SIMPLEX_NAMES),
|
||||
simplexName: "#\(domain)",
|
||||
verified: groupInfo.groupDomainVerified,
|
||||
verify: {
|
||||
do {
|
||||
let (gInfo, reason) = try await apiVerifyPublicGroupName(groupInfo.groupId)
|
||||
let (gInfo, reason) = try await apiVerifyGroupDomain(groupInfo.groupId)
|
||||
await MainActor.run {
|
||||
chatModel.updateGroup(gInfo)
|
||||
groupInfo = gInfo
|
||||
}
|
||||
return (gInfo.simplexNameVerification, reason)
|
||||
return (gInfo.groupDomainVerified, reason)
|
||||
} catch {
|
||||
logger.error("apiVerifyPublicGroupName: \(responseError(error))")
|
||||
logger.error("apiVerifyGroupDomain: \(responseError(error))")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -722,6 +696,34 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func channelSimplexNameButton() -> some View {
|
||||
NavigationLink {
|
||||
let domain = if let d = groupInfo.groupProfile.publicGroup?.publicGroupAccess?.groupDomainClaim?.shortName { "#\(d)" } else { "" }
|
||||
SetSimplexDomainView(
|
||||
title: "SimpleX name",
|
||||
footer: "Let people join via name registered with this channel link.",
|
||||
prompt: "#channelname.testing",
|
||||
simplexName: domain,
|
||||
save: { domain in
|
||||
do {
|
||||
var access = groupInfo.groupProfile.publicGroup?.publicGroupAccess ?? PublicGroupAccess()
|
||||
access.groupDomainClaim = domain.map { SimplexDomainClaim(domain: $0) }
|
||||
let gInfo = try await apiSetPublicGroupAccess(groupInfo.groupId, access: access)
|
||||
await MainActor.run {
|
||||
chatModel.updateGroup(gInfo)
|
||||
groupInfo = gInfo
|
||||
}
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
)
|
||||
} label: {
|
||||
Label("SimpleX name", systemImage: "number")
|
||||
}
|
||||
}
|
||||
|
||||
private func groupLinkDestinationView() -> some View {
|
||||
GroupLinkView(
|
||||
groupId: groupInfo.groupId,
|
||||
|
||||
@@ -16,7 +16,7 @@ struct PrivacySettings: View {
|
||||
@AppStorage(GROUP_DEFAULT_PRIVACY_LINK_PREVIEWS, store: groupDefaults) private var useLinkPreviews = true
|
||||
@AppStorage(GROUP_DEFAULT_PRIVACY_SANITIZE_LINKS, store: groupDefaults) private var privacySanitizeLinks = false
|
||||
@AppStorage(DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS) private var showChatPreviews = true
|
||||
@AppStorage(DEFAULT_PRIVACY_VERIFY_SIMPLEX_NAMES) private var verifySimplexNames = true
|
||||
@AppStorage(DEFAULT_PRIVACY_VERIFY_SIMPLEX_NAMES) private var verifySimplexNames = false
|
||||
@AppStorage(DEFAULT_PRIVACY_SAVE_LAST_DRAFT) private var saveLastDraft = true
|
||||
@AppStorage(GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES, store: groupDefaults) private var encryptLocalFiles = true
|
||||
@AppStorage(GROUP_DEFAULT_PRIVACY_ASK_TO_APPROVE_RELAYS, store: groupDefaults) private var askToApproveRelays = true
|
||||
|
||||
@@ -100,7 +100,7 @@ let appDefaults: [String: Any] = [
|
||||
DEFAULT_PRIVACY_LINK_PREVIEWS: true,
|
||||
DEFAULT_PRIVACY_SIMPLEX_LINK_MODE: SimpleXLinkMode.description.rawValue,
|
||||
DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS: true,
|
||||
DEFAULT_PRIVACY_VERIFY_SIMPLEX_NAMES: true,
|
||||
DEFAULT_PRIVACY_VERIFY_SIMPLEX_NAMES: false,
|
||||
DEFAULT_PRIVACY_SAVE_LAST_DRAFT: true,
|
||||
DEFAULT_PRIVACY_PROTECT_SCREEN: false,
|
||||
DEFAULT_PRIVACY_DELIVERY_RECEIPTS_SET: false,
|
||||
|
||||
@@ -193,14 +193,15 @@ struct UserAddressView: View {
|
||||
|
||||
Section {
|
||||
NavigationLink {
|
||||
SetSimplexNameView(
|
||||
titleKey: "Your SimpleX name",
|
||||
let simplexName = if let d = chatModel.currentUser?.profile.contactDomain?.shortName { "@\(d)" } else { "" }
|
||||
SetSimplexDomainView(
|
||||
title: "Your SimpleX name",
|
||||
footer: "Let people connect to you via name registered with your SimpleX address.",
|
||||
prefix: "@",
|
||||
nameText: chatModel.currentUser?.profile.simplexName?.shortName ?? "",
|
||||
save: { name in
|
||||
prompt: "@yourname.testing",
|
||||
simplexName: simplexName,
|
||||
save: { simplexDomain in
|
||||
do {
|
||||
let u = try await apiSetUserName(name)
|
||||
let u = try await apiSetUserDomain(simplexDomain)
|
||||
await MainActor.run { chatModel.updateUser(u) }
|
||||
return true
|
||||
} catch {
|
||||
@@ -710,14 +711,11 @@ private func saveAddressSettings(_ settings: AddressSettingsState, _ savedSettin
|
||||
}
|
||||
}
|
||||
|
||||
// Set the user's own (prefix "@") or a channel's (prefix "#") SimpleX name.
|
||||
// The field is prefilled with the full prefixed name; `save` receives the encoded name (or nil to
|
||||
// clear) and returns a failure message (nil on success). The prefix is normalised on save.
|
||||
struct SetSimplexNameView: View {
|
||||
let titleKey: LocalizedStringKey
|
||||
struct SetSimplexDomainView: View {
|
||||
let title: LocalizedStringKey
|
||||
let footer: LocalizedStringKey
|
||||
let prefix: String
|
||||
@State var nameText: String
|
||||
let prompt: String
|
||||
@State var simplexName: String
|
||||
let save: (String?) async -> Bool
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@@ -726,7 +724,7 @@ struct SetSimplexNameView: View {
|
||||
var body: some View {
|
||||
List {
|
||||
Section {
|
||||
TextField(prefix + "name.simplex", text: $nameText)
|
||||
TextField(prompt, text: $simplexName)
|
||||
.autocorrectionDisabled(true)
|
||||
.textInputAutocapitalization(.never)
|
||||
} header: {
|
||||
@@ -750,16 +748,19 @@ struct SetSimplexNameView: View {
|
||||
.disabled(saving)
|
||||
}
|
||||
}
|
||||
.navigationTitle(titleKey)
|
||||
.navigationTitle(title)
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
}
|
||||
|
||||
// ensure the correct type prefix so a contact name is not parsed as a group (or vice versa)
|
||||
private func normalized() -> String? {
|
||||
let s = nameText.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if s.isEmpty { return nil }
|
||||
if s.hasPrefix("@") || s.hasPrefix("#") { return prefix + s.dropFirst() }
|
||||
return prefix + s
|
||||
let s = simplexName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return s.isEmpty
|
||||
? nil
|
||||
: addSimplexTLD(s.hasPrefix("@") || s.hasPrefix("#") ? String(s.dropFirst()) : s)
|
||||
}
|
||||
|
||||
private func addSimplexTLD(_ d: String) -> String {
|
||||
if d.contains(".") { d } else { "\(d).simplex" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -183,8 +183,8 @@
|
||||
64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; };
|
||||
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; };
|
||||
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; };
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-7.0.0.6-IDb07VxlHBtGmeucUQceZv-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-7.0.0.6-IDb07VxlHBtGmeucUQceZv-ghc9.6.3.a */; };
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-7.0.0.6-IDb07VxlHBtGmeucUQceZv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-7.0.0.6-IDb07VxlHBtGmeucUQceZv.a */; };
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-7.0.0.6-67j0xDFDAaz6bMQcxTdG4Z-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-7.0.0.6-67j0xDFDAaz6bMQcxTdG4Z-ghc9.6.3.a */; };
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-7.0.0.6-67j0xDFDAaz6bMQcxTdG4Z.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-7.0.0.6-67j0xDFDAaz6bMQcxTdG4Z.a */; };
|
||||
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; };
|
||||
64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; };
|
||||
64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; };
|
||||
@@ -563,8 +563,8 @@
|
||||
64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = "<group>"; };
|
||||
64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-7.0.0.6-IDb07VxlHBtGmeucUQceZv-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-7.0.0.6-IDb07VxlHBtGmeucUQceZv-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-7.0.0.6-IDb07VxlHBtGmeucUQceZv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-7.0.0.6-IDb07VxlHBtGmeucUQceZv.a"; sourceTree = "<group>"; };
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-7.0.0.6-67j0xDFDAaz6bMQcxTdG4Z-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-7.0.0.6-67j0xDFDAaz6bMQcxTdG4Z-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-7.0.0.6-67j0xDFDAaz6bMQcxTdG4Z.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-7.0.0.6-67j0xDFDAaz6bMQcxTdG4Z.a"; sourceTree = "<group>"; };
|
||||
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = "<group>"; };
|
||||
64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = "<group>"; };
|
||||
@@ -735,8 +735,8 @@
|
||||
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */,
|
||||
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */,
|
||||
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */,
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-7.0.0.6-IDb07VxlHBtGmeucUQceZv-ghc9.6.3.a in Frameworks */,
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-7.0.0.6-IDb07VxlHBtGmeucUQceZv.a in Frameworks */,
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-7.0.0.6-67j0xDFDAaz6bMQcxTdG4Z-ghc9.6.3.a in Frameworks */,
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-7.0.0.6-67j0xDFDAaz6bMQcxTdG4Z.a in Frameworks */,
|
||||
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -822,8 +822,8 @@
|
||||
64C829992D54AEEE006B9E89 /* libffi.a */,
|
||||
64C829982D54AEED006B9E89 /* libgmp.a */,
|
||||
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */,
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-7.0.0.6-IDb07VxlHBtGmeucUQceZv-ghc9.6.3.a */,
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-7.0.0.6-IDb07VxlHBtGmeucUQceZv.a */,
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-7.0.0.6-67j0xDFDAaz6bMQcxTdG4Z-ghc9.6.3.a */,
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-7.0.0.6-67j0xDFDAaz6bMQcxTdG4Z.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
|
||||
@@ -758,7 +758,7 @@ public enum ChatErrorType: Decodable, Hashable {
|
||||
case chatNotStopped
|
||||
case chatStoreChanged
|
||||
case invalidConnReq
|
||||
case simplexName(simplexName: SimplexNameInfo, simplexNameError: SimplexNameError)
|
||||
case simplexDomain(simplexDomain: SimplexDomain, simplexDomainError: SimplexDomainError)
|
||||
case unsupportedConnReq
|
||||
case invalidChatMessage(connection: Connection, message: String)
|
||||
case connReqMessageProhibited
|
||||
|
||||
@@ -119,7 +119,8 @@ public struct Profile: Codable, NamedChat, Hashable {
|
||||
image: String? = nil,
|
||||
contactLink: String? = nil,
|
||||
preferences: Preferences? = nil,
|
||||
peerType: ChatPeerType? = nil
|
||||
peerType: ChatPeerType? = nil,
|
||||
contactDomain: SimplexDomainClaim? = nil
|
||||
) {
|
||||
self.displayName = displayName
|
||||
self.fullName = fullName
|
||||
@@ -127,6 +128,7 @@ public struct Profile: Codable, NamedChat, Hashable {
|
||||
self.image = image
|
||||
self.contactLink = contactLink
|
||||
self.preferences = preferences
|
||||
self.contactDomain = contactDomain
|
||||
}
|
||||
|
||||
public var displayName: String
|
||||
@@ -139,6 +141,7 @@ public struct Profile: Codable, NamedChat, Hashable {
|
||||
// the badge proof from the wire profile - opaque to the UI, round-tripped to the core (apiPrepareContact)
|
||||
public var badge: BadgeProof?
|
||||
public var localAlias: String { get { "" } }
|
||||
public var contactDomain: SimplexDomainClaim?
|
||||
|
||||
var profileViewName: String {
|
||||
(fullName == "" || displayName == fullName) ? displayName : "\(displayName) (\(fullName))"
|
||||
@@ -162,8 +165,8 @@ public struct LocalProfile: Codable, NamedChat, Hashable {
|
||||
peerType: ChatPeerType? = nil,
|
||||
localBadge: LocalBadge? = nil,
|
||||
localAlias: String,
|
||||
simplexName: SimplexNameClaim? = nil,
|
||||
simplexNameVerification: Bool? = nil
|
||||
contactDomain: SimplexDomainClaim? = nil,
|
||||
contactDomainVerified: Bool? = nil
|
||||
) {
|
||||
self.profileId = profileId
|
||||
self.displayName = displayName
|
||||
@@ -175,8 +178,8 @@ public struct LocalProfile: Codable, NamedChat, Hashable {
|
||||
self.peerType = peerType
|
||||
self.localBadge = localBadge
|
||||
self.localAlias = localAlias
|
||||
self.simplexName = simplexName
|
||||
self.simplexNameVerification = simplexNameVerification
|
||||
self.contactDomain = contactDomain
|
||||
self.contactDomainVerified = contactDomainVerified
|
||||
}
|
||||
|
||||
public var profileId: Int64
|
||||
@@ -189,8 +192,8 @@ public struct LocalProfile: Codable, NamedChat, Hashable {
|
||||
public var peerType: ChatPeerType?
|
||||
public var localBadge: LocalBadge?
|
||||
public var localAlias: String
|
||||
public var simplexName: SimplexNameClaim?
|
||||
public var simplexNameVerification: Bool?
|
||||
public var contactDomain: SimplexDomainClaim?
|
||||
public var contactDomainVerified: Bool?
|
||||
|
||||
var profileViewName: String {
|
||||
localAlias == ""
|
||||
@@ -2540,7 +2543,7 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable {
|
||||
public var chatTags: [Int64]
|
||||
public var chatItemTTL: Int64?
|
||||
public var localAlias: String
|
||||
public var simplexNameVerification: Bool?
|
||||
public var groupDomainVerified: Bool?
|
||||
|
||||
public var isOwner: Bool {
|
||||
return membership.memberRole == .owner && membership.memberCurrent
|
||||
@@ -2621,35 +2624,35 @@ public enum GroupType: Codable, Hashable {
|
||||
}
|
||||
|
||||
public struct PublicGroupAccess: Codable, Hashable {
|
||||
public init(groupWebPage: String? = nil, simplexName: SimplexNameClaim? = nil, domainWebPage: Bool = false, allowEmbedding: Bool = false) {
|
||||
public init(groupWebPage: String? = nil, groupDomainClaim: SimplexDomainClaim? = nil, domainWebPage: Bool = false, allowEmbedding: Bool = false) {
|
||||
self.groupWebPage = groupWebPage
|
||||
self.simplexName = simplexName
|
||||
self.groupDomainClaim = groupDomainClaim
|
||||
self.domainWebPage = domainWebPage
|
||||
self.allowEmbedding = allowEmbedding
|
||||
}
|
||||
|
||||
public var groupWebPage: String?
|
||||
public var simplexName: SimplexNameClaim?
|
||||
public var groupDomainClaim: SimplexDomainClaim?
|
||||
public var domainWebPage: Bool = false
|
||||
public var allowEmbedding: Bool = false
|
||||
}
|
||||
|
||||
public struct SimplexNameClaim: Codable, Hashable {
|
||||
public init(name: String, proof: NameClaimProof? = nil) {
|
||||
self.name = name
|
||||
public struct SimplexDomainClaim: Codable, Hashable {
|
||||
public init(domain: String, proof: SimplexDomainProof? = nil) {
|
||||
self.domain = domain
|
||||
self.proof = proof
|
||||
}
|
||||
public var name: String
|
||||
public var proof: NameClaimProof?
|
||||
public var domain: String
|
||||
public var proof: SimplexDomainProof?
|
||||
|
||||
public var shortName: String {
|
||||
name.hasPrefix("simplex:/name") ? String(name.dropFirst("simplex:/name".count)) : name
|
||||
domain.hasSuffix(".simplex") ? String(domain.dropLast(".simplex".count)) : domain
|
||||
}
|
||||
}
|
||||
|
||||
public enum SimplexNameError: Decodable, Hashable {
|
||||
public enum SimplexDomainError: Decodable, Hashable {
|
||||
case noValidLink
|
||||
case unknownName
|
||||
case unknownDomain
|
||||
}
|
||||
|
||||
public struct RelayCapabilities: Codable, Hashable {
|
||||
@@ -5285,28 +5288,15 @@ public enum SimplexLinkType: String, Decodable, Hashable {
|
||||
|
||||
public struct SimplexNameInfo: Codable, Equatable, Hashable {
|
||||
public var nameType: SimplexNameType
|
||||
public var nameDomain: SimplexNameDomain
|
||||
public var nameDomain: SimplexDomain
|
||||
|
||||
// prefix-less domain for prefilling the set-name field (mirrors shortName without the @/# prefix)
|
||||
public var editDomain: String {
|
||||
if nameType == .publicGroup, nameDomain.nameTLD == .simplex, nameDomain.subDomain.isEmpty {
|
||||
return nameDomain.domain
|
||||
}
|
||||
return nameDomain.fullDomainName
|
||||
}
|
||||
|
||||
// user-facing display string, mirrors backend shortNameInfoStr
|
||||
public var shortName: String {
|
||||
(nameType == .publicGroup ? "#" : "@") + editDomain
|
||||
}
|
||||
|
||||
public init(nameType: SimplexNameType, nameDomain: SimplexNameDomain) {
|
||||
public init(nameType: SimplexNameType, nameDomain: SimplexDomain) {
|
||||
self.nameType = nameType
|
||||
self.nameDomain = nameDomain
|
||||
}
|
||||
}
|
||||
|
||||
public struct SimplexNameDomain: Codable, Equatable, Hashable {
|
||||
public struct SimplexDomain: Codable, Equatable, Hashable {
|
||||
public var nameTLD: SimplexTLD
|
||||
public var domain: String
|
||||
public var subDomain: [String]
|
||||
@@ -5340,11 +5330,10 @@ public enum SimplexNameType: String, Codable, Hashable {
|
||||
case contact
|
||||
}
|
||||
|
||||
// peer's signed name claim; UI only checks presence
|
||||
public struct NameClaimProof: Codable, Hashable {
|
||||
public struct SimplexDomainProof: Codable, Hashable {
|
||||
public var linkOwnerId: String?
|
||||
public var presHeader: String
|
||||
public var signature: String
|
||||
public var linkOwnerId: String?
|
||||
}
|
||||
|
||||
public enum FormatColor: String, Decodable, Hashable {
|
||||
|
||||
+16
-25
@@ -2039,14 +2039,15 @@ data class Profile(
|
||||
val peerType: ChatPeerType? = null,
|
||||
// the badge proof from the wire profile: not interpreted by the UI (display uses crypto-free LocalBadge),
|
||||
// but preserved so passing a link profile back to the core (apiPrepareContact) keeps the proof
|
||||
val badge: BadgeProof? = null
|
||||
val badge: BadgeProof? = null,
|
||||
val contactDomain: SimplexDomainClaim? = null
|
||||
): NamedChat {
|
||||
val profileViewName: String
|
||||
get() {
|
||||
return if (fullName == "" || displayName == fullName) displayName else "$displayName ($fullName)"
|
||||
}
|
||||
|
||||
fun toLocalProfile(profileId: Long): LocalProfile = LocalProfile(profileId, displayName, fullName, shortDescr, image, localAlias, contactLink, preferences, peerType)
|
||||
fun toLocalProfile(profileId: Long): LocalProfile = LocalProfile(profileId, displayName, fullName, shortDescr, image, localAlias, contactLink, preferences, peerType, contactDomain = contactDomain)
|
||||
|
||||
companion object {
|
||||
val sampleData = Profile(
|
||||
@@ -2069,12 +2070,12 @@ data class LocalProfile(
|
||||
val preferences: ChatPreferences? = null,
|
||||
val peerType: ChatPeerType? = null,
|
||||
val localBadge: LocalBadge? = null,
|
||||
val simplexName: SimplexNameClaim? = null,
|
||||
val simplexNameVerification: Boolean? = null
|
||||
val contactDomain: SimplexDomainClaim? = null,
|
||||
val contactDomainVerified: Boolean? = null
|
||||
): NamedChat {
|
||||
val profileViewName: String = localAlias.ifEmpty { if (fullName == "" || displayName == fullName) displayName else "$displayName ($fullName)" }
|
||||
|
||||
fun toProfile(): Profile = Profile(displayName, fullName, shortDescr, image, localAlias, contactLink, preferences, peerType)
|
||||
fun toProfile(): Profile = Profile(displayName, fullName, shortDescr, image, localAlias, contactLink, preferences, peerType, contactDomain = contactDomain)
|
||||
|
||||
companion object {
|
||||
val sampleData = LocalProfile(
|
||||
@@ -2200,7 +2201,7 @@ data class GroupInfo (
|
||||
val chatTags: List<Long>,
|
||||
val chatItemTTL: Long?,
|
||||
override val localAlias: String,
|
||||
val simplexNameVerification: Boolean? = null,
|
||||
val groupDomainVerified: Boolean? = null,
|
||||
): SomeChat, NamedChat {
|
||||
override val chatType get() = ChatType.Group
|
||||
override val id get() = "#$groupId"
|
||||
@@ -2323,17 +2324,17 @@ object GroupTypeSerializer : KSerializer<GroupType> {
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class SimplexNameClaim(
|
||||
val name: String,
|
||||
val proof: NameClaimProof? = null
|
||||
data class SimplexDomainClaim(
|
||||
val domain: String,
|
||||
val proof: SimplexDomainProof? = null
|
||||
) {
|
||||
val shortName: String get() = name.removePrefix("simplex:/name")
|
||||
val shortName: String get() = domain.removeSuffix(".simplex")
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class PublicGroupAccess(
|
||||
val groupWebPage: String? = null,
|
||||
val simplexName: SimplexNameClaim? = null,
|
||||
val groupDomainClaim: SimplexDomainClaim? = null,
|
||||
val domainWebPage: Boolean = false,
|
||||
val allowEmbedding: Boolean = false
|
||||
)
|
||||
@@ -4885,21 +4886,11 @@ enum class SimplexLinkType(val linkType: String) {
|
||||
@Serializable
|
||||
data class SimplexNameInfo(
|
||||
val nameType: SimplexNameType,
|
||||
val nameDomain: SimplexNameDomain
|
||||
) {
|
||||
// prefix-less domain for prefilling the set-name field (shortName without the @/# prefix)
|
||||
val editDomain: String
|
||||
get() = if (nameType == SimplexNameType.publicGroup && nameDomain.nameTLD == SimplexTLD.simplex && nameDomain.subDomain.isEmpty())
|
||||
nameDomain.domain
|
||||
else nameDomain.fullDomainName
|
||||
|
||||
// user-facing display string, mirrors backend shortNameInfoStr
|
||||
val shortName: String
|
||||
get() = (if (nameType == SimplexNameType.publicGroup) "#" else "@") + editDomain
|
||||
}
|
||||
val nameDomain: SimplexDomain
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SimplexNameDomain(
|
||||
data class SimplexDomain(
|
||||
val nameTLD: SimplexTLD,
|
||||
val domain: String,
|
||||
val subDomain: List<String>
|
||||
@@ -4931,7 +4922,7 @@ enum class SimplexNameType {
|
||||
|
||||
// peer's signed name claim; UI only checks presence
|
||||
@Serializable
|
||||
data class NameClaimProof(
|
||||
data class SimplexDomainProof(
|
||||
val presHeader: String,
|
||||
val signature: String,
|
||||
val linkOwnerId: String? = null
|
||||
|
||||
+35
-35
@@ -122,7 +122,7 @@ class AppPreferences {
|
||||
val privacyProtectScreen = mkBoolPreference(SHARED_PREFS_PRIVACY_PROTECT_SCREEN, true)
|
||||
val privacyAcceptImages = mkBoolPreference(SHARED_PREFS_PRIVACY_ACCEPT_IMAGES, true)
|
||||
val privacyLinkPreviews = mkBoolPreference(SHARED_PREFS_PRIVACY_LINK_PREVIEWS, true)
|
||||
val privacyVerifySimplexNames = mkBoolPreference(SHARED_PREFS_PRIVACY_VERIFY_SIMPLEX_NAMES, true)
|
||||
val privacyVerifySimplexNames = mkBoolPreference(SHARED_PREFS_PRIVACY_VERIFY_SIMPLEX_NAMES, false)
|
||||
val privacyLinkPreviewsShowAlert = mkBoolPreference(SHARED_PREFS_PRIVACY_LINK_PREVIEWS_SHOW_ALERT, true)
|
||||
val privacySanitizeLinks = mkBoolPreference(SHARED_PREFS_PRIVACY_SANITIZE_LINKS, false)
|
||||
// TODO remove
|
||||
@@ -1558,17 +1558,17 @@ object ChatController {
|
||||
)
|
||||
}
|
||||
r is API.Error && r.err is ChatError.ChatErrorChat
|
||||
&& r.err.errorType is ChatErrorType.SimplexName -> {
|
||||
val name = r.err.errorType.simplexName.shortName
|
||||
if (r.err.errorType.simplexNameError is SimplexNameError.NoValidLink) {
|
||||
&& r.err.errorType is ChatErrorType.CESimplexDomain -> {
|
||||
val domain = r.err.errorType.simplexDomain.fullDomainName
|
||||
if (r.err.errorType.simplexDomainError is SimplexDomainError.NoValidLink) {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.simplex_name_no_valid_link),
|
||||
generalGetString(MR.strings.simplex_name_no_valid_link_desc).format(name)
|
||||
generalGetString(MR.strings.simplex_name_no_valid_link_desc).format(domain)
|
||||
)
|
||||
} else {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.simplex_name_unconfirmed),
|
||||
generalGetString(MR.strings.simplex_name_unconfirmed_desc).format(name)
|
||||
generalGetString(MR.strings.simplex_name_unconfirmed_desc).format(domain)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1631,10 +1631,10 @@ object ChatController {
|
||||
|
||||
// owner-specific wording for setting one's own/channel name; null for other errors (handled by apiConnectResponseAlert)
|
||||
fun simplexNameOwnerError(err: ChatError, isChannel: Boolean): String? =
|
||||
if (err is ChatError.ChatErrorChat && err.errorType is ChatErrorType.SimplexName && err.errorType.simplexNameError is SimplexNameError.NoValidLink) {
|
||||
val name = err.errorType.simplexName.shortName
|
||||
if (isChannel) generalGetString(MR.strings.simplex_name_owner_no_channel_link).format(name)
|
||||
else generalGetString(MR.strings.simplex_name_owner_no_address).format(name)
|
||||
if (err is ChatError.ChatErrorChat && err.errorType is ChatErrorType.CESimplexDomain && err.errorType.simplexDomainError is SimplexDomainError.NoValidLink) {
|
||||
val domain = err.errorType.simplexDomain.fullDomainName
|
||||
if (isChannel) generalGetString(MR.strings.simplex_name_owner_no_channel_link).format(domain)
|
||||
else generalGetString(MR.strings.simplex_name_owner_no_address).format(domain)
|
||||
} else null
|
||||
|
||||
fun connErrorText(e: ChatError): String = when {
|
||||
@@ -1642,8 +1642,8 @@ object ChatController {
|
||||
generalGetString(MR.strings.invalid_connection_link)
|
||||
e is ChatError.ChatErrorChat && e.errorType is ChatErrorType.UnsupportedConnReq ->
|
||||
generalGetString(MR.strings.unsupported_connection_link)
|
||||
e is ChatError.ChatErrorChat && e.errorType is ChatErrorType.SimplexName ->
|
||||
if (e.errorType.simplexNameError is SimplexNameError.NoValidLink)
|
||||
e is ChatError.ChatErrorChat && e.errorType is ChatErrorType.CESimplexDomain ->
|
||||
if (e.errorType.simplexDomainError is SimplexDomainError.NoValidLink)
|
||||
generalGetString(MR.strings.simplex_name_no_valid_link)
|
||||
else generalGetString(MR.strings.simplex_name_unconfirmed)
|
||||
e is ChatError.ChatErrorAgent && e.agentError is AgentErrorType.NO_NAME_SERVERS ->
|
||||
@@ -1823,9 +1823,9 @@ object ChatController {
|
||||
}
|
||||
|
||||
// name is the encoded SimplexName (e.g. "@alice.simplex"); null clears it. Throws on rejection.
|
||||
suspend fun apiSetUserName(rh: Long?, name: String?): User {
|
||||
val userId = currentUserId("apiSetUserName")
|
||||
val r = sendCmd(rh, CC.ApiSetUserName(userId, name))
|
||||
suspend fun apiSetUserDomain(rh: Long?, simplexDomain: String?): User {
|
||||
val userId = currentUserId("apiSetUserDomain")
|
||||
val r = sendCmd(rh, CC.ApiSetUserDomain(userId, simplexDomain))
|
||||
return when {
|
||||
r is API.Result && r.res is CR.UserProfileUpdated -> r.res.user.updateRemoteHostId(rh)
|
||||
r is API.Result && r.res is CR.UserProfileNoChange -> r.res.user.updateRemoteHostId(rh)
|
||||
@@ -1840,17 +1840,17 @@ object ChatController {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun apiVerifyContactName(rh: Long?, contactId: Long): Pair<Contact, String?>? {
|
||||
val r = sendCmd(rh, CC.ApiVerifyContactName(contactId))
|
||||
suspend fun apiVerifyContactDomain(rh: Long?, contactId: Long): Pair<Contact, String?>? {
|
||||
val r = sendCmd(rh, CC.ApiVerifyContactDomain(contactId))
|
||||
if (r is API.Result && r.res is CR.ContactNameVerified) return r.res.contact to r.res.verificationFailure
|
||||
Log.e(TAG, "apiVerifyContactName bad response: ${r.responseType} ${r.details}")
|
||||
Log.e(TAG, "apiVerifyContactDomain bad response: ${r.responseType} ${r.details}")
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiVerifyPublicGroupName(rh: Long?, groupId: Long): Pair<GroupInfo, String?>? {
|
||||
val r = sendCmd(rh, CC.ApiVerifyPublicGroupName(groupId))
|
||||
suspend fun apiVerifyGroupDomain(rh: Long?, groupId: Long): Pair<GroupInfo, String?>? {
|
||||
val r = sendCmd(rh, CC.ApiVerifyGroupDomain(groupId))
|
||||
if (r is API.Result && r.res is CR.GroupNameVerified) return r.res.groupInfo to r.res.verificationFailure
|
||||
Log.e(TAG, "apiVerifyPublicGroupName bad response: ${r.responseType} ${r.details}")
|
||||
Log.e(TAG, "apiVerifyGroupDomain bad response: ${r.responseType} ${r.details}")
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -3885,9 +3885,9 @@ sealed class CC {
|
||||
class ApiShowMyAddress(val userId: Long): CC()
|
||||
class ApiAddMyAddressShortLink(val userId: Long): CC()
|
||||
class ApiSetProfileAddress(val userId: Long, val on: Boolean): CC()
|
||||
class ApiSetUserName(val userId: Long, val name: String?): CC()
|
||||
class ApiVerifyContactName(val contactId: Long): CC()
|
||||
class ApiVerifyPublicGroupName(val groupId: Long): CC()
|
||||
class ApiSetUserDomain(val userId: Long, val simplexDomain: String?): CC()
|
||||
class ApiVerifyContactDomain(val contactId: Long): CC()
|
||||
class ApiVerifyGroupDomain(val groupId: Long): CC()
|
||||
class ApiSetAddressSettings(val userId: Long, val addressSettings: AddressSettings): CC()
|
||||
class ApiGetCallInvitations: CC()
|
||||
class ApiSendCallInvitation(val contact: Contact, val callType: CallType): CC()
|
||||
@@ -4096,10 +4096,10 @@ sealed class CC {
|
||||
is ApiShowMyAddress -> "/_show_address $userId"
|
||||
is ApiAddMyAddressShortLink -> "/_short_link_address $userId"
|
||||
is ApiSetProfileAddress -> "/_profile_address $userId ${onOff(on)}"
|
||||
is ApiSetUserName -> "/_set_name $userId" + (if (name != null) " $name" else "")
|
||||
is ApiSetUserDomain -> "/_set domain $userId" + (if (simplexDomain != null) " $simplexDomain" else "")
|
||||
is ApiSetPublicGroupAccess -> "/_public group access #$groupId ${json.encodeToString(access)}"
|
||||
is ApiVerifyContactName -> "/_verify name @$contactId"
|
||||
is ApiVerifyPublicGroupName -> "/_verify name #$groupId"
|
||||
is ApiVerifyContactDomain -> "/_verify domain @$contactId"
|
||||
is ApiVerifyGroupDomain -> "/_verify domain #$groupId"
|
||||
is ApiSetAddressSettings -> "/_address_settings $userId ${json.encodeToString(addressSettings)}"
|
||||
is ApiAcceptContact -> "/_accept incognito=${onOff(incognito)} $contactReqId"
|
||||
is ApiRejectContact -> "/_reject $contactReqId"
|
||||
@@ -4281,10 +4281,10 @@ sealed class CC {
|
||||
is ApiShowMyAddress -> "apiShowMyAddress"
|
||||
is ApiAddMyAddressShortLink -> "apiAddMyAddressShortLink"
|
||||
is ApiSetProfileAddress -> "apiSetProfileAddress"
|
||||
is ApiSetUserName -> "apiSetUserName"
|
||||
is ApiSetUserDomain -> "apiSetUserDomain"
|
||||
is ApiSetPublicGroupAccess -> "apiSetPublicGroupAccess"
|
||||
is ApiVerifyContactName -> "apiVerifyContactName"
|
||||
is ApiVerifyPublicGroupName -> "apiVerifyPublicGroupName"
|
||||
is ApiVerifyContactDomain -> "apiVerifyContactDomain"
|
||||
is ApiVerifyGroupDomain -> "apiVerifyGroupDomain"
|
||||
is ApiSetAddressSettings -> "apiSetAddressSettings"
|
||||
is ApiAcceptContact -> "apiAcceptContact"
|
||||
is ApiRejectContact -> "apiRejectContact"
|
||||
@@ -7096,9 +7096,9 @@ sealed class OwnerVerification {
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class SimplexNameError {
|
||||
@Serializable @SerialName("noValidLink") object NoValidLink : SimplexNameError()
|
||||
@Serializable @SerialName("unknownName") object UnknownName : SimplexNameError()
|
||||
sealed class SimplexDomainError {
|
||||
@Serializable @SerialName("noValidLink") object NoValidLink : SimplexDomainError()
|
||||
@Serializable @SerialName("unknownDomain") object UnknownDomain : SimplexDomainError()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@@ -7438,7 +7438,7 @@ sealed class ChatErrorType {
|
||||
is ChatStoreChanged -> "chatStoreChanged"
|
||||
is ConnectionPlanChatError -> "connectionPlan"
|
||||
is InvalidConnReq -> "invalidConnReq"
|
||||
is SimplexName -> "simplexName"
|
||||
is CESimplexDomain -> "simplexDomain"
|
||||
is UnsupportedConnReq -> "unsupportedConnReq"
|
||||
is InvalidChatMessage -> "invalidChatMessage"
|
||||
is ConnReqMessageProhibited -> "connReqMessageProhibited"
|
||||
@@ -7521,7 +7521,7 @@ sealed class ChatErrorType {
|
||||
@Serializable @SerialName("chatStoreChanged") object ChatStoreChanged: ChatErrorType()
|
||||
@Serializable @SerialName("connectionPlan") class ConnectionPlanChatError(val connectionPlan: ConnectionPlan): ChatErrorType()
|
||||
@Serializable @SerialName("invalidConnReq") object InvalidConnReq: ChatErrorType()
|
||||
@Serializable @SerialName("simplexName") class SimplexName(val simplexName: SimplexNameInfo, val simplexNameError: SimplexNameError): ChatErrorType()
|
||||
@Serializable @SerialName("simplexDomain") class CESimplexDomain(val simplexDomain: SimplexDomain, val simplexDomainError: SimplexDomainError): ChatErrorType()
|
||||
@Serializable @SerialName("unsupportedConnReq") object UnsupportedConnReq: ChatErrorType()
|
||||
@Serializable @SerialName("invalidChatMessage") class InvalidChatMessage(val connection: Connection, val message: String): ChatErrorType()
|
||||
@Serializable @SerialName("connReqMessageProhibited") object ConnReqMessageProhibited: ChatErrorType()
|
||||
|
||||
+6
-7
@@ -757,17 +757,16 @@ fun ChatInfoHeader(cInfo: ChatInfo, contact: Contact) {
|
||||
modifier = Modifier.combinedClickable(onClick = copyDisplayName, onLongClick = copyDisplayName).onRightClick(copyDisplayName)
|
||||
)
|
||||
ChatInfoDescription(cInfo, displayName, copyNameToClipboard)
|
||||
val simplexName = contact.profile.simplexName
|
||||
if (simplexName != null && (contact.profile.simplexNameVerification != null || simplexName.proof != null)) {
|
||||
val domain = contact.profile.contactDomain
|
||||
if (domain != null && (contact.profile.contactDomainVerified != null || domain.proof != null)) {
|
||||
SimplexNameView(
|
||||
name = simplexName.shortName,
|
||||
verification = contact.profile.simplexNameVerification,
|
||||
autoVerify = chatModel.controller.appPrefs.privacyVerifySimplexNames.get(),
|
||||
simplexName = "@${domain.shortName}",
|
||||
verified = contact.profile.contactDomainVerified,
|
||||
verify = {
|
||||
val rhId = chatModel.remoteHostId()
|
||||
chatModel.controller.apiVerifyContactName(rhId, contact.contactId)?.let { (ct, reason) ->
|
||||
chatModel.controller.apiVerifyContactDomain(rhId, contact.contactId)?.let { (ct, reason) ->
|
||||
chatModel.chatsContext.updateContact(rhId, ct)
|
||||
ct.profile.simplexNameVerification to reason
|
||||
ct.profile.contactDomainVerified to reason
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
+8
-9
@@ -24,9 +24,8 @@ import kotlinx.coroutines.*
|
||||
// null on network error. With `autoVerify`, it runs once on open when state is null.
|
||||
@Composable
|
||||
fun SimplexNameView(
|
||||
name: String,
|
||||
verification: Boolean?,
|
||||
autoVerify: Boolean,
|
||||
simplexName: String,
|
||||
verified: Boolean?,
|
||||
verify: suspend () -> Pair<Boolean?, String?>?
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
@@ -59,7 +58,7 @@ fun SimplexNameView(
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (autoVerify && verification == null) runVerify(manual = false)
|
||||
if (chatModel.controller.appPrefs.privacyVerifySimplexNames.get() && verified == null) runVerify(manual = false)
|
||||
}
|
||||
|
||||
Row(
|
||||
@@ -68,18 +67,18 @@ fun SimplexNameView(
|
||||
modifier = Modifier.padding(top = DEFAULT_PADDING_HALF)
|
||||
) {
|
||||
Text(
|
||||
name,
|
||||
simplexName,
|
||||
style = MaterialTheme.typography.body2.copy(
|
||||
color = if (verification == true) MaterialTheme.colors.primary else MaterialTheme.colors.secondary,
|
||||
fontFamily = if (verification == true) FontFamily.Default else FontFamily.Monospace
|
||||
color = if (verified == true) MaterialTheme.colors.primary else MaterialTheme.colors.secondary,
|
||||
fontFamily = if (verified == true) FontFamily.Default else FontFamily.Monospace
|
||||
)
|
||||
)
|
||||
when {
|
||||
showSpinner.value ->
|
||||
CircularProgressIndicator(Modifier.size(16.dp), strokeWidth = 2.dp, color = MaterialTheme.colors.secondary)
|
||||
verification == true ->
|
||||
verified == true ->
|
||||
Icon(painterResource(MR.images.ic_check_filled), null, Modifier.size(18.dp), tint = MaterialTheme.colors.onBackground)
|
||||
verification == false ->
|
||||
verified == false ->
|
||||
Icon(
|
||||
painterResource(MR.images.ic_close), null, tint = Color.Red,
|
||||
modifier = Modifier.size(18.dp).clickable { runVerify(manual = true) }
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ fun ChannelWebPageView(
|
||||
val trimmedPage = webPage.value.trim()
|
||||
val newAccess = PublicGroupAccess(
|
||||
groupWebPage = trimmedPage.ifEmpty { null },
|
||||
simplexName = access?.simplexName,
|
||||
groupDomainClaim = access?.groupDomainClaim,
|
||||
domainWebPage = access?.domainWebPage ?: false,
|
||||
allowEmbedding = allowEmbedding.value
|
||||
)
|
||||
|
||||
+18
-20
@@ -180,14 +180,15 @@ fun ModalData.GroupChatInfoView(
|
||||
},
|
||||
setSimplexName = {
|
||||
ModalManager.end.showCustomModal { close ->
|
||||
SetSimplexNameView(
|
||||
val domain = groupInfo.groupProfile.publicGroup?.publicGroupAccess?.groupDomainClaim?.shortName
|
||||
SetSimplexDomainView(
|
||||
title = generalGetString(MR.strings.set_simplex_name),
|
||||
footer = generalGetString(MR.strings.set_channel_simplex_name_footer),
|
||||
prefix = "#",
|
||||
initial = groupInfo.groupProfile.publicGroup?.publicGroupAccess?.simplexName?.shortName ?: "",
|
||||
save = { name ->
|
||||
placeholder = "#channelname.testing",
|
||||
simplexName = if (domain == null) "" else "#$domain",
|
||||
save = { domain ->
|
||||
val access = groupInfo.groupProfile.publicGroup?.publicGroupAccess ?: PublicGroupAccess()
|
||||
val newAccess = access.copy(simplexName = name?.let { SimplexNameClaim(it) })
|
||||
val newAccess = access.copy(groupDomainClaim = domain?.let { SimplexDomainClaim(it) })
|
||||
val gInfo = chatModel.controller.apiSetPublicGroupAccess(rhId, groupInfo.groupId, newAccess)
|
||||
if (gInfo != null) {
|
||||
withContext(Dispatchers.Main) { chatModel.chatsContext.updateGroup(rhId, gInfo) }
|
||||
@@ -637,6 +638,12 @@ fun ModalData.GroupChatInfoLayout(
|
||||
if (groupInfo.isOwner && groupLink != null) {
|
||||
anyTopSectionRowShow = true
|
||||
ChannelLinkButton(manageGroupLink)
|
||||
SettingsActionItem(
|
||||
painterResource(MR.images.ic_tag),
|
||||
stringResource(MR.strings.simplex_name),
|
||||
setSimplexName,
|
||||
iconColor = MaterialTheme.colors.secondary
|
||||
)
|
||||
} else if (channelLink != null) {
|
||||
anyTopSectionRowShow = true
|
||||
ChannelLinkQRCodeSection(channelLink)
|
||||
@@ -825,14 +832,6 @@ fun ModalData.GroupChatInfoLayout(
|
||||
SectionDividerSpaced()
|
||||
SectionView(title = stringResource(MR.strings.advanced_options)) {
|
||||
ChannelWebPageButton(groupInfo, manageWebPage)
|
||||
if (groupInfo.groupProfile.publicGroup != null) {
|
||||
SettingsActionItem(
|
||||
painterResource(MR.images.ic_tag),
|
||||
stringResource(MR.strings.simplex_name),
|
||||
setSimplexName,
|
||||
iconColor = MaterialTheme.colors.secondary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -975,17 +974,16 @@ private fun GroupChatInfoHeader(cInfo: ChatInfo, groupInfo: GroupInfo) {
|
||||
)
|
||||
ChatInfoDescription(cInfo, displayName, copyNameToClipboard)
|
||||
val access = groupInfo.groupProfile.publicGroup?.publicGroupAccess
|
||||
val groupName = access?.simplexName?.shortName
|
||||
if (groupName != null && (groupInfo.simplexNameVerification != null || access.simplexName?.proof != null)) {
|
||||
val domain = access?.groupDomainClaim?.shortName
|
||||
if (domain != null && (groupInfo.groupDomainVerified != null || access.groupDomainClaim?.proof != null)) {
|
||||
SimplexNameView(
|
||||
name = groupName,
|
||||
verification = groupInfo.simplexNameVerification,
|
||||
autoVerify = chatModel.controller.appPrefs.privacyVerifySimplexNames.get(),
|
||||
simplexName = "#${domain}",
|
||||
verified = groupInfo.groupDomainVerified,
|
||||
verify = {
|
||||
val rhId = chatModel.remoteHostId()
|
||||
chatModel.controller.apiVerifyPublicGroupName(rhId, groupInfo.groupId)?.let { (gInfo, reason) ->
|
||||
chatModel.controller.apiVerifyGroupDomain(rhId, groupInfo.groupId)?.let { (gInfo, reason) ->
|
||||
chatModel.chatsContext.updateGroup(rhId, gInfo)
|
||||
gInfo.simplexNameVerification to reason
|
||||
gInfo.groupDomainVerified to reason
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
+13
-9
@@ -19,24 +19,28 @@ import kotlinx.coroutines.*
|
||||
// The field is prefilled with the full prefixed name; `save` receives the encoded name (or null to
|
||||
// clear) and returns true on success (it shows its own error alert otherwise).
|
||||
@Composable
|
||||
fun SetSimplexNameView(
|
||||
fun SetSimplexDomainView(
|
||||
title: String,
|
||||
footer: String,
|
||||
prefix: String,
|
||||
initial: String,
|
||||
placeholder: String,
|
||||
simplexName: String,
|
||||
save: suspend (String?) -> Boolean,
|
||||
close: () -> Unit
|
||||
) {
|
||||
val name = rememberSaveable { mutableStateOf(initial) }
|
||||
val name = rememberSaveable { mutableStateOf(simplexName) }
|
||||
val saving = remember { mutableStateOf(false) }
|
||||
val unchanged = name.value.trim() == initial.trim()
|
||||
val unchanged = name.value.trim() == simplexName.trim()
|
||||
|
||||
fun addSimplexTLD(s: String): String {
|
||||
return if (s.contains(".")) s else "$s.simplex"
|
||||
}
|
||||
|
||||
fun normalized(): String? {
|
||||
val s = name.value.trim()
|
||||
return when {
|
||||
s.isEmpty() -> null
|
||||
s.startsWith("@") || s.startsWith("#") -> prefix + s.substring(1)
|
||||
else -> prefix + s
|
||||
s.startsWith("@") || s.startsWith("#") -> addSimplexTLD(s.substring(1))
|
||||
else -> addSimplexTLD(s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +48,7 @@ fun SetSimplexNameView(
|
||||
withBGApi {
|
||||
saving.value = true
|
||||
val ok = try { save(normalized()) } catch (e: Exception) {
|
||||
Log.e(TAG, "SetSimplexNameView save: ${e.stackTraceToString()}")
|
||||
Log.e(TAG, "SetSimplexDomainView save: ${e.stackTraceToString()}")
|
||||
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_saving_simplex_name), e.message ?: "")
|
||||
false
|
||||
}
|
||||
@@ -57,7 +61,7 @@ fun SetSimplexNameView(
|
||||
ColumnWithScrollBar {
|
||||
AppBarTitle(title)
|
||||
SectionView {
|
||||
PlainTextEditor(name, placeholder = prefix + stringResource(MR.strings.simplex_name_placeholder))
|
||||
PlainTextEditor(name, placeholder)
|
||||
}
|
||||
SectionTextFooter(footer)
|
||||
SectionDividerSpaced()
|
||||
|
||||
+7
-6
@@ -369,18 +369,19 @@ private fun UserAddressLayout(
|
||||
stringResource(MR.strings.your_simplex_name),
|
||||
click = {
|
||||
ModalManager.start.showCustomModal { close ->
|
||||
SetSimplexNameView(
|
||||
val domain = user?.profile?.contactDomain?.shortName
|
||||
SetSimplexDomainView(
|
||||
title = generalGetString(MR.strings.set_simplex_name),
|
||||
footer = generalGetString(MR.strings.set_user_simplex_name_footer),
|
||||
prefix = "@",
|
||||
initial = user?.profile?.simplexName?.shortName ?: "",
|
||||
save = { name ->
|
||||
placeholder = "@yourname.testing",
|
||||
simplexName = if (domain == null) "" else "@$domain",
|
||||
save = { simplexDomain ->
|
||||
try {
|
||||
val u = chatModel.controller.apiSetUserName(user?.remoteHostId, name)
|
||||
val u = chatModel.controller.apiSetUserDomain(user?.remoteHostId, simplexDomain)
|
||||
withContext(Dispatchers.Main) { chatModel.updateUser(u) }
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "apiSetUserName: ${e.message}")
|
||||
Log.e(TAG, "apiSetUserDomain: ${e.message}")
|
||||
false
|
||||
}
|
||||
},
|
||||
|
||||
@@ -947,7 +947,6 @@
|
||||
<string name="simplex_name">SimpleX name</string>
|
||||
<string name="your_simplex_name">Your SimpleX name</string>
|
||||
<string name="set_simplex_name">Set SimpleX name</string>
|
||||
<string name="simplex_name_placeholder">name.simplex</string>
|
||||
<string name="error_saving_simplex_name">Error saving name</string>
|
||||
<string name="simplex_name_owner_no_channel_link">The SimpleX name %1$s is registered without channel link. Add channel link to the name via the registration page.</string>
|
||||
<string name="simplex_name_owner_no_address">The SimpleX name %1$s is registered without SimpleX address. Add your SimpleX address to the name via the registration page.</string>
|
||||
|
||||
Reference in new issue
Block a user