mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-03-14 09:45:42 +00:00
Merge branch 'master' into remote-desktop
This commit is contained in:
commit
0444367002
41 changed files with 704 additions and 306 deletions
|
@ -292,10 +292,7 @@ struct ContentView: View {
|
|||
var path = url.path
|
||||
if (path == "/contact" || path == "/invitation") {
|
||||
path.removeFirst()
|
||||
// TODO normalize in backend; revert
|
||||
// let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)")
|
||||
var link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)")
|
||||
link = link.starts(with: "simplex:/") ? link.replacingOccurrences(of: "simplex:/", with: "https://simplex.chat/") : link
|
||||
let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)")
|
||||
planAndConnect(
|
||||
link,
|
||||
showAlert: showPlanAndConnectAlert,
|
||||
|
|
|
@ -62,6 +62,7 @@ final class ChatModel: ObservableObject {
|
|||
// current chat
|
||||
@Published var chatId: String?
|
||||
@Published var reversedChatItems: [ChatItem] = []
|
||||
var chatItemStatuses: Dictionary<Int64, CIStatus> = [:]
|
||||
@Published var chatToTop: String?
|
||||
@Published var groupMembers: [GroupMember] = []
|
||||
// items in the terminal view
|
||||
|
@ -306,7 +307,11 @@ final class ChatModel: ObservableObject {
|
|||
return false
|
||||
} else {
|
||||
withAnimation(itemAnimation()) {
|
||||
reversedChatItems.insert(cItem, at: hasLiveDummy ? 1 : 0)
|
||||
var ci = cItem
|
||||
if let status = chatItemStatuses.removeValue(forKey: ci.id), case .sndNew = ci.meta.itemStatus {
|
||||
ci.meta.itemStatus = status
|
||||
}
|
||||
reversedChatItems.insert(ci, at: hasLiveDummy ? 1 : 0)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -319,23 +324,19 @@ final class ChatModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func updateChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) {
|
||||
func updateChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem, status: CIStatus? = nil) {
|
||||
if chatId == cInfo.id, let i = getChatItemIndex(cItem) {
|
||||
withAnimation {
|
||||
_updateChatItem(at: i, with: cItem)
|
||||
}
|
||||
} else if let status = status {
|
||||
chatItemStatuses.updateValue(status, forKey: cItem.id)
|
||||
}
|
||||
}
|
||||
|
||||
private func _updateChatItem(at i: Int, with cItem: ChatItem) {
|
||||
let ci = reversedChatItems[i]
|
||||
reversedChatItems[i] = cItem
|
||||
reversedChatItems[i].viewTimestamp = .now
|
||||
// on some occasions the confirmation of message being accepted by the server (tick)
|
||||
// arrives earlier than the response from API, and item remains without tick
|
||||
if case .sndNew = cItem.meta.itemStatus {
|
||||
reversedChatItems[i].meta.itemStatus = ci.meta.itemStatus
|
||||
}
|
||||
}
|
||||
|
||||
private func getChatItemIndex(_ cItem: ChatItem) -> Int? {
|
||||
|
@ -474,6 +475,7 @@ final class ChatModel: ObservableObject {
|
|||
}
|
||||
// clear current chat
|
||||
if chatId == cInfo.id {
|
||||
chatItemStatuses = [:]
|
||||
reversedChatItems = []
|
||||
}
|
||||
}
|
||||
|
|
|
@ -312,6 +312,7 @@ func loadChat(chat: Chat, search: String = "") {
|
|||
do {
|
||||
let cInfo = chat.chatInfo
|
||||
let m = ChatModel.shared
|
||||
m.chatItemStatuses = [:]
|
||||
m.reversedChatItems = []
|
||||
let chat = try apiGetChat(type: cInfo.chatType, id: cInfo.apiId, search: search)
|
||||
m.updateChatInfo(chat.chatInfo)
|
||||
|
@ -593,7 +594,6 @@ func apiSetConnectionIncognito(connId: Int64, incognito: Bool) async throws -> P
|
|||
}
|
||||
|
||||
func apiConnectPlan(connReq: String) async throws -> ConnectionPlan {
|
||||
logger.error("apiConnectPlan connReq: \(connReq)")
|
||||
let userId = try currentUserId("apiConnectPlan")
|
||||
let r = await chatSendCmd(.apiConnectPlan(userId: userId, connReq: connReq))
|
||||
if case let .connectionPlan(_, connectionPlan) = r { return connectionPlan }
|
||||
|
@ -1453,11 +1453,8 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
|||
case let .chatItemStatusUpdated(user, aChatItem):
|
||||
let cInfo = aChatItem.chatInfo
|
||||
let cItem = aChatItem.chatItem
|
||||
if !cItem.isDeletedContent {
|
||||
let added = active(user) ? await MainActor.run { m.upsertChatItem(cInfo, cItem) } : true
|
||||
if added && cItem.showNotification {
|
||||
NtfManager.shared.notifyMessageReceived(user, cInfo, cItem)
|
||||
}
|
||||
if !cItem.isDeletedContent && active(user) {
|
||||
await MainActor.run { m.updateChatItem(cInfo, cItem, status: cItem.meta.itemStatus) }
|
||||
}
|
||||
if let endTask = m.messageDelivery[cItem.id] {
|
||||
switch cItem.meta.itemStatus {
|
||||
|
|
|
@ -168,9 +168,9 @@ struct ChatInfoView: View {
|
|||
|
||||
if let contactLink = contact.contactLink {
|
||||
Section {
|
||||
QRCode(uri: contactLink)
|
||||
SimpleXLinkQRCode(uri: contactLink)
|
||||
Button {
|
||||
showShareSheet(items: [contactLink])
|
||||
showShareSheet(items: [simplexChatLink(contactLink)])
|
||||
} label: {
|
||||
Label("Share address", systemImage: "square.and.arrow.up")
|
||||
}
|
||||
|
|
|
@ -121,13 +121,11 @@ private func formatText(_ ft: FormattedText, _ preview: Bool) -> Text {
|
|||
case .secret: return Text(t).foregroundColor(.clear).underline(color: .primary)
|
||||
case let .colored(color): return Text(t).foregroundColor(color.uiColor)
|
||||
case .uri: return linkText(t, t, preview, prefix: "")
|
||||
case let .simplexLink(linkType, simplexUri, trustedUri, smpHosts):
|
||||
case let .simplexLink(linkType, simplexUri, smpHosts):
|
||||
switch privacySimplexLinkModeDefault.get() {
|
||||
case .description: return linkText(simplexLinkText(linkType, smpHosts), simplexUri, preview, prefix: "")
|
||||
case .full: return linkText(t, simplexUri, preview, prefix: "")
|
||||
case .browser: return trustedUri
|
||||
? linkText(t, t, preview, prefix: "")
|
||||
: linkText(t, t, preview, prefix: "", color: .red, uiColor: .red)
|
||||
case .browser: return linkText(t, simplexUri, preview, prefix: "")
|
||||
}
|
||||
case .email: return linkText(t, t, preview, prefix: "mailto:")
|
||||
case .phone: return linkText(t, t.replacingOccurrences(of: " ", with: ""), preview, prefix: "tel:")
|
||||
|
|
|
@ -91,6 +91,7 @@ struct ChatView: View {
|
|||
chatModel.chatId = nil
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
|
||||
if chatModel.chatId == nil {
|
||||
chatModel.chatItemStatuses = [:]
|
||||
chatModel.reversedChatItems = []
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,9 +41,9 @@ struct GroupLinkView: View {
|
|||
}
|
||||
}
|
||||
.frame(height: 36)
|
||||
QRCode(uri: groupLink)
|
||||
SimpleXLinkQRCode(uri: groupLink)
|
||||
Button {
|
||||
showShareSheet(items: [groupLink])
|
||||
showShareSheet(items: [simplexChatLink(groupLink)])
|
||||
} label: {
|
||||
Label("Share link", systemImage: "square.and.arrow.up")
|
||||
}
|
||||
|
|
|
@ -94,9 +94,9 @@ struct GroupMemberInfoView: View {
|
|||
|
||||
if let contactLink = member.contactLink {
|
||||
Section {
|
||||
QRCode(uri: contactLink)
|
||||
SimpleXLinkQRCode(uri: contactLink)
|
||||
Button {
|
||||
showShareSheet(items: [contactLink])
|
||||
showShareSheet(items: [simplexChatLink(contactLink)])
|
||||
} label: {
|
||||
Label("Share address", systemImage: "square.and.arrow.up")
|
||||
}
|
||||
|
|
|
@ -27,8 +27,7 @@ struct GroupPreferencesView: View {
|
|||
featureSection(.directMessages, $preferences.directMessages.enable)
|
||||
featureSection(.reactions, $preferences.reactions.enable)
|
||||
featureSection(.voice, $preferences.voice.enable)
|
||||
// TODO uncomment in 5.3
|
||||
// featureSection(.files, $preferences.files.enable)
|
||||
featureSection(.files, $preferences.files.enable)
|
||||
|
||||
if groupInfo.canEdit {
|
||||
Section {
|
||||
|
|
|
@ -61,7 +61,7 @@ struct ContactConnectionInfo: View {
|
|||
|
||||
if contactConnection.initiated,
|
||||
let connReqInv = contactConnection.connReqInv {
|
||||
QRCode(uri: connReqInv)
|
||||
SimpleXLinkQRCode(uri: simplexChatLink(connReqInv))
|
||||
incognitoEnabled()
|
||||
shareLinkButton(connReqInv)
|
||||
oneTimeLinkLearnMoreButton()
|
||||
|
|
|
@ -21,7 +21,7 @@ struct AddContactView: View {
|
|||
List {
|
||||
Section {
|
||||
if connReqInvitation != "" {
|
||||
QRCode(uri: connReqInvitation)
|
||||
SimpleXLinkQRCode(uri: connReqInvitation)
|
||||
} else {
|
||||
ProgressView()
|
||||
.progressViewStyle(.circular)
|
||||
|
@ -99,7 +99,7 @@ func sharedProfileInfo(_ incognito: Bool) -> Text {
|
|||
|
||||
func shareLinkButton(_ connReqInvitation: String) -> some View {
|
||||
Button {
|
||||
showShareSheet(items: [connReqInvitation])
|
||||
showShareSheet(items: [simplexChatLink(connReqInvitation)])
|
||||
} label: {
|
||||
settingsRow("square.and.arrow.up") {
|
||||
Text("Share 1-time link")
|
||||
|
|
|
@ -62,7 +62,9 @@ enum PlanAndConnectAlert: Identifiable {
|
|||
case ownInvitationLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case invitationLinkConnecting(connectionLink: String)
|
||||
case ownContactAddressConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case contactAddressConnectingConfirmReconnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case groupLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case groupLinkConnectingConfirmReconnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case groupLinkConnecting(connectionLink: String, groupInfo: GroupInfo?)
|
||||
|
||||
var id: String {
|
||||
|
@ -70,7 +72,9 @@ enum PlanAndConnectAlert: Identifiable {
|
|||
case let .ownInvitationLinkConfirmConnect(connectionLink, _, _): return "ownInvitationLinkConfirmConnect \(connectionLink)"
|
||||
case let .invitationLinkConnecting(connectionLink): return "invitationLinkConnecting \(connectionLink)"
|
||||
case let .ownContactAddressConfirmConnect(connectionLink, _, _): return "ownContactAddressConfirmConnect \(connectionLink)"
|
||||
case let .contactAddressConnectingConfirmReconnect(connectionLink, _, _): return "contactAddressConnectingConfirmReconnect \(connectionLink)"
|
||||
case let .groupLinkConfirmConnect(connectionLink, _, _): return "groupLinkConfirmConnect \(connectionLink)"
|
||||
case let .groupLinkConnectingConfirmReconnect(connectionLink, _, _): return "groupLinkConnectingConfirmReconnect \(connectionLink)"
|
||||
case let .groupLinkConnecting(connectionLink, _): return "groupLinkConnecting \(connectionLink)"
|
||||
}
|
||||
}
|
||||
|
@ -103,6 +107,16 @@ func planAndConnectAlert(_ alert: PlanAndConnectAlert, dismiss: Bool) -> Alert {
|
|||
),
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
case let .contactAddressConnectingConfirmReconnect(connectionLink, connectionPlan, incognito):
|
||||
return Alert(
|
||||
title: Text("Repeat connection request?"),
|
||||
message: Text("You have already requested connection via this address!"),
|
||||
primaryButton: .destructive(
|
||||
Text(incognito ? "Connect incognito" : "Connect"),
|
||||
action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) }
|
||||
),
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
case let .groupLinkConfirmConnect(connectionLink, connectionPlan, incognito):
|
||||
return Alert(
|
||||
title: Text("Join group?"),
|
||||
|
@ -113,6 +127,16 @@ func planAndConnectAlert(_ alert: PlanAndConnectAlert, dismiss: Bool) -> Alert {
|
|||
),
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
case let .groupLinkConnectingConfirmReconnect(connectionLink, connectionPlan, incognito):
|
||||
return Alert(
|
||||
title: Text("Repeat join request?"),
|
||||
message: Text("You are already joining the group via this link!"),
|
||||
primaryButton: .destructive(
|
||||
Text(incognito ? "Join incognito" : "Join"),
|
||||
action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) }
|
||||
),
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
case let .groupLinkConnecting(_, groupInfo):
|
||||
if let groupInfo = groupInfo {
|
||||
return Alert(
|
||||
|
@ -130,13 +154,13 @@ func planAndConnectAlert(_ alert: PlanAndConnectAlert, dismiss: Bool) -> Alert {
|
|||
|
||||
enum PlanAndConnectActionSheet: Identifiable {
|
||||
case askCurrentOrIncognitoProfile(connectionLink: String, connectionPlan: ConnectionPlan?, title: LocalizedStringKey)
|
||||
case ownLinkAskCurrentOrIncognitoProfile(connectionLink: String, connectionPlan: ConnectionPlan, title: LocalizedStringKey)
|
||||
case askCurrentOrIncognitoProfileDestructive(connectionLink: String, connectionPlan: ConnectionPlan, title: LocalizedStringKey)
|
||||
case ownGroupLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool?, groupInfo: GroupInfo)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case let .askCurrentOrIncognitoProfile(connectionLink, _, _): return "askCurrentOrIncognitoProfile \(connectionLink)"
|
||||
case let .ownLinkAskCurrentOrIncognitoProfile(connectionLink, _, _): return "ownLinkAskCurrentOrIncognitoProfile \(connectionLink)"
|
||||
case let .askCurrentOrIncognitoProfileDestructive(connectionLink, _, _): return "askCurrentOrIncognitoProfileDestructive \(connectionLink)"
|
||||
case let .ownGroupLinkConfirmConnect(connectionLink, _, _, _): return "ownGroupLinkConfirmConnect \(connectionLink)"
|
||||
}
|
||||
}
|
||||
|
@ -153,7 +177,7 @@ func planAndConnectActionSheet(_ sheet: PlanAndConnectActionSheet, dismiss: Bool
|
|||
.cancel()
|
||||
]
|
||||
)
|
||||
case let .ownLinkAskCurrentOrIncognitoProfile(connectionLink, connectionPlan, title):
|
||||
case let .askCurrentOrIncognitoProfileDestructive(connectionLink, connectionPlan, title):
|
||||
return ActionSheet(
|
||||
title: Text(title),
|
||||
buttons: [
|
||||
|
@ -211,7 +235,7 @@ func planAndConnect(
|
|||
if let incognito = incognito {
|
||||
showAlert(.ownInvitationLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.ownLinkAskCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own one-time link!"))
|
||||
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own one-time link!"))
|
||||
}
|
||||
case let .connecting(contact_):
|
||||
logger.debug("planAndConnect, .invitationLink, .connecting, incognito=\(incognito?.description ?? "nil")")
|
||||
|
@ -238,10 +262,17 @@ func planAndConnect(
|
|||
if let incognito = incognito {
|
||||
showAlert(.ownContactAddressConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.ownLinkAskCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own SimpleX address!"))
|
||||
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own SimpleX address!"))
|
||||
}
|
||||
case let .connecting(contact):
|
||||
logger.debug("planAndConnect, .contactAddress, .connecting, incognito=\(incognito?.description ?? "nil")")
|
||||
case .connectingConfirmReconnect:
|
||||
logger.debug("planAndConnect, .contactAddress, .connectingConfirmReconnect, incognito=\(incognito?.description ?? "nil")")
|
||||
if let incognito = incognito {
|
||||
showAlert(.contactAddressConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You have already requested connection!\nRepeat connection request?"))
|
||||
}
|
||||
case let .connectingProhibit(contact):
|
||||
logger.debug("planAndConnect, .contactAddress, .connectingProhibit, incognito=\(incognito?.description ?? "nil")")
|
||||
openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) }
|
||||
case let .known(contact):
|
||||
logger.debug("planAndConnect, .contactAddress, .known, incognito=\(incognito?.description ?? "nil")")
|
||||
|
@ -258,8 +289,15 @@ func planAndConnect(
|
|||
case let .ownLink(groupInfo):
|
||||
logger.debug("planAndConnect, .groupLink, .ownLink, incognito=\(incognito?.description ?? "nil")")
|
||||
showActionSheet(.ownGroupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito, groupInfo: groupInfo))
|
||||
case let .connecting(groupInfo_):
|
||||
logger.debug("planAndConnect, .groupLink, .connecting, incognito=\(incognito?.description ?? "nil")")
|
||||
case .connectingConfirmReconnect:
|
||||
logger.debug("planAndConnect, .groupLink, .connectingConfirmReconnect, incognito=\(incognito?.description ?? "nil")")
|
||||
if let incognito = incognito {
|
||||
showAlert(.groupLinkConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You are already joining the group!\nRepeat join request?"))
|
||||
}
|
||||
case let .connectingProhibit(groupInfo_):
|
||||
logger.debug("planAndConnect, .groupLink, .connectingProhibit, incognito=\(incognito?.description ?? "nil")")
|
||||
showAlert(.groupLinkConnecting(connectionLink: connectionLink, groupInfo: groupInfo_))
|
||||
case let .known(groupInfo):
|
||||
logger.debug("planAndConnect, .groupLink, .known, incognito=\(incognito?.description ?? "nil")")
|
||||
|
|
|
@ -28,6 +28,22 @@ struct MutableQRCode: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct SimpleXLinkQRCode: View {
|
||||
let uri: String
|
||||
var withLogo: Bool = true
|
||||
var tintColor = UIColor(red: 0.023, green: 0.176, blue: 0.337, alpha: 1)
|
||||
|
||||
var body: some View {
|
||||
QRCode(uri: simplexChatLink(uri), withLogo: withLogo, tintColor: tintColor)
|
||||
}
|
||||
}
|
||||
|
||||
func simplexChatLink(_ uri: String) -> String {
|
||||
uri.starts(with: "simplex:/")
|
||||
? uri.replacingOccurrences(of: "simplex:/", with: "https://simplex.chat/")
|
||||
: uri
|
||||
}
|
||||
|
||||
struct QRCode: View {
|
||||
let uri: String
|
||||
var withLogo: Bool = true
|
||||
|
|
|
@ -31,7 +31,7 @@ struct CreateSimpleXAddress: View {
|
|||
Spacer()
|
||||
|
||||
if let userAddress = m.userAddress {
|
||||
QRCode(uri: userAddress.connReqContact)
|
||||
SimpleXLinkQRCode(uri: userAddress.connReqContact)
|
||||
.frame(maxHeight: g.size.width)
|
||||
shareQRCodeButton(userAddress)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
@ -126,7 +126,7 @@ struct CreateSimpleXAddress: View {
|
|||
|
||||
private func shareQRCodeButton(_ userAddress: UserContactLink) -> some View {
|
||||
Button {
|
||||
showShareSheet(items: [userAddress.connReqContact])
|
||||
showShareSheet(items: [simplexChatLink(userAddress.connReqContact)])
|
||||
} label: {
|
||||
Label("Share", systemImage: "square.and.arrow.up")
|
||||
}
|
||||
|
@ -194,7 +194,7 @@ struct SendAddressMailView: View {
|
|||
let messageBody = String(format: NSLocalizedString("""
|
||||
<p>Hi!</p>
|
||||
<p><a href="%@">Connect to me via SimpleX Chat</a></p>
|
||||
""", comment: "email text"), userAddress.connReqContact)
|
||||
""", comment: "email text"), simplexChatLink(userAddress.connReqContact))
|
||||
MailView(
|
||||
isShowing: self.$showMailView,
|
||||
result: $mailViewResult,
|
||||
|
|
|
@ -93,7 +93,9 @@ struct PrivacySettings: View {
|
|||
}
|
||||
settingsRow("link") {
|
||||
Picker("SimpleX links", selection: $simplexLinkMode) {
|
||||
ForEach(SimpleXLinkMode.values) { mode in
|
||||
ForEach(
|
||||
SimpleXLinkMode.values + (SimpleXLinkMode.values.contains(simplexLinkMode) ? [] : [simplexLinkMode])
|
||||
) { mode in
|
||||
Text(mode.text)
|
||||
}
|
||||
}
|
||||
|
@ -104,10 +106,6 @@ struct PrivacySettings: View {
|
|||
}
|
||||
} header: {
|
||||
Text("Chats")
|
||||
} footer: {
|
||||
if case .browser = simplexLinkMode {
|
||||
Text("Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red.")
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
|
|
|
@ -93,7 +93,7 @@ enum SimpleXLinkMode: String, Identifiable {
|
|||
case full
|
||||
case browser
|
||||
|
||||
static var values: [SimpleXLinkMode] = [.description, .full, .browser]
|
||||
static var values: [SimpleXLinkMode] = [.description, .full]
|
||||
|
||||
public var id: Self { self }
|
||||
|
||||
|
|
|
@ -190,7 +190,7 @@ struct UserAddressView: View {
|
|||
|
||||
@ViewBuilder private func existingAddressView(_ userAddress: UserContactLink) -> some View {
|
||||
Section {
|
||||
MutableQRCode(uri: Binding.constant(userAddress.connReqContact))
|
||||
MutableQRCode(uri: Binding.constant(simplexChatLink(userAddress.connReqContact)))
|
||||
shareQRCodeButton(userAddress)
|
||||
if MFMailComposeViewController.canSendMail() {
|
||||
shareViaEmailButton(userAddress)
|
||||
|
@ -248,7 +248,7 @@ struct UserAddressView: View {
|
|||
|
||||
private func shareQRCodeButton(_ userAddress: UserContactLink) -> some View {
|
||||
Button {
|
||||
showShareSheet(items: [userAddress.connReqContact])
|
||||
showShareSheet(items: [simplexChatLink(userAddress.connReqContact)])
|
||||
} label: {
|
||||
settingsRow("square.and.arrow.up") {
|
||||
Text("Share address")
|
||||
|
|
|
@ -931,14 +931,16 @@ public enum InvitationLinkPlan: Decodable {
|
|||
public enum ContactAddressPlan: Decodable {
|
||||
case ok
|
||||
case ownLink
|
||||
case connecting(contact: Contact)
|
||||
case connectingConfirmReconnect
|
||||
case connectingProhibit(contact: Contact)
|
||||
case known(contact: Contact)
|
||||
}
|
||||
|
||||
public enum GroupLinkPlan: Decodable {
|
||||
case ok
|
||||
case ownLink(groupInfo: GroupInfo)
|
||||
case connecting(groupInfo_: GroupInfo?)
|
||||
case connectingConfirmReconnect
|
||||
case connectingProhibit(groupInfo_: GroupInfo?)
|
||||
case known(groupInfo: GroupInfo)
|
||||
}
|
||||
|
||||
|
|
|
@ -3068,7 +3068,7 @@ public enum Format: Decodable, Equatable {
|
|||
case secret
|
||||
case colored(color: FormatColor)
|
||||
case uri
|
||||
case simplexLink(linkType: SimplexLinkType, simplexUri: String, trustedUri: Bool, smpHosts: [String])
|
||||
case simplexLink(linkType: SimplexLinkType, simplexUri: String, smpHosts: [String])
|
||||
case email
|
||||
case phone
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ object ChatModel {
|
|||
// current chat
|
||||
val chatId = mutableStateOf<String?>(null)
|
||||
val chatItems = mutableStateListOf<ChatItem>()
|
||||
val chatItemStatuses = mutableMapOf<Long, CIStatus>()
|
||||
val groupMembers = mutableStateListOf<GroupMember>()
|
||||
|
||||
val terminalItems = mutableStateListOf<TerminalItem>()
|
||||
|
@ -135,6 +136,7 @@ object ChatModel {
|
|||
fun hasChat(id: String): Boolean = chats.toList().firstOrNull { it.id == id } != null
|
||||
fun getChat(id: String): Chat? = chats.toList().firstOrNull { it.id == id }
|
||||
fun getContactChat(contactId: Long): Chat? = chats.toList().firstOrNull { it.chatInfo is ChatInfo.Direct && it.chatInfo.apiId == contactId }
|
||||
fun getGroupChat(groupId: Long): Chat? = chats.toList().firstOrNull { it.chatInfo is ChatInfo.Group && it.chatInfo.apiId == groupId }
|
||||
private fun getChatIndex(id: String): Int = chats.toList().indexOfFirst { it.id == id }
|
||||
fun addChat(chat: Chat) = chats.add(index = 0, chat)
|
||||
|
||||
|
@ -272,7 +274,13 @@ object ChatModel {
|
|||
Log.d(TAG, "TODOCHAT: upsertChatItem: updated in chat $chatId from ${cInfo.id} ${cItem.id}, size ${chatItems.size}")
|
||||
false
|
||||
} else {
|
||||
chatItems.add(cItem)
|
||||
val status = chatItemStatuses.remove(cItem.id)
|
||||
val ci = if (status != null && cItem.meta.itemStatus is CIStatus.SndNew) {
|
||||
cItem.copy(meta = cItem.meta.copy(itemStatus = status))
|
||||
} else {
|
||||
cItem
|
||||
}
|
||||
chatItems.add(ci)
|
||||
Log.d(TAG, "TODOCHAT: upsertChatItem: added to chat $chatId from ${cInfo.id} ${cItem.id}, size ${chatItems.size}")
|
||||
true
|
||||
}
|
||||
|
@ -282,13 +290,15 @@ object ChatModel {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun updateChatItem(cInfo: ChatInfo, cItem: ChatItem) {
|
||||
suspend fun updateChatItem(cInfo: ChatInfo, cItem: ChatItem, status: CIStatus? = null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
if (chatId.value == cInfo.id) {
|
||||
val itemIndex = chatItems.indexOfFirst { it.id == cItem.id }
|
||||
if (itemIndex >= 0) {
|
||||
chatItems[itemIndex] = cItem
|
||||
}
|
||||
} else if (status != null) {
|
||||
chatItemStatuses[cItem.id] = status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -326,6 +336,7 @@ object ChatModel {
|
|||
}
|
||||
// clear current chat
|
||||
if (chatId.value == cInfo.id) {
|
||||
chatItemStatuses.clear()
|
||||
chatItems.clear()
|
||||
}
|
||||
}
|
||||
|
@ -2416,7 +2427,7 @@ sealed class Format {
|
|||
@Serializable @SerialName("secret") class Secret: Format()
|
||||
@Serializable @SerialName("colored") class Colored(val color: FormatColor): Format()
|
||||
@Serializable @SerialName("uri") class Uri: Format()
|
||||
@Serializable @SerialName("simplexLink") class SimplexLink(val linkType: SimplexLinkType, val simplexUri: String, val trustedUri: Boolean, val smpHosts: List<String>): Format()
|
||||
@Serializable @SerialName("simplexLink") class SimplexLink(val linkType: SimplexLinkType, val simplexUri: String, val smpHosts: List<String>): Format()
|
||||
@Serializable @SerialName("email") class Email: Format()
|
||||
@Serializable @SerialName("phone") class Phone: Format()
|
||||
|
||||
|
|
|
@ -863,6 +863,14 @@ object ChatController {
|
|||
return null
|
||||
}
|
||||
|
||||
suspend fun apiConnectPlan(connReq: String): ConnectionPlan? {
|
||||
val userId = kotlin.runCatching { currentUserId("apiConnectPlan") }.getOrElse { return null }
|
||||
val r = sendCmd(CC.APIConnectPlan(userId, connReq))
|
||||
if (r is CR.CRConnectionPlan) return r.connectionPlan
|
||||
Log.e(TAG, "apiConnectPlan bad response: ${r.responseType} ${r.details}")
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiConnect(incognito: Boolean, connReq: String): Boolean {
|
||||
val userId = chatModel.currentUser.value?.userId ?: run {
|
||||
Log.e(TAG, "apiConnect: no current user")
|
||||
|
@ -1544,11 +1552,8 @@ object ChatController {
|
|||
is CR.ChatItemStatusUpdated -> {
|
||||
val cInfo = r.chatItem.chatInfo
|
||||
val cItem = r.chatItem.chatItem
|
||||
if (!cItem.isDeletedContent) {
|
||||
val added = if (active(r.user)) chatModel.upsertChatItem(cInfo, cItem) else true
|
||||
if (added && cItem.showNotification) {
|
||||
ntfManager.notifyMessageReceived(r.user, cInfo, cItem)
|
||||
}
|
||||
if (!cItem.isDeletedContent && active(r.user)) {
|
||||
chatModel.updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus)
|
||||
}
|
||||
}
|
||||
is CR.ChatItemUpdated ->
|
||||
|
@ -1979,6 +1984,7 @@ sealed class CC {
|
|||
class APIVerifyGroupMember(val groupId: Long, val groupMemberId: Long, val connectionCode: String?): CC()
|
||||
class APIAddContact(val userId: Long, val incognito: Boolean): CC()
|
||||
class ApiSetConnectionIncognito(val connId: Long, val incognito: Boolean): CC()
|
||||
class APIConnectPlan(val userId: Long, val connReq: String): CC()
|
||||
class APIConnect(val userId: Long, val incognito: Boolean, val connReq: String): CC()
|
||||
class ApiDeleteChat(val type: ChatType, val id: Long): CC()
|
||||
class ApiClearChat(val type: ChatType, val id: Long): CC()
|
||||
|
@ -2101,6 +2107,7 @@ sealed class CC {
|
|||
is APIVerifyGroupMember -> "/_verify code #$groupId $groupMemberId" + if (connectionCode != null) " $connectionCode" else ""
|
||||
is APIAddContact -> "/_connect $userId incognito=${onOff(incognito)}"
|
||||
is ApiSetConnectionIncognito -> "/_set incognito :$connId ${onOff(incognito)}"
|
||||
is APIConnectPlan -> "/_connect plan $userId $connReq"
|
||||
is APIConnect -> "/_connect $userId incognito=${onOff(incognito)} $connReq"
|
||||
is ApiDeleteChat -> "/_delete ${chatRef(type, id)}"
|
||||
is ApiClearChat -> "/_clear chat ${chatRef(type, id)}"
|
||||
|
@ -2215,6 +2222,7 @@ sealed class CC {
|
|||
is APIVerifyGroupMember -> "apiVerifyGroupMember"
|
||||
is APIAddContact -> "apiAddContact"
|
||||
is ApiSetConnectionIncognito -> "apiSetConnectionIncognito"
|
||||
is APIConnectPlan -> "apiConnectPlan"
|
||||
is APIConnect -> "apiConnect"
|
||||
is ApiDeleteChat -> "apiDeleteChat"
|
||||
is ApiClearChat -> "apiClearChat"
|
||||
|
@ -3458,6 +3466,7 @@ sealed class CR {
|
|||
@Serializable @SerialName("connectionVerified") class ConnectionVerified(val user: UserRef, val verified: Boolean, val expectedCode: String): CR()
|
||||
@Serializable @SerialName("invitation") class Invitation(val user: UserRef, val connReqInvitation: String, val connection: PendingContactConnection): CR()
|
||||
@Serializable @SerialName("connectionIncognitoUpdated") class ConnectionIncognitoUpdated(val user: UserRef, val toConnection: PendingContactConnection): CR()
|
||||
@Serializable @SerialName("connectionPlan") class CRConnectionPlan(val user: UserRef, val connectionPlan: ConnectionPlan): CR()
|
||||
@Serializable @SerialName("sentConfirmation") class SentConfirmation(val user: UserRef): CR()
|
||||
@Serializable @SerialName("sentInvitation") class SentInvitation(val user: UserRef): CR()
|
||||
@Serializable @SerialName("contactAlreadyExists") class ContactAlreadyExists(val user: UserRef, val contact: Contact): CR()
|
||||
|
@ -3607,6 +3616,7 @@ sealed class CR {
|
|||
is ConnectionVerified -> "connectionVerified"
|
||||
is Invitation -> "invitation"
|
||||
is ConnectionIncognitoUpdated -> "connectionIncognitoUpdated"
|
||||
is CRConnectionPlan -> "connectionPlan"
|
||||
is SentConfirmation -> "sentConfirmation"
|
||||
is SentInvitation -> "sentInvitation"
|
||||
is ContactAlreadyExists -> "contactAlreadyExists"
|
||||
|
@ -3748,6 +3758,7 @@ sealed class CR {
|
|||
is ConnectionVerified -> withUser(user, "verified: $verified\nconnectionCode: $expectedCode")
|
||||
is Invitation -> withUser(user, connReqInvitation)
|
||||
is ConnectionIncognitoUpdated -> withUser(user, json.encodeToString(toConnection))
|
||||
is CRConnectionPlan -> withUser(user, json.encodeToString(connectionPlan))
|
||||
is SentConfirmation -> withUser(user, noDetails())
|
||||
is SentInvitation -> withUser(user, noDetails())
|
||||
is ContactAlreadyExists -> withUser(user, json.encodeToString(contact))
|
||||
|
@ -3872,6 +3883,39 @@ fun chatError(r: CR): ChatErrorType? {
|
|||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class ConnectionPlan {
|
||||
@Serializable @SerialName("invitationLink") class InvitationLink(val invitationLinkPlan: InvitationLinkPlan): ConnectionPlan()
|
||||
@Serializable @SerialName("contactAddress") class ContactAddress(val contactAddressPlan: ContactAddressPlan): ConnectionPlan()
|
||||
@Serializable @SerialName("groupLink") class GroupLink(val groupLinkPlan: GroupLinkPlan): ConnectionPlan()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class InvitationLinkPlan {
|
||||
@Serializable @SerialName("ok") object Ok: InvitationLinkPlan()
|
||||
@Serializable @SerialName("ownLink") object OwnLink: InvitationLinkPlan()
|
||||
@Serializable @SerialName("connecting") class Connecting(val contact_: Contact? = null): InvitationLinkPlan()
|
||||
@Serializable @SerialName("known") class Known(val contact: Contact): InvitationLinkPlan()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class ContactAddressPlan {
|
||||
@Serializable @SerialName("ok") object Ok: ContactAddressPlan()
|
||||
@Serializable @SerialName("ownLink") object OwnLink: ContactAddressPlan()
|
||||
@Serializable @SerialName("connectingConfirmReconnect") object ConnectingConfirmReconnect: ContactAddressPlan()
|
||||
@Serializable @SerialName("connectingProhibit") class ConnectingProhibit(val contact: Contact): ContactAddressPlan()
|
||||
@Serializable @SerialName("known") class Known(val contact: Contact): ContactAddressPlan()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class GroupLinkPlan {
|
||||
@Serializable @SerialName("ok") object Ok: GroupLinkPlan()
|
||||
@Serializable @SerialName("ownLink") class OwnLink(val groupInfo: GroupInfo): GroupLinkPlan()
|
||||
@Serializable @SerialName("connectingConfirmReconnect") object ConnectingConfirmReconnect: GroupLinkPlan()
|
||||
@Serializable @SerialName("connectingProhibit") class ConnectingProhibit(val groupInfo_: GroupInfo? = null): GroupLinkPlan()
|
||||
@Serializable @SerialName("known") class Known(val groupInfo: GroupInfo): GroupLinkPlan()
|
||||
}
|
||||
|
||||
abstract class TerminalItem {
|
||||
abstract val id: Long
|
||||
val date: Instant = Clock.System.now()
|
||||
|
@ -4035,6 +4079,7 @@ sealed class ChatErrorType {
|
|||
is ChatNotStarted -> "chatNotStarted"
|
||||
is ChatNotStopped -> "chatNotStopped"
|
||||
is ChatStoreChanged -> "chatStoreChanged"
|
||||
is ConnectionPlanChatError -> "connectionPlan"
|
||||
is InvalidConnReq -> "invalidConnReq"
|
||||
is InvalidChatMessage -> "invalidChatMessage"
|
||||
is ContactNotReady -> "contactNotReady"
|
||||
|
@ -4111,6 +4156,7 @@ sealed class ChatErrorType {
|
|||
@Serializable @SerialName("chatNotStarted") object ChatNotStarted: ChatErrorType()
|
||||
@Serializable @SerialName("chatNotStopped") object ChatNotStopped: ChatErrorType()
|
||||
@Serializable @SerialName("chatStoreChanged") object ChatStoreChanged: ChatErrorType()
|
||||
@Serializable @SerialName("connectionPlan") class ConnectionPlanChatError(val connectionPlan: ConnectionPlan): ChatErrorType()
|
||||
@Serializable @SerialName("invalidConnReq") object InvalidConnReq: ChatErrorType()
|
||||
@Serializable @SerialName("invalidChatMessage") class InvalidChatMessage(val connection: Connection, val message: String): ChatErrorType()
|
||||
@Serializable @SerialName("contactNotReady") class ContactNotReady(val contact: Contact): ChatErrorType()
|
||||
|
|
|
@ -31,10 +31,10 @@ import androidx.compose.ui.unit.sp
|
|||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.newchat.QRCode
|
||||
import chat.simplex.common.views.usersettings.*
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.views.chatlist.updateChatSettings
|
||||
import chat.simplex.common.views.newchat.*
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
@ -309,9 +309,9 @@ fun ChatInfoLayout(
|
|||
|
||||
if (contact.contactLink != null) {
|
||||
SectionView(stringResource(MR.strings.address_section_title).uppercase()) {
|
||||
QRCode(contact.contactLink, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
|
||||
SimpleXLinkQRCode(contact.contactLink, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
|
||||
val clipboard = LocalClipboardManager.current
|
||||
ShareAddressButton { clipboard.shareText(contact.contactLink) }
|
||||
ShareAddressButton { clipboard.shareText(simplexChatLink(contact.contactLink)) }
|
||||
SectionTextFooter(stringResource(MR.strings.you_can_share_this_address_with_your_contacts).format(contact.displayName))
|
||||
}
|
||||
SectionDividerSpaced()
|
||||
|
|
|
@ -19,7 +19,7 @@ import chat.simplex.common.model.*
|
|||
import chat.simplex.common.platform.shareText
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.newchat.QRCode
|
||||
import chat.simplex.common.views.newchat.*
|
||||
import chat.simplex.res.MR
|
||||
|
||||
@Composable
|
||||
|
@ -44,14 +44,12 @@ fun GroupLinkView(chatModel: ChatModel, groupInfo: GroupInfo, connReqContact: St
|
|||
createLink()
|
||||
}
|
||||
}
|
||||
val clipboard = LocalClipboardManager.current
|
||||
GroupLinkLayout(
|
||||
groupLink = groupLink,
|
||||
groupInfo,
|
||||
groupLinkMemberRole,
|
||||
creatingLink,
|
||||
createLink = ::createLink,
|
||||
share = { clipboard.shareText(groupLink ?: return@GroupLinkLayout) },
|
||||
updateLink = {
|
||||
val role = groupLinkMemberRole.value
|
||||
if (role != null) {
|
||||
|
@ -95,7 +93,6 @@ fun GroupLinkLayout(
|
|||
groupLinkMemberRole: MutableState<GroupMemberRole?>,
|
||||
creatingLink: Boolean,
|
||||
createLink: () -> Unit,
|
||||
share: () -> Unit,
|
||||
updateLink: () -> Unit,
|
||||
deleteLink: () -> Unit
|
||||
) {
|
||||
|
@ -125,16 +122,17 @@ fun GroupLinkLayout(
|
|||
}
|
||||
initialLaunch = false
|
||||
}
|
||||
QRCode(groupLink, Modifier.aspectRatio(1f).padding(horizontal = DEFAULT_PADDING))
|
||||
SimpleXLinkQRCode(groupLink, Modifier.aspectRatio(1f).padding(horizontal = DEFAULT_PADDING))
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(horizontal = DEFAULT_PADDING, vertical = 10.dp)
|
||||
) {
|
||||
val clipboard = LocalClipboardManager.current
|
||||
SimpleButton(
|
||||
stringResource(MR.strings.share_link),
|
||||
icon = painterResource(MR.images.ic_share),
|
||||
click = share
|
||||
click = { clipboard.shareText(simplexChatLink(groupLink)) }
|
||||
)
|
||||
SimpleButton(
|
||||
stringResource(MR.strings.delete_link),
|
||||
|
|
|
@ -73,6 +73,7 @@ fun GroupMemberInfoView(
|
|||
chatModel.addChat(c)
|
||||
}
|
||||
chatModel.chatItems.clear()
|
||||
chatModel.chatItemStatuses.clear()
|
||||
chatModel.chatItems.addAll(c.chatItems)
|
||||
chatModel.chatId.value = c.id
|
||||
closeAll()
|
||||
|
@ -283,9 +284,9 @@ fun GroupMemberInfoLayout(
|
|||
|
||||
if (member.contactLink != null) {
|
||||
SectionView(stringResource(MR.strings.address_section_title).uppercase()) {
|
||||
QRCode(member.contactLink, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
|
||||
SimpleXLinkQRCode(member.contactLink, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
|
||||
val clipboard = LocalClipboardManager.current
|
||||
ShareAddressButton { clipboard.shareText(member.contactLink) }
|
||||
ShareAddressButton { clipboard.shareText(simplexChatLink(member.contactLink)) }
|
||||
if (contactId != null) {
|
||||
if (knownDirectChat(contactId) == null && !groupInfo.fullGroupPreferences.directMessages.on) {
|
||||
ConnectViaAddressButton(onClick = { connectViaAddress(member.contactLink) })
|
||||
|
@ -472,43 +473,17 @@ private fun updateMemberRoleDialog(
|
|||
}
|
||||
|
||||
fun connectViaMemberAddressAlert(connReqUri: String) {
|
||||
AlertManager.shared.showAlertDialogButtonsColumn(
|
||||
title = generalGetString(MR.strings.connect_via_member_address_alert_title),
|
||||
text = AnnotatedString(generalGetString(MR.strings.connect_via_member_address_alert_desc)),
|
||||
buttons = {
|
||||
Column {
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
val uri = URI(connReqUri)
|
||||
withUriAction(uri) { linkType ->
|
||||
withApi {
|
||||
Log.d(TAG, "connectViaUri: connecting")
|
||||
connectViaUri(chatModel, linkType, uri, incognito = false)
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
val uri = URI(connReqUri)
|
||||
withUriAction(uri) { linkType ->
|
||||
withApi {
|
||||
Log.d(TAG, "connectViaUri: connecting incognito")
|
||||
connectViaUri(chatModel, linkType, uri, incognito = true)
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
}) {
|
||||
Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
try {
|
||||
val uri = URI(connReqUri)
|
||||
withApi {
|
||||
planAndConnect(chatModel, uri, incognito = null, close = { ModalManager.closeAllModalsEverywhere() })
|
||||
}
|
||||
)
|
||||
} catch (e: RuntimeException) {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.invalid_connection_link),
|
||||
text = generalGetString(MR.strings.this_string_is_not_a_connection_link)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
|
|
|
@ -103,12 +103,11 @@ private fun GroupPreferencesLayout(
|
|||
FeatureSection(GroupFeature.Voice, allowVoice, groupInfo, preferences, onTTLUpdated) {
|
||||
applyPrefs(preferences.copy(voice = GroupPreference(enable = it)))
|
||||
}
|
||||
// TODO uncomment in 5.3
|
||||
// SectionDividerSpaced(true, maxBottomPadding = false)
|
||||
// val allowFiles = remember(preferences) { mutableStateOf(preferences.files.enable) }
|
||||
// FeatureSection(GroupFeature.Files, allowFiles, groupInfo, preferences, onTTLUpdated) {
|
||||
// applyPrefs(preferences.copy(files = GroupPreference(enable = it)))
|
||||
// }
|
||||
SectionDividerSpaced(true, maxBottomPadding = false)
|
||||
val allowFiles = remember(preferences) { mutableStateOf(preferences.files.enable) }
|
||||
FeatureSection(GroupFeature.Files, allowFiles, groupInfo, preferences, onTTLUpdated) {
|
||||
applyPrefs(preferences.copy(files = GroupPreference(enable = it)))
|
||||
}
|
||||
if (groupInfo.canEdit) {
|
||||
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
|
||||
ResetSaveButtons(
|
||||
|
|
|
@ -136,12 +136,8 @@ fun MarkdownText (
|
|||
val link = ft.link(linkMode)
|
||||
if (link != null) {
|
||||
hasLinks = true
|
||||
val ftStyle = if (ft.format is Format.SimplexLink && !ft.format.trustedUri && linkMode == SimplexLinkMode.BROWSER) {
|
||||
SpanStyle(color = Color.Red, textDecoration = TextDecoration.Underline)
|
||||
} else {
|
||||
ft.format.style
|
||||
}
|
||||
withAnnotation(tag = if (ft.format is Format.SimplexLink && linkMode != SimplexLinkMode.BROWSER) "SIMPLEX_URL" else "URL", annotation = link) {
|
||||
val ftStyle = ft.format.style
|
||||
withAnnotation(tag = if (ft.format is Format.SimplexLink) "SIMPLEX_URL" else "URL", annotation = link) {
|
||||
withStyle(ftStyle) { append(ft.viewText(linkMode)) }
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -130,6 +130,13 @@ suspend fun openDirectChat(contactId: Long, chatModel: ChatModel) {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun openGroupChat(groupId: Long, chatModel: ChatModel) {
|
||||
val chat = chatModel.controller.apiGetChat(ChatType.Group, groupId)
|
||||
if (chat != null) {
|
||||
openChat(chat, chatModel)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun openChat(chatInfo: ChatInfo, chatModel: ChatModel) {
|
||||
Log.d(TAG, "TODOCHAT: openChat: opening ${chatInfo.id}, current chatId ${ChatModel.chatId.value}, size ${ChatModel.chatItems.size}")
|
||||
val chat = chatModel.controller.apiGetChat(chatInfo.chatType, chatInfo.apiId)
|
||||
|
@ -141,6 +148,7 @@ suspend fun openChat(chatInfo: ChatInfo, chatModel: ChatModel) {
|
|||
|
||||
suspend fun openChat(chat: Chat, chatModel: ChatModel) {
|
||||
chatModel.chatItems.clear()
|
||||
chatModel.chatItemStatuses.clear()
|
||||
chatModel.chatItems.addAll(chat.chatItems)
|
||||
chatModel.chatId.value = chat.chatInfo.id
|
||||
}
|
||||
|
|
|
@ -316,46 +316,8 @@ fun connectIfOpenedViaUri(uri: URI, chatModel: ChatModel) {
|
|||
if (chatModel.currentUser.value == null) {
|
||||
chatModel.appOpenUrl.value = uri
|
||||
} else {
|
||||
withUriAction(uri) { linkType ->
|
||||
val title = when (linkType) {
|
||||
ConnectionLinkType.CONTACT -> generalGetString(MR.strings.connect_via_contact_link)
|
||||
ConnectionLinkType.INVITATION -> generalGetString(MR.strings.connect_via_invitation_link)
|
||||
ConnectionLinkType.GROUP -> generalGetString(MR.strings.connect_via_group_link)
|
||||
}
|
||||
AlertManager.shared.showAlertDialogButtonsColumn(
|
||||
title = title,
|
||||
text = if (linkType == ConnectionLinkType.GROUP)
|
||||
AnnotatedString(generalGetString(MR.strings.you_will_join_group))
|
||||
else
|
||||
AnnotatedString(generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link)),
|
||||
buttons = {
|
||||
Column {
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
withApi {
|
||||
Log.d(TAG, "connectIfOpenedViaUri: connecting")
|
||||
connectViaUri(chatModel, linkType, uri, incognito = false)
|
||||
}
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
withApi {
|
||||
Log.d(TAG, "connectIfOpenedViaUri: connecting incognito")
|
||||
connectViaUri(chatModel, linkType, uri, incognito = true)
|
||||
}
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
}) {
|
||||
Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
withApi {
|
||||
planAndConnect(chatModel, uri, incognito = null, close = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,6 @@ fun AddContactView(
|
|||
incognitoPref = chatModel.controller.appPrefs.incognito,
|
||||
connReq = connReqInvitation,
|
||||
contactConnection = contactConnection,
|
||||
share = { clipboard.shareText(connReqInvitation) },
|
||||
learnMore = {
|
||||
ModalManager.center.showModal {
|
||||
Column(
|
||||
|
@ -56,7 +55,6 @@ fun AddContactLayout(
|
|||
incognitoPref: SharedPreference<Boolean>,
|
||||
connReq: String,
|
||||
contactConnection: MutableState<PendingContactConnection?>,
|
||||
share: () -> Unit,
|
||||
learnMore: () -> Unit
|
||||
) {
|
||||
val incognito = remember { mutableStateOf(incognitoPref.get()) }
|
||||
|
@ -82,7 +80,7 @@ fun AddContactLayout(
|
|||
|
||||
SectionView(stringResource(MR.strings.one_time_link_short).uppercase()) {
|
||||
if (connReq.isNotEmpty()) {
|
||||
QRCode(
|
||||
SimpleXLinkQRCode(
|
||||
connReq, Modifier
|
||||
.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF)
|
||||
.aspectRatio(1f)
|
||||
|
@ -99,7 +97,7 @@ fun AddContactLayout(
|
|||
}
|
||||
|
||||
IncognitoToggle(incognitoPref, incognito) { ModalManager.start.showModal { IncognitoView() } }
|
||||
ShareLinkButton(share)
|
||||
ShareLinkButton(connReq)
|
||||
OneTimeLinkLearnMoreButton(learnMore)
|
||||
}
|
||||
SectionTextFooter(sharedProfileInfo(chatModel, incognito.value))
|
||||
|
@ -109,11 +107,12 @@ fun AddContactLayout(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun ShareLinkButton(onClick: () -> Unit) {
|
||||
fun ShareLinkButton(connReqInvitation: String) {
|
||||
val clipboard = LocalClipboardManager.current
|
||||
SettingsActionItem(
|
||||
painterResource(MR.images.ic_share),
|
||||
stringResource(MR.strings.share_invitation_link),
|
||||
onClick,
|
||||
click = { clipboard.shareText(simplexChatLink(connReqInvitation)) },
|
||||
iconColor = MaterialTheme.colors.primary,
|
||||
textColor = MaterialTheme.colors.primary,
|
||||
)
|
||||
|
@ -177,7 +176,6 @@ fun PreviewAddContactView() {
|
|||
incognitoPref = SharedPreference({ false }, {}),
|
||||
connReq = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D",
|
||||
contactConnection = mutableStateOf(PendingContactConnection.getSampleData()),
|
||||
share = {},
|
||||
learnMore = {},
|
||||
)
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ fun AddGroupView(chatModel: ChatModel, close: () -> Unit) {
|
|||
if (groupInfo != null) {
|
||||
chatModel.addChat(Chat(chatInfo = ChatInfo.Group(groupInfo), chatItems = listOf()))
|
||||
chatModel.chatItems.clear()
|
||||
chatModel.chatItemStatuses.clear()
|
||||
chatModel.chatId.value = groupInfo.id
|
||||
setGroupMembers(groupInfo, chatModel)
|
||||
close.invoke()
|
||||
|
|
|
@ -131,13 +131,13 @@ private fun ContactConnectionInfoLayout(
|
|||
|
||||
SectionView {
|
||||
if (!connReq.isNullOrEmpty() && contactConnection.initiated) {
|
||||
QRCode(
|
||||
SimpleXLinkQRCode(
|
||||
connReq, Modifier
|
||||
.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF)
|
||||
.aspectRatio(1f)
|
||||
)
|
||||
incognitoEnabled()
|
||||
ShareLinkButton(share)
|
||||
ShareLinkButton(connReq)
|
||||
OneTimeLinkLearnMoreButton(learnMore)
|
||||
} else {
|
||||
incognitoEnabled()
|
||||
|
|
|
@ -53,32 +53,14 @@ fun PasteToConnectLayout(
|
|||
fun connectViaLink(connReqUri: String) {
|
||||
try {
|
||||
val uri = URI(connReqUri)
|
||||
withUriAction(uri) { linkType ->
|
||||
val action = suspend {
|
||||
Log.d(TAG, "connectViaUri: connecting")
|
||||
if (connectViaUri(chatModel, linkType, uri, incognito = incognito.value)) {
|
||||
close()
|
||||
}
|
||||
}
|
||||
if (linkType == ConnectionLinkType.GROUP) {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.connect_via_group_link),
|
||||
text = generalGetString(MR.strings.you_will_join_group),
|
||||
confirmText = if (incognito.value) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
|
||||
onConfirm = { withApi { action() } }
|
||||
)
|
||||
} else action()
|
||||
withApi {
|
||||
planAndConnect(chatModel, uri, incognito = incognito.value, close)
|
||||
}
|
||||
} catch (e: RuntimeException) {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.invalid_connection_link),
|
||||
text = generalGetString(MR.strings.this_string_is_not_a_connection_link)
|
||||
)
|
||||
} catch (e: URISyntaxException) {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.invalid_connection_link),
|
||||
text = generalGetString(MR.strings.this_string_is_not_a_connection_link)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,6 +97,7 @@ fun PasteToConnectLayout(
|
|||
painterResource(MR.images.ic_link),
|
||||
stringResource(MR.strings.connect_button),
|
||||
click = { connectViaLink(connectionLink.value) },
|
||||
disabled = connectionLink.value.isEmpty() || connectionLink.value.trim().contains(" ")
|
||||
)
|
||||
|
||||
IncognitoToggle(incognitoPref, incognito) { ModalManager.start.showModal { IncognitoView() } }
|
||||
|
|
|
@ -19,6 +19,29 @@ import chat.simplex.common.views.helpers.*
|
|||
import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun SimpleXLinkQRCode(
|
||||
connReq: String,
|
||||
modifier: Modifier = Modifier,
|
||||
tintColor: Color = Color(0xff062d56),
|
||||
withLogo: Boolean = true
|
||||
) {
|
||||
QRCode(
|
||||
simplexChatLink(connReq),
|
||||
modifier,
|
||||
tintColor,
|
||||
withLogo
|
||||
)
|
||||
}
|
||||
|
||||
fun simplexChatLink(uri: String): String {
|
||||
return if (uri.startsWith("simplex:/")) {
|
||||
uri.replace("simplex:/", "https://simplex.chat/")
|
||||
} else {
|
||||
uri
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun QRCode(
|
||||
connReq: String,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package chat.simplex.common.views.newchat
|
||||
|
||||
import SectionBottomSpacer
|
||||
import SectionItemView
|
||||
import SectionTextFooter
|
||||
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||
import chat.simplex.common.platform.Log
|
||||
|
@ -10,66 +11,265 @@ import androidx.compose.foundation.verticalScroll
|
|||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.platform.TAG
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.chatlist.openDirectChat
|
||||
import chat.simplex.common.views.chatlist.openGroupChat
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.usersettings.*
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.net.URI
|
||||
|
||||
@Composable
|
||||
expect fun ScanToConnectView(chatModel: ChatModel, close: () -> Unit)
|
||||
|
||||
enum class ConnectionLinkType {
|
||||
CONTACT, INVITATION, GROUP
|
||||
INVITATION, CONTACT, GROUP
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class CReqClientData {
|
||||
@Serializable @SerialName("group") data class Group(val groupLinkId: String): CReqClientData()
|
||||
}
|
||||
|
||||
fun withUriAction(uri: URI, run: suspend (ConnectionLinkType) -> Unit) {
|
||||
val action = uri.path?.drop(1)?.replace("/", "")
|
||||
val data = URI(uri.toString().replaceFirst("#/", "/")).getQueryParameter("data")
|
||||
val type = when {
|
||||
data != null -> {
|
||||
val parsed = runCatching {
|
||||
json.decodeFromString(CReqClientData.serializer(), data)
|
||||
suspend fun planAndConnect(
|
||||
chatModel: ChatModel,
|
||||
uri: URI,
|
||||
incognito: Boolean?,
|
||||
close: (() -> Unit)?
|
||||
) {
|
||||
val connectionPlan = chatModel.controller.apiConnectPlan(uri.toString())
|
||||
if (connectionPlan != null) {
|
||||
when (connectionPlan) {
|
||||
is ConnectionPlan.InvitationLink -> when (connectionPlan.invitationLinkPlan) {
|
||||
InvitationLinkPlan.Ok -> {
|
||||
Log.d(TAG, "planAndConnect, .InvitationLink, .Ok, incognito=$incognito")
|
||||
if (incognito != null) {
|
||||
connectViaUri(chatModel, uri, incognito, connectionPlan, close)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, uri, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_via_invitation_link),
|
||||
text = AnnotatedString(generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link)),
|
||||
connectDestructive = false
|
||||
)
|
||||
}
|
||||
}
|
||||
InvitationLinkPlan.OwnLink -> {
|
||||
Log.d(TAG, "planAndConnect, .InvitationLink, .OwnLink, incognito=$incognito")
|
||||
if (incognito != null) {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
|
||||
text = generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link),
|
||||
confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
|
||||
onConfirm = { withApi { connectViaUri(chatModel, uri, incognito, connectionPlan, close) } },
|
||||
destructive = true,
|
||||
)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, uri, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
|
||||
text = AnnotatedString(generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link)),
|
||||
connectDestructive = true
|
||||
)
|
||||
}
|
||||
}
|
||||
is InvitationLinkPlan.Connecting -> {
|
||||
Log.d(TAG, "planAndConnect, .InvitationLink, .Connecting, incognito=$incognito")
|
||||
val contact = connectionPlan.invitationLinkPlan.contact_
|
||||
if (contact != null) {
|
||||
openKnownContact(chatModel, close, contact)
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.contact_already_exists),
|
||||
String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName)
|
||||
)
|
||||
} else {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.connect_plan_already_connecting),
|
||||
generalGetString(MR.strings.connect_plan_you_are_already_connecting_via_this_one_time_link)
|
||||
)
|
||||
}
|
||||
}
|
||||
is InvitationLinkPlan.Known -> {
|
||||
Log.d(TAG, "planAndConnect, .InvitationLink, .Known, incognito=$incognito")
|
||||
val contact = connectionPlan.invitationLinkPlan.contact
|
||||
openKnownContact(chatModel, close, contact)
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.contact_already_exists),
|
||||
String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName)
|
||||
)
|
||||
}
|
||||
}
|
||||
when {
|
||||
parsed.getOrNull() is CReqClientData.Group -> ConnectionLinkType.GROUP
|
||||
else -> null
|
||||
is ConnectionPlan.ContactAddress -> when (connectionPlan.contactAddressPlan) {
|
||||
ContactAddressPlan.Ok -> {
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .Ok, incognito=$incognito")
|
||||
if (incognito != null) {
|
||||
connectViaUri(chatModel, uri, incognito, connectionPlan, close)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, uri, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_via_contact_link),
|
||||
text = AnnotatedString(generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link)),
|
||||
connectDestructive = false
|
||||
)
|
||||
}
|
||||
}
|
||||
ContactAddressPlan.OwnLink -> {
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .OwnLink, incognito=$incognito")
|
||||
if (incognito != null) {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
|
||||
text = generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address),
|
||||
confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
|
||||
onConfirm = { withApi { connectViaUri(chatModel, uri, incognito, connectionPlan, close) } },
|
||||
destructive = true,
|
||||
)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, uri, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
|
||||
text = AnnotatedString(generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address)),
|
||||
connectDestructive = true
|
||||
)
|
||||
}
|
||||
}
|
||||
ContactAddressPlan.ConnectingConfirmReconnect -> {
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .ConnectingConfirmReconnect, incognito=$incognito")
|
||||
if (incognito != null) {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.connect_plan_repeat_connection_request),
|
||||
text = generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address),
|
||||
confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
|
||||
onConfirm = { withApi { connectViaUri(chatModel, uri, incognito, connectionPlan, close) } },
|
||||
destructive = true,
|
||||
)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, uri, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_plan_repeat_connection_request),
|
||||
text = AnnotatedString(generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address)),
|
||||
connectDestructive = true
|
||||
)
|
||||
}
|
||||
}
|
||||
is ContactAddressPlan.ConnectingProhibit -> {
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .ConnectingProhibit, incognito=$incognito")
|
||||
val contact = connectionPlan.contactAddressPlan.contact
|
||||
openKnownContact(chatModel, close, contact)
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.contact_already_exists),
|
||||
String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName)
|
||||
)
|
||||
}
|
||||
is ContactAddressPlan.Known -> {
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .Known, incognito=$incognito")
|
||||
val contact = connectionPlan.contactAddressPlan.contact
|
||||
openKnownContact(chatModel, close, contact)
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.contact_already_exists),
|
||||
String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName)
|
||||
)
|
||||
}
|
||||
}
|
||||
is ConnectionPlan.GroupLink -> when (connectionPlan.groupLinkPlan) {
|
||||
GroupLinkPlan.Ok -> {
|
||||
Log.d(TAG, "planAndConnect, .GroupLink, .Ok, incognito=$incognito")
|
||||
if (incognito != null) {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.connect_via_group_link),
|
||||
text = generalGetString(MR.strings.you_will_join_group),
|
||||
confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button),
|
||||
onConfirm = { withApi { connectViaUri(chatModel, uri, incognito, connectionPlan, close) } }
|
||||
)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, uri, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_via_group_link),
|
||||
text = AnnotatedString(generalGetString(MR.strings.you_will_join_group)),
|
||||
connectDestructive = false
|
||||
)
|
||||
}
|
||||
}
|
||||
is GroupLinkPlan.OwnLink -> {
|
||||
Log.d(TAG, "planAndConnect, .GroupLink, .OwnLink, incognito=$incognito")
|
||||
val groupInfo = connectionPlan.groupLinkPlan.groupInfo
|
||||
ownGroupLinkConfirmConnect(chatModel, uri, incognito, connectionPlan, groupInfo, close)
|
||||
}
|
||||
GroupLinkPlan.ConnectingConfirmReconnect -> {
|
||||
Log.d(TAG, "planAndConnect, .GroupLink, .ConnectingConfirmReconnect, incognito=$incognito")
|
||||
if (incognito != null) {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.connect_plan_repeat_join_request),
|
||||
text = generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link),
|
||||
confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button),
|
||||
onConfirm = { withApi { connectViaUri(chatModel, uri, incognito, connectionPlan, close) } },
|
||||
destructive = true,
|
||||
)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, uri, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_plan_repeat_join_request),
|
||||
text = AnnotatedString(generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link)),
|
||||
connectDestructive = true
|
||||
)
|
||||
}
|
||||
}
|
||||
is GroupLinkPlan.ConnectingProhibit -> {
|
||||
Log.d(TAG, "planAndConnect, .GroupLink, .ConnectingProhibit, incognito=$incognito")
|
||||
val groupInfo = connectionPlan.groupLinkPlan.groupInfo_
|
||||
if (groupInfo != null) {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.connect_plan_group_already_exists),
|
||||
String.format(generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_vName), groupInfo.displayName)
|
||||
)
|
||||
} else {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.connect_plan_already_joining_the_group),
|
||||
generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link)
|
||||
)
|
||||
}
|
||||
}
|
||||
is GroupLinkPlan.Known -> {
|
||||
Log.d(TAG, "planAndConnect, .GroupLink, .Known, incognito=$incognito")
|
||||
val groupInfo = connectionPlan.groupLinkPlan.groupInfo
|
||||
openKnownGroup(chatModel, close, groupInfo)
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.connect_plan_group_already_exists),
|
||||
String.format(generalGetString(MR.strings.connect_plan_you_are_already_in_group_vName), groupInfo.displayName)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
action == "contact" -> ConnectionLinkType.CONTACT
|
||||
action == "invitation" -> ConnectionLinkType.INVITATION
|
||||
else -> null
|
||||
}
|
||||
if (type != null) {
|
||||
withApi { run(type) }
|
||||
} else {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.invalid_contact_link),
|
||||
text = generalGetString(MR.strings.this_link_is_not_a_valid_connection_link)
|
||||
)
|
||||
Log.d(TAG, "planAndConnect, plan error")
|
||||
if (incognito != null) {
|
||||
connectViaUri(chatModel, uri, incognito, connectionPlan = null, close)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, uri, connectionPlan = null, close,
|
||||
title = generalGetString(MR.strings.connect_plan_connect_via_link),
|
||||
connectDestructive = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun connectViaUri(chatModel: ChatModel, action: ConnectionLinkType, uri: URI, incognito: Boolean): Boolean {
|
||||
suspend fun connectViaUri(
|
||||
chatModel: ChatModel,
|
||||
uri: URI,
|
||||
incognito: Boolean,
|
||||
connectionPlan: ConnectionPlan?,
|
||||
close: (() -> Unit)?
|
||||
): Boolean {
|
||||
val r = chatModel.controller.apiConnect(incognito, uri.toString())
|
||||
val connLinkType = if (connectionPlan != null) planToConnectionLinkType(connectionPlan) else ConnectionLinkType.INVITATION
|
||||
if (r) {
|
||||
close?.invoke()
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.connection_request_sent),
|
||||
text =
|
||||
when (action) {
|
||||
when (connLinkType) {
|
||||
ConnectionLinkType.CONTACT -> generalGetString(MR.strings.you_will_be_connected_when_your_connection_request_is_accepted)
|
||||
ConnectionLinkType.INVITATION -> generalGetString(MR.strings.you_will_be_connected_when_your_contacts_device_is_online)
|
||||
ConnectionLinkType.GROUP -> generalGetString(MR.strings.you_will_be_connected_when_group_host_device_is_online)
|
||||
|
@ -79,6 +279,139 @@ suspend fun connectViaUri(chatModel: ChatModel, action: ConnectionLinkType, uri:
|
|||
return r
|
||||
}
|
||||
|
||||
fun planToConnectionLinkType(connectionPlan: ConnectionPlan): ConnectionLinkType {
|
||||
return when(connectionPlan) {
|
||||
is ConnectionPlan.InvitationLink -> ConnectionLinkType.INVITATION
|
||||
is ConnectionPlan.ContactAddress -> ConnectionLinkType.CONTACT
|
||||
is ConnectionPlan.GroupLink -> ConnectionLinkType.GROUP
|
||||
}
|
||||
}
|
||||
|
||||
fun askCurrentOrIncognitoProfileAlert(
|
||||
chatModel: ChatModel,
|
||||
uri: URI,
|
||||
connectionPlan: ConnectionPlan?,
|
||||
close: (() -> Unit)?,
|
||||
title: String,
|
||||
text: AnnotatedString? = null,
|
||||
connectDestructive: Boolean,
|
||||
) {
|
||||
AlertManager.shared.showAlertDialogButtonsColumn(
|
||||
title = title,
|
||||
text = text,
|
||||
buttons = {
|
||||
Column {
|
||||
val connectColor = if (connectDestructive) MaterialTheme.colors.error else MaterialTheme.colors.primary
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
withApi {
|
||||
connectViaUri(chatModel, uri, incognito = false, connectionPlan, close)
|
||||
}
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = connectColor)
|
||||
}
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
withApi {
|
||||
connectViaUri(chatModel, uri, incognito = true, connectionPlan, close)
|
||||
}
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = connectColor)
|
||||
}
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
}) {
|
||||
Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun openKnownContact(chatModel: ChatModel, close: (() -> Unit)?, contact: Contact) {
|
||||
withApi {
|
||||
val c = chatModel.getContactChat(contact.contactId)
|
||||
if (c != null) {
|
||||
close?.invoke()
|
||||
openDirectChat(contact.contactId, chatModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ownGroupLinkConfirmConnect(
|
||||
chatModel: ChatModel,
|
||||
uri: URI,
|
||||
incognito: Boolean?,
|
||||
connectionPlan: ConnectionPlan?,
|
||||
groupInfo: GroupInfo,
|
||||
close: (() -> Unit)?,
|
||||
) {
|
||||
AlertManager.shared.showAlertDialogButtonsColumn(
|
||||
title = generalGetString(MR.strings.connect_plan_join_your_group),
|
||||
text = AnnotatedString(String.format(generalGetString(MR.strings.connect_plan_this_is_your_link_for_group_vName), groupInfo.displayName)),
|
||||
buttons = {
|
||||
Column {
|
||||
// Open group
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
openKnownGroup(chatModel, close, groupInfo)
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_plan_open_group), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
if (incognito != null) {
|
||||
// Join incognito / Join with current profile
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
withApi {
|
||||
connectViaUri(chatModel, uri, incognito, connectionPlan, close)
|
||||
}
|
||||
}) {
|
||||
Text(
|
||||
if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button),
|
||||
Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Use current profile
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
withApi {
|
||||
connectViaUri(chatModel, uri, incognito = false, connectionPlan, close)
|
||||
}
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error)
|
||||
}
|
||||
// Use new incognito profile
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
withApi {
|
||||
connectViaUri(chatModel, uri, incognito = true, connectionPlan, close)
|
||||
}
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error)
|
||||
}
|
||||
}
|
||||
// Cancel
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
}) {
|
||||
Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun openKnownGroup(chatModel: ChatModel, close: (() -> Unit)?, groupInfo: GroupInfo) {
|
||||
withApi {
|
||||
val g = chatModel.getGroupChat(groupInfo.groupId)
|
||||
if (g != null) {
|
||||
close?.invoke()
|
||||
openGroupChat(groupInfo.groupId, chatModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ConnectContactLayout(
|
||||
chatModel: ChatModel,
|
||||
|
@ -92,21 +425,8 @@ fun ConnectContactLayout(
|
|||
QRCodeScanner { connReqUri ->
|
||||
try {
|
||||
val uri = URI(connReqUri)
|
||||
withUriAction(uri) { linkType ->
|
||||
val action = suspend {
|
||||
Log.d(TAG, "connectViaUri: connecting")
|
||||
if (connectViaUri(ChatModel, linkType, uri, incognito = incognito.value)) {
|
||||
close()
|
||||
}
|
||||
}
|
||||
if (linkType == ConnectionLinkType.GROUP) {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.connect_via_group_link),
|
||||
text = generalGetString(MR.strings.you_will_join_group),
|
||||
confirmText = if (incognito.value) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
|
||||
onConfirm = { withApi { action() } }
|
||||
)
|
||||
} else action()
|
||||
withApi {
|
||||
planAndConnect(chatModel, uri, incognito = incognito.value, close)
|
||||
}
|
||||
} catch (e: RuntimeException) {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
|
|
|
@ -18,7 +18,8 @@ import chat.simplex.common.model.*
|
|||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.newchat.QRCode
|
||||
import chat.simplex.common.views.newchat.SimpleXLinkQRCode
|
||||
import chat.simplex.common.views.newchat.simplexChatLink
|
||||
import chat.simplex.res.MR
|
||||
|
||||
@Composable
|
||||
|
@ -38,7 +39,7 @@ fun CreateSimpleXAddress(m: ChatModel) {
|
|||
sendEmail = { address ->
|
||||
uriHandler.sendEmail(
|
||||
generalGetString(MR.strings.email_invite_subject),
|
||||
generalGetString(MR.strings.email_invite_body).format(address.connReqContact)
|
||||
generalGetString(MR.strings.email_invite_body).format(simplexChatLink(address.connReqContact))
|
||||
)
|
||||
},
|
||||
createAddress = {
|
||||
|
@ -91,8 +92,8 @@ private fun CreateSimpleXAddressLayout(
|
|||
Spacer(Modifier.weight(1f))
|
||||
|
||||
if (userAddress != null) {
|
||||
QRCode(userAddress.connReqContact, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
|
||||
ShareAddressButton { share(userAddress.connReqContact) }
|
||||
SimpleXLinkQRCode(userAddress.connReqContact, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
|
||||
ShareAddressButton { share(simplexChatLink(userAddress.connReqContact)) }
|
||||
Spacer(Modifier.weight(1f))
|
||||
ShareViaEmailButton { sendEmail(userAddress) }
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
|
|
@ -92,9 +92,6 @@ fun PrivacySettingsView(
|
|||
chatModel.simplexLinkMode.value = it
|
||||
})
|
||||
}
|
||||
if (chatModel.simplexLinkMode.value == SimplexLinkMode.BROWSER) {
|
||||
SectionTextFooter(stringResource(MR.strings.simplex_link_mode_browser_warning))
|
||||
}
|
||||
SectionDividerSpaced()
|
||||
|
||||
val currentUser = chatModel.currentUser.value
|
||||
|
@ -185,8 +182,10 @@ fun PrivacySettingsView(
|
|||
|
||||
@Composable
|
||||
private fun SimpleXLinkOptions(simplexLinkModeState: State<SimplexLinkMode>, onSelected: (SimplexLinkMode) -> Unit) {
|
||||
val modeValues = listOf(SimplexLinkMode.DESCRIPTION, SimplexLinkMode.FULL)
|
||||
val pickerValues = modeValues + if (modeValues.contains(simplexLinkModeState.value)) emptyList() else listOf(simplexLinkModeState.value)
|
||||
val values = remember {
|
||||
SimplexLinkMode.values().map {
|
||||
pickerValues.map {
|
||||
when (it) {
|
||||
SimplexLinkMode.DESCRIPTION -> it to generalGetString(MR.strings.simplex_link_mode_description)
|
||||
SimplexLinkMode.FULL -> it to generalGetString(MR.strings.simplex_link_mode_full)
|
||||
|
|
|
@ -24,10 +24,10 @@ import chat.simplex.common.model.*
|
|||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.chat.ShareAddressButton
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.newchat.QRCode
|
||||
import chat.simplex.common.model.ChatModel
|
||||
import chat.simplex.common.model.MsgContent
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.views.newchat.*
|
||||
import chat.simplex.res.MR
|
||||
|
||||
@Composable
|
||||
|
@ -100,7 +100,7 @@ fun UserAddressView(
|
|||
sendEmail = { userAddress ->
|
||||
uriHandler.sendEmail(
|
||||
generalGetString(MR.strings.email_invite_subject),
|
||||
generalGetString(MR.strings.email_invite_body).format(userAddress.connReqContact)
|
||||
generalGetString(MR.strings.email_invite_body).format(simplexChatLink( userAddress.connReqContact))
|
||||
)
|
||||
},
|
||||
setProfileAddress = ::setProfileAddress,
|
||||
|
@ -201,8 +201,8 @@ private fun UserAddressLayout(
|
|||
val autoAcceptState = remember { mutableStateOf(AutoAcceptState(userAddress)) }
|
||||
val autoAcceptStateSaved = remember { mutableStateOf(autoAcceptState.value) }
|
||||
SectionView(stringResource(MR.strings.address_section_title).uppercase()) {
|
||||
QRCode(userAddress.connReqContact, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
|
||||
ShareAddressButton { share(userAddress.connReqContact) }
|
||||
SimpleXLinkQRCode(userAddress.connReqContact, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
|
||||
ShareAddressButton { share(simplexChatLink(userAddress.connReqContact)) }
|
||||
ShareViaEmailButton { sendEmail(userAddress) }
|
||||
ShareWithContactsButton(shareViaProfile, setProfileAddress)
|
||||
AutoAcceptToggle(autoAcceptState) { saveAas(autoAcceptState.value, autoAcceptStateSaved) }
|
||||
|
|
|
@ -4,13 +4,13 @@
|
|||
<string name="thousand_abbreviation">k</string>
|
||||
|
||||
<!-- Connect via Link - MainActivity.kt -->
|
||||
<string name="connect_via_contact_link">Connect via contact link?</string>
|
||||
<string name="connect_via_invitation_link">Connect via invitation link?</string>
|
||||
<string name="connect_via_group_link">Connect via group link?</string>
|
||||
<string name="connect_via_contact_link">Connect via contact address?</string>
|
||||
<string name="connect_via_invitation_link">Connect via one-time link?</string>
|
||||
<string name="connect_via_group_link">Join group?</string>
|
||||
<string name="connect_use_current_profile">Use current profile</string>
|
||||
<string name="connect_use_new_incognito_profile">Use new incognito profile</string>
|
||||
<string name="profile_will_be_sent_to_contact_sending_link">Your profile will be sent to the contact that you received this link from.</string>
|
||||
<string name="you_will_join_group">You will join a group this link refers to and connect to its group members.</string>
|
||||
<string name="you_will_join_group">You will connect to all group members.</string>
|
||||
<string name="connect_via_link_verb">Connect</string>
|
||||
<string name="connect_via_link_incognito">Connect incognito</string>
|
||||
|
||||
|
@ -1600,4 +1600,25 @@
|
|||
<!-- Under development -->
|
||||
<string name="in_developing_title">Coming soon!</string>
|
||||
<string name="in_developing_desc">This feature is not yet supported. Try the next release.</string>
|
||||
|
||||
<!-- Connection plan -->
|
||||
<string name="connect_plan_connect_to_yourself">Connect to yourself?</string>
|
||||
<string name="connect_plan_this_is_your_own_one_time_link">This is your own one-time link!</string>
|
||||
<string name="connect_plan_you_are_already_connecting_to_vName">You are already connecting to %1$s.</string>
|
||||
<string name="connect_plan_already_connecting">Already connecting!</string>
|
||||
<string name="connect_plan_you_are_already_connecting_via_this_one_time_link">You are already connecting via this one-time link!</string>
|
||||
<string name="connect_plan_this_is_your_own_simplex_address">This is your own SimpleX address!</string>
|
||||
<string name="connect_plan_repeat_connection_request">Repeat connection request?</string>
|
||||
<string name="connect_plan_you_have_already_requested_connection_via_this_address">You have already requested connection via this address!</string>
|
||||
<string name="connect_plan_join_your_group">Join your group?</string>
|
||||
<string name="connect_plan_this_is_your_link_for_group_vName">This is your link for group %1$s!</string>
|
||||
<string name="connect_plan_open_group">Open group</string>
|
||||
<string name="connect_plan_repeat_join_request">Repeat join request?</string>
|
||||
<string name="connect_plan_you_are_already_joining_the_group_via_this_link">You are already joining the group via this link!</string>
|
||||
<string name="connect_plan_group_already_exists">Group already exists!</string>
|
||||
<string name="connect_plan_you_are_already_joining_the_group_vName">You are already joining the group %1$s.</string>
|
||||
<string name="connect_plan_already_joining_the_group">Already joining the group!</string>
|
||||
<string name="connect_plan_you_are_already_joining_the_group_via_this_link">You are already joining the group via this link.</string>
|
||||
<string name="connect_plan_you_are_already_in_group_vName">You are already in group %1$s.</string>
|
||||
<string name="connect_plan_connect_via_link">Connect via link?</string>
|
||||
</resources>
|
|
@ -604,11 +604,11 @@ processChatCommand = \case
|
|||
timed_ <- sndContactCITimed live ct itemTTL
|
||||
(msgContainer, quotedItem_) <- prepareMsg fInv_ timed_
|
||||
(msg@SndMessage {sharedMsgId}, _) <- sendDirectContactMessage ct (XMsgNew msgContainer)
|
||||
ci <- saveSndChatItem' user (CDDirectSnd ct) msg (CISndMsgContent mc) ciFile_ quotedItem_ timed_ live
|
||||
case ft_ of
|
||||
Just ft@FileTransferMeta {fileInline = Just IFMSent} ->
|
||||
sendDirectFileInline ct ft sharedMsgId
|
||||
_ -> pure ()
|
||||
ci <- saveSndChatItem' user (CDDirectSnd ct) msg (CISndMsgContent mc) ciFile_ quotedItem_ timed_ live
|
||||
forM_ (timed_ >>= timedDeleteAt') $
|
||||
startProximateTimedItemThread user (ChatRef CTDirect contactId, chatItemId' ci)
|
||||
pure $ CRNewChatItem user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci)
|
||||
|
@ -669,11 +669,11 @@ processChatCommand = \case
|
|||
timed_ <- sndGroupCITimed live gInfo itemTTL
|
||||
(msgContainer, quotedItem_) <- prepareMsg fInv_ timed_ membership
|
||||
(msg@SndMessage {sharedMsgId}, sentToMembers) <- sendGroupMessage user gInfo ms (XMsgNew msgContainer)
|
||||
mapM_ (sendGroupFileInline ms sharedMsgId) ft_
|
||||
ci <- saveSndChatItem' user (CDGroupSnd gInfo) msg (CISndMsgContent mc) ciFile_ quotedItem_ timed_ live
|
||||
withStore' $ \db ->
|
||||
forM_ sentToMembers $ \GroupMember {groupMemberId} ->
|
||||
createGroupSndStatus db (chatItemId' ci) groupMemberId CISSndNew
|
||||
mapM_ (sendGroupFileInline ms sharedMsgId) ft_
|
||||
forM_ (timed_ >>= timedDeleteAt') $
|
||||
startProximateTimedItemThread user (ChatRef CTGroup groupId, chatItemId' ci)
|
||||
pure $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci)
|
||||
|
@ -780,8 +780,9 @@ processChatCommand = \case
|
|||
unzipMaybe3 _ = (Nothing, Nothing, Nothing)
|
||||
APIUpdateChatItem (ChatRef cType chatId) itemId live mc -> withUser $ \user -> withChatLock "updateChatItem" $ case cType of
|
||||
CTDirect -> do
|
||||
(ct@Contact {contactId}, cci) <- withStore $ \db -> (,) <$> getContact db user chatId <*> getDirectChatItem db user chatId itemId
|
||||
ct@Contact {contactId} <- withStore $ \db -> getContact db user chatId
|
||||
assertDirectAllowed user MDSnd ct XMsgUpdate_
|
||||
cci <- withStore $ \db -> getDirectCIWithReactions db user ct itemId
|
||||
case cci of
|
||||
CChatItem SMDSnd ci@ChatItem {meta = CIMeta {itemSharedMsgId, itemTimed, itemLive, editable}, content = ciContent} -> do
|
||||
case (ciContent, itemSharedMsgId, editable) of
|
||||
|
@ -803,7 +804,7 @@ processChatCommand = \case
|
|||
CTGroup -> do
|
||||
Group gInfo@GroupInfo {groupId} ms <- withStore $ \db -> getGroup db user chatId
|
||||
assertUserGroupRole gInfo GRAuthor
|
||||
cci <- withStore $ \db -> getGroupChatItem db user chatId itemId
|
||||
cci <- withStore $ \db -> getGroupCIWithReactions db user gInfo itemId
|
||||
case cci of
|
||||
CChatItem SMDSnd ci@ChatItem {meta = CIMeta {itemSharedMsgId, itemTimed, itemLive, editable}, content = ciContent} -> do
|
||||
case (ciContent, itemSharedMsgId, editable) of
|
||||
|
@ -2423,8 +2424,8 @@ updateCallItemStatus user ct Call {chatItemId} receivedStatus msgId_ = do
|
|||
forM_ aciContent_ $ \aciContent -> updateDirectChatItemView user ct chatItemId aciContent False msgId_
|
||||
|
||||
updateDirectChatItemView :: ChatMonad m => User -> Contact -> ChatItemId -> ACIContent -> Bool -> Maybe MessageId -> m ()
|
||||
updateDirectChatItemView user ct@Contact {contactId} chatItemId (ACIContent msgDir ciContent) live msgId_ = do
|
||||
ci' <- withStore $ \db -> updateDirectChatItem db user contactId chatItemId ciContent live msgId_
|
||||
updateDirectChatItemView user ct chatItemId (ACIContent msgDir ciContent) live msgId_ = do
|
||||
ci' <- withStore $ \db -> updateDirectChatItem db user ct chatItemId ciContent live msgId_
|
||||
toView $ CRChatItemUpdated user (AChatItem SCTDirect msgDir (DirectChat ct) ci')
|
||||
|
||||
callStatusItemContent :: ChatMonad m => User -> Contact -> ChatItemId -> WebRTCCallStatus -> m (Maybe ACIContent)
|
||||
|
@ -4029,7 +4030,8 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||
ci' <- withStore' $ \db -> do
|
||||
when changed $
|
||||
addInitialAndNewCIVersions db (chatItemId' ci) (chatItemTs' ci, oldMC) (brokerTs, mc)
|
||||
updateDirectChatItem' db user contactId ci content live $ Just msgId
|
||||
reactions <- getDirectCIReactions db ct sharedMsgId
|
||||
updateDirectChatItem' db user contactId ci {reactions} content live $ Just msgId
|
||||
toView $ CRChatItemUpdated user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci')
|
||||
startUpdatedTimedItemThread user (ChatRef CTDirect contactId) ci ci'
|
||||
else toView $ CRChatItemNotChanged user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci)
|
||||
|
@ -4167,7 +4169,8 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||
ci' <- withStore' $ \db -> do
|
||||
when changed $
|
||||
addInitialAndNewCIVersions db (chatItemId' ci) (chatItemTs' ci, oldMC) (brokerTs, mc)
|
||||
updateGroupChatItem db user groupId ci content live $ Just msgId
|
||||
reactions <- getGroupCIReactions db gInfo memberId sharedMsgId
|
||||
updateGroupChatItem db user groupId ci {reactions} content live $ Just msgId
|
||||
toView $ CRChatItemUpdated user (AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci')
|
||||
startUpdatedTimedItemThread user (ChatRef CTGroup groupId) ci ci'
|
||||
else toView $ CRChatItemNotChanged user (AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci)
|
||||
|
@ -4972,7 +4975,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||
Just (CChatItem SMDSnd ChatItem {meta = CIMeta {itemId, itemStatus}})
|
||||
| itemStatus == newStatus -> pure ()
|
||||
| otherwise -> do
|
||||
chatItem <- withStore $ \db -> updateDirectChatItemStatus db user contactId itemId newStatus
|
||||
chatItem <- withStore $ \db -> updateDirectChatItemStatus db user ct itemId newStatus
|
||||
toView $ CRChatItemStatusUpdated user (AChatItem SCTDirect SMDSnd (DirectChat ct) chatItem)
|
||||
_ -> pure ()
|
||||
|
||||
|
@ -4995,7 +4998,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||
memStatusCounts <- withStore' (`getGroupSndStatusCounts` itemId)
|
||||
let newStatus = membersGroupItemStatus memStatusCounts
|
||||
when (newStatus /= itemStatus) $ do
|
||||
chatItem <- withStore $ \db -> updateGroupChatItemStatus db user groupId itemId newStatus
|
||||
chatItem <- withStore $ \db -> updateGroupChatItemStatus db user gInfo itemId newStatus
|
||||
toView $ CRChatItemStatusUpdated user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) chatItem)
|
||||
_ -> pure ()
|
||||
|
||||
|
@ -5235,9 +5238,7 @@ deliverMessage conn@Connection {connId} cmEventTag msgBody msgId = do
|
|||
let msgFlags = MsgFlags {notification = hasNotification cmEventTag}
|
||||
agentMsgId <- withAgent $ \a -> sendMessage a (aConnId conn) msgFlags msgBody
|
||||
let sndMsgDelivery = SndMsgDelivery {connId, agentMsgId}
|
||||
withStoreCtx'
|
||||
(Just $ "createSndMsgDelivery, sndMsgDelivery: " <> show sndMsgDelivery <> ", msgId: " <> show msgId <> ", cmEventTag: " <> show cmEventTag <> ", msgDeliveryStatus: MDSSndAgent")
|
||||
$ \db -> createSndMsgDelivery db sndMsgDelivery msgId
|
||||
withStore' $ \db -> createSndMsgDelivery db sndMsgDelivery msgId
|
||||
|
||||
sendGroupMessage :: (MsgEncodingI e, ChatMonad m) => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> m (SndMessage, [GroupMember])
|
||||
sendGroupMessage user GroupInfo {groupId} members chatMsgEvent =
|
||||
|
|
|
@ -1090,7 +1090,7 @@ instance ChatTypeI c => ToJSON (CIDeleted c) where
|
|||
|
||||
data JSONCIDeleted
|
||||
= JCIDDeleted {deletedTs :: Maybe UTCTime, chatType :: ChatType}
|
||||
| JCIBlocked {deletedTs :: Maybe UTCTime}
|
||||
| JCIDBlocked {deletedTs :: Maybe UTCTime}
|
||||
| JCIDModerated {deletedTs :: Maybe UTCTime, byGroupMember :: GroupMember}
|
||||
deriving (Show, Generic)
|
||||
|
||||
|
@ -1104,13 +1104,13 @@ instance ToJSON JSONCIDeleted where
|
|||
jsonCIDeleted :: forall d. ChatTypeI d => CIDeleted d -> JSONCIDeleted
|
||||
jsonCIDeleted = \case
|
||||
CIDeleted ts -> JCIDDeleted ts (toChatType $ chatTypeI @d)
|
||||
CIBlocked ts -> JCIBlocked ts
|
||||
CIBlocked ts -> JCIDBlocked ts
|
||||
CIModerated ts m -> JCIDModerated ts m
|
||||
|
||||
jsonACIDeleted :: JSONCIDeleted -> ACIDeleted
|
||||
jsonACIDeleted = \case
|
||||
JCIDDeleted ts cType -> case aChatType cType of ACT c -> ACIDeleted c $ CIDeleted ts
|
||||
JCIBlocked ts -> ACIDeleted SCTGroup $ CIBlocked ts
|
||||
JCIDBlocked ts -> ACIDeleted SCTGroup $ CIBlocked ts
|
||||
JCIDModerated ts m -> ACIDeleted SCTGroup (CIModerated ts m)
|
||||
|
||||
itemDeletedTs :: CIDeleted d -> Maybe UTCTime
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
|
||||
module Simplex.Chat.Store.Messages
|
||||
( getContactConnIds_,
|
||||
getDirectChatReactions_,
|
||||
|
||||
-- * Message and chat item functions
|
||||
deleteContactCIs,
|
||||
|
@ -68,9 +67,11 @@ module Simplex.Chat.Store.Messages
|
|||
setGroupReaction,
|
||||
getChatItemIdByAgentMsgId,
|
||||
getDirectChatItem,
|
||||
getDirectCIWithReactions,
|
||||
getDirectChatItemBySharedMsgId,
|
||||
getDirectChatItemByAgentMsgId,
|
||||
getGroupChatItem,
|
||||
getGroupCIWithReactions,
|
||||
getGroupChatItemBySharedMsgId,
|
||||
getGroupMemberCIBySharedMsgId,
|
||||
getGroupChatItemByAgentMsgId,
|
||||
|
@ -755,7 +756,7 @@ getGroupChat :: DB.Connection -> User -> Int64 -> ChatPagination -> Maybe String
|
|||
getGroupChat db user groupId pagination search_ = do
|
||||
let search = fromMaybe "" search_
|
||||
g <- getGroupInfo db user groupId
|
||||
liftIO . getGroupChatReactions_ db g =<< case pagination of
|
||||
case pagination of
|
||||
CPLast count -> getGroupChatLast_ db user g count search
|
||||
CPAfter afterId count -> getGroupChatAfter_ db user g afterId count search
|
||||
CPBefore beforeId count -> getGroupChatBefore_ db user g beforeId count search
|
||||
|
@ -764,7 +765,7 @@ getGroupChatLast_ :: DB.Connection -> User -> GroupInfo -> Int -> String -> Exce
|
|||
getGroupChatLast_ db user@User {userId} g@GroupInfo {groupId} count search = do
|
||||
let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False}
|
||||
chatItemIds <- liftIO getGroupChatItemIdsLast_
|
||||
chatItems <- mapM (getGroupChatItem db user groupId) chatItemIds
|
||||
chatItems <- mapM (getGroupCIWithReactions db user g) chatItemIds
|
||||
pure $ Chat (GroupChat g) (reverse chatItems) stats
|
||||
where
|
||||
getGroupChatItemIdsLast_ :: IO [ChatItemId]
|
||||
|
@ -802,7 +803,7 @@ getGroupChatAfter_ db user@User {userId} g@GroupInfo {groupId} afterChatItemId c
|
|||
let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False}
|
||||
afterChatItem <- getGroupChatItem db user groupId afterChatItemId
|
||||
chatItemIds <- liftIO $ getGroupChatItemIdsAfter_ (chatItemTs afterChatItem)
|
||||
chatItems <- mapM (getGroupChatItem db user groupId) chatItemIds
|
||||
chatItems <- mapM (getGroupCIWithReactions db user g) chatItemIds
|
||||
pure $ Chat (GroupChat g) chatItems stats
|
||||
where
|
||||
getGroupChatItemIdsAfter_ :: UTCTime -> IO [ChatItemId]
|
||||
|
@ -825,7 +826,7 @@ getGroupChatBefore_ db user@User {userId} g@GroupInfo {groupId} beforeChatItemId
|
|||
let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False}
|
||||
beforeChatItem <- getGroupChatItem db user groupId beforeChatItemId
|
||||
chatItemIds <- liftIO $ getGroupChatItemIdsBefore_ (chatItemTs beforeChatItem)
|
||||
chatItems <- mapM (getGroupChatItem db user groupId) chatItemIds
|
||||
chatItems <- mapM (getGroupCIWithReactions db user g) chatItemIds
|
||||
pure $ Chat (GroupChat g) (reverse chatItems) stats
|
||||
where
|
||||
getGroupChatItemIdsBefore_ :: UTCTime -> IO [ChatItemId]
|
||||
|
@ -1149,23 +1150,24 @@ getChatItemIdByAgentMsgId db connId msgId =
|
|||
|]
|
||||
(connId, msgId)
|
||||
|
||||
updateDirectChatItemStatus :: forall d. MsgDirectionI d => DB.Connection -> User -> Int64 -> ChatItemId -> CIStatus d -> ExceptT StoreError IO (ChatItem 'CTDirect d)
|
||||
updateDirectChatItemStatus db user@User {userId} contactId itemId itemStatus = do
|
||||
ci <- liftEither . correctDir =<< getDirectChatItem db user contactId itemId
|
||||
updateDirectChatItemStatus :: forall d. MsgDirectionI d => DB.Connection -> User -> Contact -> ChatItemId -> CIStatus d -> ExceptT StoreError IO (ChatItem 'CTDirect d)
|
||||
updateDirectChatItemStatus db user@User {userId} ct@Contact {contactId} itemId itemStatus = do
|
||||
ci <- liftEither . correctDir =<< getDirectCIWithReactions db user ct itemId
|
||||
currentTs <- liftIO getCurrentTime
|
||||
liftIO $ DB.execute db "UPDATE chat_items SET item_status = ?, updated_at = ? WHERE user_id = ? AND contact_id = ? AND chat_item_id = ?" (itemStatus, currentTs, userId, contactId, itemId)
|
||||
pure ci {meta = (meta ci) {itemStatus}}
|
||||
where
|
||||
correctDir :: CChatItem c -> Either StoreError (ChatItem c d)
|
||||
correctDir (CChatItem _ ci) = first SEInternalError $ checkDirection ci
|
||||
|
||||
updateDirectChatItem :: forall d. MsgDirectionI d => DB.Connection -> User -> Int64 -> ChatItemId -> CIContent d -> Bool -> Maybe MessageId -> ExceptT StoreError IO (ChatItem 'CTDirect d)
|
||||
updateDirectChatItem db user contactId itemId newContent live msgId_ = do
|
||||
ci <- liftEither . correctDir =<< getDirectChatItem db user contactId itemId
|
||||
updateDirectChatItem :: MsgDirectionI d => DB.Connection -> User -> Contact -> ChatItemId -> CIContent d -> Bool -> Maybe MessageId -> ExceptT StoreError IO (ChatItem 'CTDirect d)
|
||||
updateDirectChatItem db user ct@Contact {contactId} itemId newContent live msgId_ = do
|
||||
ci <- liftEither . correctDir =<< getDirectCIWithReactions db user ct itemId
|
||||
liftIO $ updateDirectChatItem' db user contactId ci newContent live msgId_
|
||||
where
|
||||
correctDir :: CChatItem c -> Either StoreError (ChatItem c d)
|
||||
correctDir (CChatItem _ ci) = first SEInternalError $ checkDirection ci
|
||||
|
||||
getDirectCIWithReactions :: DB.Connection -> User -> Contact -> ChatItemId -> ExceptT StoreError IO (CChatItem 'CTDirect)
|
||||
getDirectCIWithReactions db user ct@Contact {contactId} itemId =
|
||||
liftIO . directCIWithReactions db ct =<< getDirectChatItem db user contactId itemId
|
||||
|
||||
correctDir :: MsgDirectionI d => CChatItem c -> Either StoreError (ChatItem c d)
|
||||
correctDir (CChatItem _ ci) = first SEInternalError $ checkDirection ci
|
||||
|
||||
updateDirectChatItem' :: forall d. MsgDirectionI d => DB.Connection -> User -> Int64 -> ChatItem 'CTDirect d -> CIContent d -> Bool -> Maybe MessageId -> IO (ChatItem 'CTDirect d)
|
||||
updateDirectChatItem' db User {userId} contactId ci newContent live msgId_ = do
|
||||
|
@ -1303,7 +1305,7 @@ getDirectChatItemIdBySharedMsgId_ db userId contactId sharedMsgId =
|
|||
getDirectChatItem :: DB.Connection -> User -> Int64 -> ChatItemId -> ExceptT StoreError IO (CChatItem 'CTDirect)
|
||||
getDirectChatItem db User {userId} contactId itemId = ExceptT $ do
|
||||
currentTs <- getCurrentTime
|
||||
join <$> firstRow (toDirectChatItem currentTs) (SEChatItemNotFound itemId) getItem
|
||||
firstRow' (toDirectChatItem currentTs) (SEChatItemNotFound itemId) getItem
|
||||
where
|
||||
getItem =
|
||||
DB.query
|
||||
|
@ -1351,17 +1353,26 @@ getDirectChatItemIdByText' db User {userId} contactId msg =
|
|||
|]
|
||||
(userId, contactId, msg <> "%")
|
||||
|
||||
updateGroupChatItemStatus :: forall d. MsgDirectionI d => DB.Connection -> User -> GroupId -> ChatItemId -> CIStatus d -> ExceptT StoreError IO (ChatItem 'CTGroup d)
|
||||
updateGroupChatItemStatus db user@User {userId} groupId itemId itemStatus = do
|
||||
ci <- liftEither . correctDir =<< getGroupChatItem db user groupId itemId
|
||||
updateGroupChatItemStatus :: MsgDirectionI d => DB.Connection -> User -> GroupInfo -> ChatItemId -> CIStatus d -> ExceptT StoreError IO (ChatItem 'CTGroup d)
|
||||
updateGroupChatItemStatus db user@User {userId} g@GroupInfo {groupId} itemId itemStatus = do
|
||||
ci <- liftEither . correctDir =<< getGroupCIWithReactions db user g itemId
|
||||
currentTs <- liftIO getCurrentTime
|
||||
liftIO $ DB.execute db "UPDATE chat_items SET item_status = ?, updated_at = ? WHERE user_id = ? AND group_id = ? AND chat_item_id = ?" (itemStatus, currentTs, userId, groupId, itemId)
|
||||
pure ci {meta = (meta ci) {itemStatus}}
|
||||
where
|
||||
correctDir :: CChatItem c -> Either StoreError (ChatItem c d)
|
||||
correctDir (CChatItem _ ci) = first SEInternalError $ checkDirection ci
|
||||
|
||||
updateGroupChatItem :: forall d. MsgDirectionI d => DB.Connection -> User -> Int64 -> ChatItem 'CTGroup d -> CIContent d -> Bool -> Maybe MessageId -> IO (ChatItem 'CTGroup d)
|
||||
getGroupCIWithReactions :: DB.Connection -> User -> GroupInfo -> ChatItemId -> ExceptT StoreError IO (CChatItem 'CTGroup)
|
||||
getGroupCIWithReactions db user g@GroupInfo {groupId} itemId = do
|
||||
liftIO . groupCIWithReactions db g =<< getGroupChatItem db user groupId itemId
|
||||
|
||||
groupCIWithReactions :: DB.Connection -> GroupInfo -> CChatItem 'CTGroup -> IO (CChatItem 'CTGroup)
|
||||
groupCIWithReactions db g cci@(CChatItem md ci@ChatItem {meta = CIMeta {itemSharedMsgId}}) = case itemSharedMsgId of
|
||||
Just sharedMsgId -> do
|
||||
let GroupMember {memberId} = chatItemMember g ci
|
||||
reactions <- getGroupCIReactions db g memberId sharedMsgId
|
||||
pure $ CChatItem md ci {reactions}
|
||||
Nothing -> pure cci
|
||||
|
||||
updateGroupChatItem :: MsgDirectionI d => DB.Connection -> User -> Int64 -> ChatItem 'CTGroup d -> CIContent d -> Bool -> Maybe MessageId -> IO (ChatItem 'CTGroup d)
|
||||
updateGroupChatItem db user groupId ci newContent live msgId_ = do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
let ci' = updatedChatItem ci newContent live currentTs
|
||||
|
@ -1370,7 +1381,7 @@ updateGroupChatItem db user groupId ci newContent live msgId_ = do
|
|||
|
||||
-- this function assumes that the group item with correct chat direction already exists,
|
||||
-- it should be checked before calling it
|
||||
updateGroupChatItem_ :: forall d. MsgDirectionI d => DB.Connection -> User -> Int64 -> ChatItem 'CTGroup d -> Maybe MessageId -> IO ()
|
||||
updateGroupChatItem_ :: MsgDirectionI d => DB.Connection -> User -> Int64 -> ChatItem 'CTGroup d -> Maybe MessageId -> IO ()
|
||||
updateGroupChatItem_ db User {userId} groupId ChatItem {content, meta} msgId_ = do
|
||||
let CIMeta {itemId, itemText, itemStatus, itemDeleted, itemEdited, itemTimed, itemLive, updatedAt} = meta
|
||||
itemDeleted' = isJust itemDeleted
|
||||
|
@ -1501,7 +1512,7 @@ getGroupChatItemByAgentMsgId db user groupId connId msgId = do
|
|||
getGroupChatItem :: DB.Connection -> User -> Int64 -> ChatItemId -> ExceptT StoreError IO (CChatItem 'CTGroup)
|
||||
getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do
|
||||
currentTs <- getCurrentTime
|
||||
join <$> firstRow (toGroupChatItem currentTs userContactId) (SEChatItemNotFound itemId) getItem
|
||||
firstRow' (toGroupChatItem currentTs userContactId) (SEChatItemNotFound itemId) getItem
|
||||
where
|
||||
getItem =
|
||||
DB.query
|
||||
|
@ -1671,18 +1682,15 @@ getChatItemVersions db itemId = do
|
|||
|
||||
getDirectChatReactions_ :: DB.Connection -> Contact -> Chat 'CTDirect -> IO (Chat 'CTDirect)
|
||||
getDirectChatReactions_ db ct c@Chat {chatItems} = do
|
||||
chatItems' <- forM chatItems $ \(CChatItem md ci@ChatItem {meta = CIMeta {itemSharedMsgId}}) -> do
|
||||
reactions <- maybe (pure []) (getDirectCIReactions db ct) itemSharedMsgId
|
||||
pure $ CChatItem md ci {reactions}
|
||||
chatItems' <- mapM (directCIWithReactions db ct) chatItems
|
||||
pure c {chatItems = chatItems'}
|
||||
|
||||
getGroupChatReactions_ :: DB.Connection -> GroupInfo -> Chat 'CTGroup -> IO (Chat 'CTGroup)
|
||||
getGroupChatReactions_ db g c@Chat {chatItems} = do
|
||||
chatItems' <- forM chatItems $ \(CChatItem md ci@ChatItem {meta = CIMeta {itemSharedMsgId}}) -> do
|
||||
let GroupMember {memberId} = chatItemMember g ci
|
||||
reactions <- maybe (pure []) (getGroupCIReactions db g memberId) itemSharedMsgId
|
||||
directCIWithReactions :: DB.Connection -> Contact -> CChatItem 'CTDirect -> IO (CChatItem 'CTDirect)
|
||||
directCIWithReactions db ct cci@(CChatItem md ci@ChatItem {meta = CIMeta {itemSharedMsgId}}) = case itemSharedMsgId of
|
||||
Just sharedMsgId -> do
|
||||
reactions <- getDirectCIReactions db ct sharedMsgId
|
||||
pure $ CChatItem md ci {reactions}
|
||||
pure c {chatItems = chatItems'}
|
||||
Nothing -> pure cci
|
||||
|
||||
getDirectCIReactions :: DB.Connection -> Contact -> SharedMsgId -> IO [CIReactionCount]
|
||||
getDirectCIReactions db Contact {contactId} itemSharedMsgId =
|
||||
|
|
Loading…
Add table
Reference in a new issue