mobile: update type for UserContactLink changes, add addressAutoAccept API (#1245)

This commit is contained in:
Evgeny Poberezkin
2022-10-23 11:16:56 +01:00
committed by GitHub
parent dfb6dafd6f
commit 341199d599
7 changed files with 100 additions and 22 deletions
@@ -38,7 +38,7 @@ class ChatModel(val controller: ChatController) {
val groupMembers = mutableStateListOf<GroupMember>()
val terminalItems = mutableStateListOf<TerminalItem>()
val userAddress = mutableStateOf<String?>(null)
val userAddress = mutableStateOf<UserContactLinkRec?>(null)
val userSMPServers = mutableStateOf<(List<String>)?>(null)
val chatItemTTL = mutableStateOf<ChatItemTTL>(ChatItemTTL.None)
@@ -628,9 +628,9 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
return false
}
private suspend fun apiGetUserAddress(): String? {
private suspend fun apiGetUserAddress(): UserContactLinkRec? {
val r = sendCmd(CC.ShowMyAddress())
if (r is CR.UserContactLink) return r.connReqContact
if (r is CR.UserContactLink) return r.contactLink
if (r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore
&& r.chatError.storeError is StoreError.UserContactLinkNotFound) {
return null
@@ -639,6 +639,17 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
return null
}
suspend fun userAddressAutoAccept(autoAccept: AutoAccept?): UserContactLinkRec? {
val r = sendCmd(CC.AddressAutoAccept(autoAccept))
if (r is CR.UserContactLinkUpdated) return r.contactLink
if (r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore
&& r.chatError.storeError is StoreError.UserContactLinkNotFound) {
return null
}
Log.e(TAG, "userAddressAutoAccept bad response: ${r.responseType} ${r.details}")
return null
}
suspend fun apiAcceptContactRequest(contactReqId: Long): Contact? {
val r = sendCmd(CC.ApiAcceptContact(contactReqId))
return when {
@@ -1422,6 +1433,7 @@ sealed class CC {
class CreateMyAddress: CC()
class DeleteMyAddress: CC()
class ShowMyAddress: CC()
class AddressAutoAccept(val autoAccept: AutoAccept?): CC()
class ApiSendCallInvitation(val contact: Contact, val callType: CallType): CC()
class ApiRejectCall(val contact: Contact): CC()
class ApiSendCallOffer(val contact: Contact, val callOffer: WebRTCCallOffer): CC()
@@ -1484,6 +1496,7 @@ sealed class CC {
is CreateMyAddress -> "/address"
is DeleteMyAddress -> "/delete_address"
is ShowMyAddress -> "/show_address"
is AddressAutoAccept -> "/auto_accept ${AutoAccept.cmdString(autoAccept)}"
is ApiAcceptContact -> "/_accept $contactReqId"
is ApiRejectContact -> "/_reject $contactReqId"
is ApiSendCallInvitation -> "/_call invite @${contact.apiId} ${json.encodeToString(callType)}"
@@ -1547,6 +1560,7 @@ sealed class CC {
is CreateMyAddress -> "createMyAddress"
is DeleteMyAddress -> "deleteMyAddress"
is ShowMyAddress -> "showMyAddress"
is AddressAutoAccept -> "addressAutoAccept"
is ApiAcceptContact -> "apiAcceptContact"
is ApiRejectContact -> "apiRejectContact"
is ApiSendCallInvitation -> "apiSendCallInvitation"
@@ -1743,7 +1757,8 @@ sealed class CR {
@Serializable @SerialName("contactAliasUpdated") class ContactAliasUpdated(val toContact: Contact): CR()
@Serializable @SerialName("connectionAliasUpdated") class ConnectionAliasUpdated(val toConnection: PendingContactConnection): CR()
@Serializable @SerialName("apiParsedMarkdown") class ParsedMarkdown(val formattedText: List<FormattedText>? = null): CR()
@Serializable @SerialName("userContactLink") class UserContactLink(val connReqContact: String): CR()
@Serializable @SerialName("userContactLink") class UserContactLink(val contactLink: UserContactLinkRec): CR()
@Serializable @SerialName("userContactLinkUpdated") class UserContactLinkUpdated(val contactLink: UserContactLinkRec): CR()
@Serializable @SerialName("userContactLinkCreated") class UserContactLinkCreated(val connReqContact: String): CR()
@Serializable @SerialName("userContactLinkDeleted") class UserContactLinkDeleted: CR()
@Serializable @SerialName("contactConnected") class ContactConnected(val contact: Contact): CR()
@@ -1839,6 +1854,7 @@ sealed class CR {
is ConnectionAliasUpdated -> "connectionAliasUpdated"
is ParsedMarkdown -> "apiParsedMarkdown"
is UserContactLink -> "userContactLink"
is UserContactLinkUpdated -> "userContactLinkUpdated"
is UserContactLinkCreated -> "userContactLinkCreated"
is UserContactLinkDeleted -> "userContactLinkDeleted"
is ContactConnected -> "contactConnected"
@@ -1931,7 +1947,8 @@ sealed class CR {
is ContactAliasUpdated -> json.encodeToString(toContact)
is ConnectionAliasUpdated -> json.encodeToString(toConnection)
is ParsedMarkdown -> json.encodeToString(formattedText)
is UserContactLink -> connReqContact
is UserContactLink -> contactLink.responseDetails
is UserContactLinkUpdated -> contactLink.responseDetails
is UserContactLinkCreated -> connReqContact
is UserContactLinkDeleted -> noDetails()
is ContactConnected -> json.encodeToString(contact)
@@ -2034,6 +2051,24 @@ abstract class TerminalItem {
@Serializable
class ConnectionStats(val rcvServers: List<String>?, val sndServers: List<String>?)
@Serializable
class UserContactLinkRec(val connReqContact: String, val autoAccept: AutoAccept? = null) {
val responseDetails: String get() = "connReqContact: ${connReqContact}\nautoAccept: ${AutoAccept.cmdString(autoAccept)}"
}
@Serializable
class AutoAccept(val acceptIncognito: Boolean, val autoReply: MsgContent?) {
companion object {
fun cmdString(autoAccept: AutoAccept?): String {
if (autoAccept == null) return "off"
val s = "on" + if (autoAccept.acceptIncognito) " incognito=on" else ""
val msg = autoAccept.autoReply ?: return s
return s + " " + msg.cmdString
}
}
}
@Serializable
sealed class ChatError {
val string: String get() = when (this) {
@@ -16,6 +16,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.model.UserContactLinkRec
import chat.simplex.app.ui.theme.SimpleButton
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.*
@@ -28,7 +29,10 @@ fun UserAddressView(chatModel: ChatModel) {
userAddress = chatModel.userAddress.value,
createAddress = {
withApi {
chatModel.userAddress.value = chatModel.controller.apiCreateUserAddress()
val connReqContact = chatModel.controller.apiCreateUserAddress()
if (connReqContact != null) {
chatModel.userAddress.value = UserContactLinkRec(connReqContact)
}
}
},
share = { userAddress: String -> shareText(cxt, userAddress) },
@@ -50,7 +54,7 @@ fun UserAddressView(chatModel: ChatModel) {
@Composable
fun UserAddressLayout(
userAddress: String?,
userAddress: UserContactLinkRec?,
createAddress: () -> Unit,
share: (String) -> Unit,
deleteAddress: () -> Unit
@@ -83,7 +87,7 @@ fun UserAddressLayout(
Modifier.padding(bottom = 24.dp),
lineHeight = 22.sp
)
QRCode(userAddress, Modifier.weight(1f, fill = false).aspectRatio(1f))
QRCode(userAddress.connReqContact, Modifier.weight(1f, fill = false).aspectRatio(1f))
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically,
@@ -92,7 +96,7 @@ fun UserAddressLayout(
SimpleButton(
stringResource(R.string.share_link),
icon = Icons.Outlined.Share,
click = { share(userAddress) })
click = { share(userAddress.connReqContact) })
SimpleButton(
stringResource(R.string.delete_address),
icon = Icons.Outlined.Delete,
@@ -133,7 +137,7 @@ fun PreviewUserAddressLayoutNoAddress() {
fun PreviewUserAddressLayoutAddressCreated() {
SimpleXTheme {
UserAddressLayout(
userAddress = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D",
userAddress = UserContactLinkRec("https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D"),
createAddress = {},
share = { _ -> },
deleteAddress = {},
+1 -1
View File
@@ -30,7 +30,7 @@ final class ChatModel: ObservableObject {
@Published var groupMembers: [GroupMember] = []
// items in the terminal view
@Published var terminalItems: [TerminalItem] = []
@Published var userAddress: String?
@Published var userAddress: UserContactLink?
@Published var userSMPServers: [String]?
@Published var chatItemTTL: ChatItemTTL = .none
@Published var appOpenUrl: URL?
+12 -5
View File
@@ -488,13 +488,20 @@ func apiDeleteUserAddress() async throws {
throw r
}
func apiGetUserAddress() throws -> String? {
func apiGetUserAddress() throws -> UserContactLink? {
let r = chatSendCmdSync(.showMyAddress)
switch r {
case let .userContactLink(connReq):
return connReq
case .chatCmdError(chatError: .errorStore(storeError: .userContactLinkNotFound)):
return nil
case let .userContactLink(contactLink): return contactLink
case .chatCmdError(chatError: .errorStore(storeError: .userContactLinkNotFound)): return nil
default: throw r
}
}
func userAddressAutoAccept(_ autoAccept: AutoAccept?) async throws -> UserContactLink? {
let r = await chatSendCmd(.addressAutoAccept(autoAccept: autoAccept))
switch r {
case let .userContactLinkUpdated(contactLink): return contactLink
case .chatCmdError(chatError: .errorStore(storeError: .userContactLinkNotFound)): return nil
default: throw r
}
}
@@ -36,7 +36,7 @@ struct UserAddress: View {
Text("You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it.")
.padding(.bottom)
if let userAdress = chatModel.userAddress {
QRCode(uri: userAdress)
QRCode(uri: userAdress.connReqContact)
HStack {
Button {
showShareSheet(items: [userAdress])
@@ -55,9 +55,9 @@ struct UserAddress: View {
Button {
Task {
do {
let userAddress = try await apiCreateUserAddress()
let connReqContact = try await apiCreateUserAddress()
DispatchQueue.main.async {
chatModel.userAddress = userAddress
chatModel.userAddress = UserContactLink(connReqContact: connReqContact)
}
} catch let error {
logger.error("UserAddress apiCreateUserAddress: \(responseError(error))")
@@ -101,7 +101,7 @@ struct UserAddress: View {
struct UserAddress_Previews: PreviewProvider {
static var previews: some View {
let chatModel = ChatModel()
chatModel.userAddress = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D"
chatModel.userAddress = UserContactLink(connReqContact: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D")
return Group {
UserAddress()
.environmentObject(chatModel)
+34 -2
View File
@@ -66,6 +66,7 @@ public enum ChatCommand {
case createMyAddress
case deleteMyAddress
case showMyAddress
case addressAutoAccept(autoAccept: AutoAccept?)
case apiAcceptContact(contactReqId: Int64)
case apiRejectContact(contactReqId: Int64)
// WebRTC calls
@@ -141,6 +142,7 @@ public enum ChatCommand {
case .createMyAddress: return "/address"
case .deleteMyAddress: return "/delete_address"
case .showMyAddress: return "/show_address"
case let .addressAutoAccept(autoAccept): return "/auto_accept \(AutoAccept.cmdString(autoAccept))"
case let .apiAcceptContact(contactReqId): return "/_accept \(contactReqId)"
case let .apiRejectContact(contactReqId): return "/_reject \(contactReqId)"
case let .apiSendCallInvitation(contact, callType): return "/_call invite @\(contact.apiId) \(encodeJSON(callType))"
@@ -215,6 +217,7 @@ public enum ChatCommand {
case .createMyAddress: return "createMyAddress"
case .deleteMyAddress: return "deleteMyAddress"
case .showMyAddress: return "showMyAddress"
case .addressAutoAccept: return "addressAutoAccept"
case .apiAcceptContact: return "apiAcceptContact"
case .apiRejectContact: return "apiRejectContact"
case .apiSendCallInvitation: return "apiSendCallInvitation"
@@ -294,7 +297,8 @@ public enum ChatResponse: Decodable, Error {
case userProfileUpdated(fromProfile: Profile, toProfile: Profile)
case contactAliasUpdated(toContact: Contact)
case connectionAliasUpdated(toConnection: PendingContactConnection)
case userContactLink(connReqContact: String)
case userContactLink(contactLink: UserContactLink)
case userContactLinkUpdated(contactLink: UserContactLink)
case userContactLinkCreated(connReqContact: String)
case userContactLinkDeleted
case contactConnected(contact: Contact)
@@ -395,6 +399,7 @@ public enum ChatResponse: Decodable, Error {
case .contactAliasUpdated: return "contactAliasUpdated"
case .connectionAliasUpdated: return "connectionAliasUpdated"
case .userContactLink: return "userContactLink"
case .userContactLinkUpdated: return "userContactLinkUpdated"
case .userContactLinkCreated: return "userContactLinkCreated"
case .userContactLinkDeleted: return "userContactLinkDeleted"
case .contactConnected: return "contactConnected"
@@ -494,7 +499,8 @@ public enum ChatResponse: Decodable, Error {
case let .userProfileUpdated(_, toProfile): return String(describing: toProfile)
case let .contactAliasUpdated(toContact): return String(describing: toContact)
case let .connectionAliasUpdated(toConnection): return String(describing: toConnection)
case let .userContactLink(connReq): return connReq
case let .userContactLink(contactLink): return contactLink.responseDetails
case let .userContactLinkUpdated(contactLink): return contactLink.responseDetails
case let .userContactLinkCreated(connReq): return connReq
case .userContactLinkDeleted: return noDetails
case let .contactConnected(contact): return String(describing: contact)
@@ -702,6 +708,32 @@ public struct ConnectionStats: Codable {
public var sndServers: [String]?
}
public struct UserContactLink: Decodable {
public var connReqContact: String
public var autoAccept: AutoAccept?
public init(connReqContact: String, autoAccept: AutoAccept? = nil) {
self.connReqContact = connReqContact
self.autoAccept = autoAccept
}
var responseDetails: String {
"connReqContact: \(connReqContact)\nautoAccept: \(AutoAccept.cmdString(autoAccept))"
}
}
public struct AutoAccept: Codable {
public var acceptIncognito: Bool
public var autoReply: MsgContent?
static func cmdString(_ autoAccept: AutoAccept?) -> String {
guard let autoAccept = autoAccept else { return "off" }
let s = "on" + (autoAccept.acceptIncognito ? " incognito=on" : "")
guard let msg = autoAccept.autoReply else { return s }
return s + " " + msg.cmdString
}
}
public protocol SelectableItem: Hashable, Identifiable {
var label: LocalizedStringKey { get }
static var values: [Self] { get }