mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-07-03 17:42:08 +00:00
core, ui: replace map of network statuses with subscription status of current chat (#6353)
* core: subscription status wip
* update
* update
* update
* remove statuses core
* cleanup ios
* comment
* plans
* remove NetworkStatus
* ios wip
* contact sub status
* Revert "contact sub status"
This reverts commit 50cf94beed.
* sub status
* set on connected
* kotlin
* rename
* layout
* member status
* kotlin
* fix chat subscription status
* string
* core: update simplexmq
* client notices
* update simplexmq
* update alert
* update simplexmq
* android/desktop
* formatting
* fix tests
* update plans and API docs
---------
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
This commit is contained in:
@@ -158,7 +158,6 @@ enum ChatCommand: ChatCmdProtocol {
|
||||
case apiGetCallInvitations
|
||||
case apiCallStatus(contact: Contact, callStatus: WebRTCCallStatus)
|
||||
// WebRTC calls /
|
||||
case apiGetNetworkStatuses
|
||||
case apiChatRead(type: ChatType, id: Int64, scope: GroupChatScope?)
|
||||
case apiChatItemsRead(type: ChatType, id: Int64, scope: GroupChatScope?, itemIds: [Int64])
|
||||
case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool)
|
||||
@@ -359,7 +358,6 @@ enum ChatCommand: ChatCmdProtocol {
|
||||
case let .apiEndCall(contact): return "/_call end @\(contact.apiId)"
|
||||
case .apiGetCallInvitations: return "/_call get"
|
||||
case let .apiCallStatus(contact, callStatus): return "/_call status @\(contact.apiId) \(callStatus.rawValue)"
|
||||
case .apiGetNetworkStatuses: return "/_network_statuses"
|
||||
case let .apiChatRead(type, id, scope): return "/_read chat \(ref(type, id, scope: scope))"
|
||||
case let .apiChatItemsRead(type, id, scope, itemIds): return "/_read chat items \(ref(type, id, scope: scope)) \(joinedIds(itemIds))"
|
||||
case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id, scope: nil)) \(onOff(unreadChat))"
|
||||
@@ -534,7 +532,6 @@ enum ChatCommand: ChatCmdProtocol {
|
||||
case .apiEndCall: return "apiEndCall"
|
||||
case .apiGetCallInvitations: return "apiGetCallInvitations"
|
||||
case .apiCallStatus: return "apiCallStatus"
|
||||
case .apiGetNetworkStatuses: return "apiGetNetworkStatuses"
|
||||
case .apiChatRead: return "apiChatRead"
|
||||
case .apiChatItemsRead: return "apiChatItemsRead"
|
||||
case .apiChatUnread: return "apiChatUnread"
|
||||
@@ -793,7 +790,6 @@ enum ChatResponse1: Decodable, ChatAPIResult {
|
||||
case userContactLinkDeleted(user: User)
|
||||
case acceptingContactRequest(user: UserRef, contact: Contact)
|
||||
case contactRequestRejected(user: UserRef, contactRequest: UserContactRequest, contact_: Contact?)
|
||||
case networkStatuses(user_: UserRef?, networkStatuses: [ConnNetworkStatus])
|
||||
case newChatItems(user: UserRef, chatItems: [AChatItem])
|
||||
case groupChatItemsDeleted(user: UserRef, groupInfo: GroupInfo, chatItemIDs: Set<Int64>, byUser: Bool, member_: GroupMember?)
|
||||
case forwardPlan(user: UserRef, chatItemIds: [Int64], forwardConfirmation: ForwardConfirmation?)
|
||||
@@ -837,7 +833,6 @@ enum ChatResponse1: Decodable, ChatAPIResult {
|
||||
case .userContactLinkDeleted: "userContactLinkDeleted"
|
||||
case .acceptingContactRequest: "acceptingContactRequest"
|
||||
case .contactRequestRejected: "contactRequestRejected"
|
||||
case .networkStatuses: "networkStatuses"
|
||||
case .newChatItems: "newChatItems"
|
||||
case .groupChatItemsDeleted: "groupChatItemsDeleted"
|
||||
case .forwardPlan: "forwardPlan"
|
||||
@@ -870,7 +865,6 @@ enum ChatResponse1: Decodable, ChatAPIResult {
|
||||
case .userContactLinkDeleted: return noDetails
|
||||
case let .acceptingContactRequest(u, contact): return withUser(u, String(describing: contact))
|
||||
case let .contactRequestRejected(u, contactRequest, contact_): return withUser(u, "contactRequest: \(String(describing: contactRequest))\ncontact_: \(String(describing: contact_))")
|
||||
case let .networkStatuses(u, statuses): return withUser(u, String(describing: statuses))
|
||||
case let .newChatItems(u, chatItems):
|
||||
let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n")
|
||||
return withUser(u, itemsString)
|
||||
@@ -1059,8 +1053,7 @@ enum ChatEvent: Decodable, ChatAPIResult {
|
||||
case receivedContactRequest(user: UserRef, contactRequest: UserContactRequest, chat_: ChatData?)
|
||||
case contactUpdated(user: UserRef, toContact: Contact)
|
||||
case groupMemberUpdated(user: UserRef, groupInfo: GroupInfo, fromMember: GroupMember, toMember: GroupMember)
|
||||
case networkStatus(networkStatus: NetworkStatus, connections: [String])
|
||||
case networkStatuses(user_: UserRef?, networkStatuses: [ConnNetworkStatus])
|
||||
case subscriptionStatus(subscriptionStatus: SubscriptionStatus, connections: [String])
|
||||
case chatInfoUpdated(user: UserRef, chatInfo: ChatInfo)
|
||||
case newChatItems(user: UserRef, chatItems: [AChatItem])
|
||||
case chatItemsStatusesUpdated(user: UserRef, chatItems: [AChatItem])
|
||||
@@ -1137,8 +1130,7 @@ enum ChatEvent: Decodable, ChatAPIResult {
|
||||
case .receivedContactRequest: "receivedContactRequest"
|
||||
case .contactUpdated: "contactUpdated"
|
||||
case .groupMemberUpdated: "groupMemberUpdated"
|
||||
case .networkStatus: "networkStatus"
|
||||
case .networkStatuses: "networkStatuses"
|
||||
case .subscriptionStatus: "subscriptionStatus"
|
||||
case .chatInfoUpdated: "chatInfoUpdated"
|
||||
case .newChatItems: "newChatItems"
|
||||
case .chatItemsStatusesUpdated: "chatItemsStatusesUpdated"
|
||||
@@ -1210,8 +1202,7 @@ enum ChatEvent: Decodable, ChatAPIResult {
|
||||
case let .receivedContactRequest(u, contactRequest, chat_): return withUser(u, "contactRequest: \(String(describing: contactRequest))\nchat_: \(String(describing: chat_))")
|
||||
case let .contactUpdated(u, toContact): return withUser(u, String(describing: toContact))
|
||||
case let .groupMemberUpdated(u, groupInfo, fromMember, toMember): return withUser(u, "groupInfo: \(groupInfo)\nfromMember: \(fromMember)\ntoMember: \(toMember)")
|
||||
case let .networkStatus(status, conns): return "networkStatus: \(String(describing: status))\nconnections: \(String(describing: conns))"
|
||||
case let .networkStatuses(u, statuses): return withUser(u, String(describing: statuses))
|
||||
case let .subscriptionStatus(status, conns): return "subscriptionStatus: \(String(describing: status))\nconnections: \(String(describing: conns))"
|
||||
case let .chatInfoUpdated(u, chatInfo): return withUser(u, String(describing: chatInfo))
|
||||
case let .newChatItems(u, chatItems):
|
||||
let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n")
|
||||
@@ -1371,38 +1362,6 @@ enum ChatDeleteMode: Codable {
|
||||
}
|
||||
}
|
||||
|
||||
enum NetworkStatus: Decodable, Equatable {
|
||||
case unknown
|
||||
case connected
|
||||
case disconnected
|
||||
case error(connectionError: String)
|
||||
|
||||
var statusString: LocalizedStringKey {
|
||||
switch self {
|
||||
case .connected: "connected"
|
||||
case .error: "error"
|
||||
default: "connecting"
|
||||
}
|
||||
}
|
||||
|
||||
var statusExplanation: LocalizedStringKey {
|
||||
switch self {
|
||||
case .connected: "You are connected to the server used to receive messages from this contact."
|
||||
case let .error(err): "Trying to connect to the server used to receive messages from this contact (error: \(err))."
|
||||
default: "Trying to connect to the server used to receive messages from this contact."
|
||||
}
|
||||
}
|
||||
|
||||
var imageName: String {
|
||||
switch self {
|
||||
case .unknown: "circle.dotted"
|
||||
case .connected: "circle.fill"
|
||||
case .disconnected: "ellipsis.circle.fill"
|
||||
case .error: "exclamationmark.circle.fill"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ForwardConfirmation: Decodable, Hashable {
|
||||
case filesNotAccepted(fileIds: [Int64])
|
||||
case filesInProgress(filesCount: Int)
|
||||
@@ -1410,11 +1369,6 @@ enum ForwardConfirmation: Decodable, Hashable {
|
||||
case filesFailed(filesCount: Int)
|
||||
}
|
||||
|
||||
struct ConnNetworkStatus: Decodable {
|
||||
var agentConnId: String
|
||||
var networkStatus: NetworkStatus
|
||||
}
|
||||
|
||||
struct UserMsgReceiptSettings: Codable {
|
||||
var enable: Bool
|
||||
var clearOverrides: Bool
|
||||
|
||||
@@ -283,29 +283,6 @@ class ChatTagsModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
class NetworkModel: ObservableObject {
|
||||
// map of connections network statuses, key is agent connection id
|
||||
@Published var networkStatuses: Dictionary<String, NetworkStatus> = [:]
|
||||
|
||||
static let shared = NetworkModel()
|
||||
|
||||
private init() { }
|
||||
|
||||
func setContactNetworkStatus(_ contact: Contact, _ status: NetworkStatus) {
|
||||
if let conn = contact.activeConn {
|
||||
networkStatuses[conn.agentConnId] = status
|
||||
}
|
||||
}
|
||||
|
||||
func contactNetworkStatus(_ contact: Contact) -> NetworkStatus {
|
||||
if let conn = contact.activeConn {
|
||||
networkStatuses[conn.agentConnId] ?? .unknown
|
||||
} else {
|
||||
.unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ChatItemWithMenu can depend on previous or next item for it's appearance
|
||||
/// This dummy model is used to force an update of all chat items,
|
||||
/// when they might have changed appearance.
|
||||
@@ -374,6 +351,8 @@ final class ChatModel: ObservableObject {
|
||||
@Published var deletedChats: Set<String> = []
|
||||
// current chat
|
||||
@Published var chatId: String?
|
||||
@Published var chatAgentConnId: String?
|
||||
@Published var chatSubStatus: SubscriptionStatus?
|
||||
@Published var openAroundItemId: ChatItem.ID? = nil
|
||||
@Published var chatToTop: String?
|
||||
@Published var groupMembers: [GMember] = []
|
||||
|
||||
@@ -15,8 +15,6 @@ import SwiftUI
|
||||
|
||||
private var chatController: chat_ctrl?
|
||||
|
||||
private let networkStatusesLock = DispatchQueue(label: "chat.simplex.app.network-statuses.lock")
|
||||
|
||||
enum TerminalItem: Identifiable {
|
||||
case cmd(Date, ChatCommand)
|
||||
case res(Date, ChatAPIResult)
|
||||
@@ -1319,6 +1317,10 @@ func apiCreateUserAddress() async throws -> CreatedConnLink? {
|
||||
let userId = try currentUserId("apiCreateUserAddress")
|
||||
let r: APIResult<ChatResponse1>? = await chatApiSendCmdWithRetry(.apiCreateMyAddress(userId: userId))
|
||||
if case let .result(.userContactLinkCreated(_, connLink)) = r { return connLink }
|
||||
if case let .error(.errorAgent(.NOTICE(server, preset, expires))) = r {
|
||||
showClientNotice(server, preset, expires)
|
||||
return nil
|
||||
}
|
||||
if let r { throw r.unexpected } else { return nil }
|
||||
}
|
||||
|
||||
@@ -1640,7 +1642,6 @@ func acceptContactRequest(incognito: Bool, contactRequestId: Int64, inProgress:
|
||||
} else {
|
||||
ChatModel.shared.replaceChat(contactRequestChatId(contactRequestId), chat)
|
||||
}
|
||||
NetworkModel.shared.setContactNetworkStatus(contact, .connected)
|
||||
inProgress?.wrappedValue = false
|
||||
}
|
||||
if contact.sndReady {
|
||||
@@ -1728,12 +1729,6 @@ func apiCallStatus(_ contact: Contact, _ status: String) async throws {
|
||||
}
|
||||
}
|
||||
|
||||
func apiGetNetworkStatuses() throws -> [ConnNetworkStatus] {
|
||||
let r: ChatResponse1 = try chatSendCmdSync(.apiGetNetworkStatuses)
|
||||
if case let .networkStatuses(_, statuses) = r { return statuses }
|
||||
throw r.unexpected
|
||||
}
|
||||
|
||||
func markChatRead(_ im: ItemsModel, _ chat: Chat) async {
|
||||
do {
|
||||
if chat.chatStats.unreadCount > 0 {
|
||||
@@ -1899,6 +1894,10 @@ func apiUpdateGroup(_ groupId: Int64, _ groupProfile: GroupProfile) async throws
|
||||
func apiCreateGroupLink(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> GroupLink? {
|
||||
let r: APIResult<ChatResponse2>? = await chatApiSendCmdWithRetry(.apiCreateGroupLink(groupId: groupId, memberRole: memberRole))
|
||||
if case let .result(.groupLinkCreated(_, _, groupLink)) = r { return groupLink }
|
||||
if case let .error(.errorAgent(.NOTICE(server, preset, expires))) = r {
|
||||
showClientNotice(server, preset, expires)
|
||||
return nil
|
||||
}
|
||||
if let r { throw r.unexpected } else { return nil }
|
||||
}
|
||||
|
||||
@@ -1955,7 +1954,6 @@ func acceptMemberContact(contactId: Int64, inProgress: Binding<Bool>? = nil) asy
|
||||
if let contact = await apiAcceptMemberContact(contactId: contactId) {
|
||||
await MainActor.run {
|
||||
ChatModel.shared.updateContact(contact)
|
||||
NetworkModel.shared.setContactNetworkStatus(contact, .connected)
|
||||
inProgress?.wrappedValue = false
|
||||
}
|
||||
if contact.sndReady {
|
||||
@@ -2241,7 +2239,6 @@ class ChatReceiver {
|
||||
|
||||
func processReceivedMsg(_ res: ChatEvent) async {
|
||||
let m = ChatModel.shared
|
||||
let n = NetworkModel.shared
|
||||
logger.debug("processReceivedMsg: \(res.responseType)")
|
||||
switch res {
|
||||
case let .contactDeletedByContact(user, contact):
|
||||
@@ -2258,14 +2255,15 @@ func processReceivedMsg(_ res: ChatEvent) async {
|
||||
m.dismissConnReqView(conn.id)
|
||||
m.removeChat(conn.id)
|
||||
}
|
||||
if contact.id == m.chatId, let conn = contact.activeConn {
|
||||
m.chatAgentConnId = conn.agentConnId
|
||||
m.chatSubStatus = .active
|
||||
}
|
||||
}
|
||||
}
|
||||
if contact.directOrUsed {
|
||||
NtfManager.shared.notifyContactConnected(user, contact)
|
||||
}
|
||||
await MainActor.run {
|
||||
n.setContactNetworkStatus(contact, .connected)
|
||||
}
|
||||
case let .contactConnecting(user, contact):
|
||||
if active(user) && contact.directOrUsed {
|
||||
await MainActor.run {
|
||||
@@ -2286,9 +2284,6 @@ func processReceivedMsg(_ res: ChatEvent) async {
|
||||
}
|
||||
}
|
||||
}
|
||||
await MainActor.run {
|
||||
n.setContactNetworkStatus(contact, .connected)
|
||||
}
|
||||
case let .receivedContactRequest(user, contactRequest, chat_):
|
||||
if active(user) {
|
||||
await MainActor.run {
|
||||
@@ -2327,30 +2322,10 @@ func processReceivedMsg(_ res: ChatEvent) async {
|
||||
_ = m.upsertGroupMember(groupInfo, toMember)
|
||||
}
|
||||
}
|
||||
case let .networkStatus(status, connections):
|
||||
// dispatch queue to synchronize access
|
||||
networkStatusesLock.sync {
|
||||
var ns = n.networkStatuses
|
||||
// slow loop is on the background thread
|
||||
for cId in connections {
|
||||
ns[cId] = status
|
||||
}
|
||||
// fast model update is on the main thread
|
||||
DispatchQueue.main.sync {
|
||||
n.networkStatuses = ns
|
||||
}
|
||||
}
|
||||
case let .networkStatuses(_, statuses): ()
|
||||
// dispatch queue to synchronize access
|
||||
networkStatusesLock.sync {
|
||||
var ns = n.networkStatuses
|
||||
// slow loop is on the background thread
|
||||
for s in statuses {
|
||||
ns[s.agentConnId] = s.networkStatus
|
||||
}
|
||||
// fast model update is on the main thread
|
||||
DispatchQueue.main.sync {
|
||||
n.networkStatuses = ns
|
||||
case let .subscriptionStatus(status, connections):
|
||||
if let chatAgentConnId = m.chatAgentConnId, connections.contains(chatAgentConnId) {
|
||||
await MainActor.run {
|
||||
m.chatSubStatus = status
|
||||
}
|
||||
}
|
||||
case let .chatInfoUpdated(user, chatInfo):
|
||||
@@ -2554,11 +2529,6 @@ func processReceivedMsg(_ res: ChatEvent) async {
|
||||
_ = m.upsertGroupMember(groupInfo, member)
|
||||
}
|
||||
}
|
||||
if let contact = memberContact {
|
||||
await MainActor.run {
|
||||
n.setContactNetworkStatus(contact, .connected)
|
||||
}
|
||||
}
|
||||
case let .groupUpdated(user, toGroup):
|
||||
if active(user) {
|
||||
await MainActor.run {
|
||||
@@ -2784,13 +2754,10 @@ func processReceivedMsg(_ res: ChatEvent) async {
|
||||
|
||||
func switchToLocalSession() {
|
||||
let m = ChatModel.shared
|
||||
let n = NetworkModel.shared
|
||||
m.remoteCtrlSession = nil
|
||||
do {
|
||||
m.users = try listUsers()
|
||||
try getUserChatData()
|
||||
let statuses = (try apiGetNetworkStatuses()).map { s in (s.agentConnId, s.networkStatus) }
|
||||
n.networkStatuses = Dictionary(uniqueKeysWithValues: statuses)
|
||||
} catch let error {
|
||||
logger.debug("error updating chat data: \(responseError(error))")
|
||||
}
|
||||
@@ -2898,3 +2865,26 @@ private struct UserResponse: Decodable {
|
||||
var user: User?
|
||||
var error: String?
|
||||
}
|
||||
|
||||
private func showClientNotice(_ server: String, _ preset: Bool, _ expiresAt: Date?) {
|
||||
DispatchQueue.main.async {
|
||||
var message = "Server: \(server).\nConditions of use violation notice received from \(preset ? "preset" : "this") server.\nNo IDs shared, see How it works."
|
||||
if let expiresAt {
|
||||
message += "\n\nNew addresses can be created after \(expiresAt.formatted(date: .abbreviated, time: .shortened))."
|
||||
}
|
||||
showAlert("Not allowed", message: message) {
|
||||
let howItWorks = UIAlertAction(title: NSLocalizedString("How it works", comment: "alert button"), style: .default, handler: { _ in
|
||||
UIApplication.shared.open(contentModerationPostLink)
|
||||
})
|
||||
return preset
|
||||
? [
|
||||
okAlertAction,
|
||||
UIAlertAction(title: NSLocalizedString("Conditions of use", comment: "alert button"), style: .default, handler: { _ in
|
||||
UIApplication.shared.open(conditionsURL)
|
||||
}),
|
||||
howItWorks
|
||||
]
|
||||
: [okAlertAction, howItWorks]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import SimpleXChat
|
||||
struct ChatInfoToolbar: View {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@ObservedObject var chat: Chat
|
||||
var imageSize: CGFloat = 32
|
||||
|
||||
@@ -56,6 +57,13 @@ struct ChatInfoToolbar: View {
|
||||
.padding(.top, -2)
|
||||
}
|
||||
}
|
||||
if let contact = chat.chatInfo.contact,
|
||||
contact.ready && contact.active,
|
||||
let chatSubStatus = m.chatSubStatus,
|
||||
chatSubStatus != .active {
|
||||
SubStatusView(status: chatSubStatus)
|
||||
.padding(.leading, 4)
|
||||
}
|
||||
}
|
||||
.foregroundColor(theme.colors.onBackground)
|
||||
.frame(width: 220)
|
||||
@@ -68,6 +76,30 @@ struct ChatInfoToolbar: View {
|
||||
.baselineOffset(1)
|
||||
.kerning(-2)
|
||||
}
|
||||
|
||||
struct SubStatusView: View {
|
||||
@Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
var status: SubscriptionStatus
|
||||
|
||||
var body: some View {
|
||||
switch status {
|
||||
case .active: EmptyView()
|
||||
case .pending: ProgressView()
|
||||
case .removed: subStatusError()
|
||||
case .noSub: subStatusError()
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func subStatusError() -> some View {
|
||||
let dynamicChatInfoSize = dynamicSize(userFont).chatInfoSize
|
||||
Image(systemName: "exclamationmark.circle")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: dynamicChatInfoSize, height: dynamicChatInfoSize)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatInfoToolbar_Previews: PreviewProvider {
|
||||
|
||||
@@ -92,7 +92,6 @@ struct ChatInfoView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
@ObservedObject var networkModel = NetworkModel.shared
|
||||
@ObservedObject var chat: Chat
|
||||
@State var contact: Contact
|
||||
@State var localAlias: String
|
||||
@@ -115,7 +114,7 @@ struct ChatInfoView: View {
|
||||
|
||||
enum ChatInfoViewAlert: Identifiable {
|
||||
case clearChatAlert
|
||||
case networkStatusAlert
|
||||
case subStatusAlert(status: SubscriptionStatus)
|
||||
case switchAddressAlert
|
||||
case abortSwitchAddressAlert
|
||||
case syncConnectionForceAlert
|
||||
@@ -126,7 +125,7 @@ struct ChatInfoView: View {
|
||||
var id: String {
|
||||
switch self {
|
||||
case .clearChatAlert: return "clearChatAlert"
|
||||
case .networkStatusAlert: return "networkStatusAlert"
|
||||
case let .subStatusAlert(status): return "subStatusAlert \(status)"
|
||||
case .switchAddressAlert: return "switchAddressAlert"
|
||||
case .abortSwitchAddressAlert: return "abortSwitchAddressAlert"
|
||||
case .syncConnectionForceAlert: return "syncConnectionForceAlert"
|
||||
@@ -236,10 +235,12 @@ struct ChatInfoView: View {
|
||||
|
||||
if contact.ready && contact.active {
|
||||
Section(header: Text("Servers").foregroundColor(theme.colors.secondary)) {
|
||||
networkStatusRow()
|
||||
.onTapGesture {
|
||||
alert = .networkStatusAlert
|
||||
}
|
||||
if let chatSubStatus = chatModel.chatSubStatus {
|
||||
SubStatusRow(status: chatSubStatus)
|
||||
.onTapGesture {
|
||||
alert = .subStatusAlert(status: chatSubStatus)
|
||||
}
|
||||
}
|
||||
if let connStats = connectionStats {
|
||||
Button("Change receiving address") {
|
||||
alert = .switchAddressAlert
|
||||
@@ -325,7 +326,7 @@ struct ChatInfoView: View {
|
||||
.alert(item: $alert) { alertItem in
|
||||
switch(alertItem) {
|
||||
case .clearChatAlert: return clearChatAlert()
|
||||
case .networkStatusAlert: return networkStatusAlert()
|
||||
case let .subStatusAlert(status): return subStatusAlert(status)
|
||||
case .switchAddressAlert: return switchAddressAlert(switchContactAddress)
|
||||
case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchContactAddress)
|
||||
case .syncConnectionForceAlert:
|
||||
@@ -546,26 +547,6 @@ struct ChatInfoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func networkStatusRow() -> some View {
|
||||
HStack {
|
||||
Text("Network status")
|
||||
Image(systemName: "info.circle")
|
||||
.foregroundColor(theme.colors.primary)
|
||||
.font(.system(size: 14))
|
||||
Spacer()
|
||||
Text(networkModel.contactNetworkStatus(contact).statusString)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
serverImage()
|
||||
}
|
||||
}
|
||||
|
||||
private func serverImage() -> some View {
|
||||
let status = networkModel.contactNetworkStatus(contact)
|
||||
return Image(systemName: status.imageName)
|
||||
.foregroundColor(status == .connected ? .green : theme.colors.secondary)
|
||||
.font(.system(size: 12))
|
||||
}
|
||||
|
||||
private func deleteContactButton() -> some View {
|
||||
Button(role: .destructive) {
|
||||
deleteContactDialog(
|
||||
@@ -605,10 +586,10 @@ struct ChatInfoView: View {
|
||||
)
|
||||
}
|
||||
|
||||
private func networkStatusAlert() -> Alert {
|
||||
private func subStatusAlert(_ status: SubscriptionStatus) -> Alert {
|
||||
Alert(
|
||||
title: Text("Network status"),
|
||||
message: Text(networkModel.contactNetworkStatus(contact).statusExplanation)
|
||||
message: Text(status.statusExplanation)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -667,6 +648,30 @@ struct ChatInfoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct SubStatusRow: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
var status: SubscriptionStatus
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Text("Network status")
|
||||
Image(systemName: "info.circle")
|
||||
.foregroundColor(theme.colors.primary)
|
||||
.font(.system(size: 14))
|
||||
Spacer()
|
||||
Text(status.statusString)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
serverImage(status)
|
||||
}
|
||||
}
|
||||
|
||||
private func serverImage(_ status: SubscriptionStatus) -> some View {
|
||||
return Image(systemName: status.imageName)
|
||||
.foregroundColor(status == .active ? .green : theme.colors.secondary)
|
||||
.font(.system(size: 12))
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatTTLOption: View {
|
||||
@ObservedObject var chat: Chat
|
||||
@Binding var progressIndicator: Bool
|
||||
|
||||
@@ -393,6 +393,8 @@ struct ChatView: View {
|
||||
if chatModel.chatId == cInfo.id && !presentationMode.wrappedValue.isPresented {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
|
||||
if chatModel.chatId == nil {
|
||||
chatModel.chatAgentConnId = nil
|
||||
chatModel.chatSubStatus = nil
|
||||
im.reversedChatItems = []
|
||||
im.chatState.clear()
|
||||
chatModel.groupMembers = []
|
||||
@@ -657,18 +659,30 @@ struct ChatView: View {
|
||||
private func initChatView() {
|
||||
let cInfo = chat.chatInfo
|
||||
// This check prevents the call to apiContactInfo after the app is suspended, and the database is closed.
|
||||
if case .active = scenePhase,
|
||||
case let .direct(contact) = cInfo {
|
||||
Task {
|
||||
do {
|
||||
let (stats, _) = try await apiContactInfo(chat.chatInfo.apiId)
|
||||
await MainActor.run {
|
||||
if let s = stats {
|
||||
chatModel.updateContactConnectionStats(contact, s)
|
||||
if case .active = scenePhase {
|
||||
if case let .direct(contact) = cInfo {
|
||||
Task {
|
||||
do {
|
||||
let (stats, _) = try await apiContactInfo(chat.chatInfo.apiId)
|
||||
await MainActor.run {
|
||||
if let s = stats {
|
||||
chatModel.updateContactConnectionStats(contact, s)
|
||||
if let conn = contact.activeConn {
|
||||
chatModel.chatAgentConnId = conn.agentConnId
|
||||
chatModel.chatSubStatus = s.subStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("apiContactInfo error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Task {
|
||||
await MainActor.run {
|
||||
chatModel.chatAgentConnId = nil
|
||||
chatModel.chatSubStatus = nil
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("apiContactInfo error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -788,7 +788,6 @@ struct ComposeView: View {
|
||||
await MainActor.run {
|
||||
self.chatModel.updateContact(contact)
|
||||
clearState()
|
||||
NetworkModel.shared.setContactNetworkStatus(contact, .connected)
|
||||
}
|
||||
} else {
|
||||
AlertManager.shared.showAlertMsg(title: "Empty message!")
|
||||
@@ -827,7 +826,6 @@ struct ComposeView: View {
|
||||
if let contact = await apiConnectPreparedContact(contactId: chat.chatInfo.apiId, incognito: incognito, msg: mc) {
|
||||
await MainActor.run {
|
||||
self.chatModel.updateContact(contact)
|
||||
NetworkModel.shared.setContactNetworkStatus(contact, .connected)
|
||||
clearState()
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -160,7 +160,15 @@ struct GroupMemberInfoView: View {
|
||||
|
||||
if let connStats = connectionStats {
|
||||
Section(header: Text("Servers").foregroundColor(theme.colors.secondary)) {
|
||||
// TODO network connection status
|
||||
if let subStatus = connStats.subStatus {
|
||||
SubStatusRow(status: subStatus)
|
||||
.onTapGesture {
|
||||
showAlert(
|
||||
NSLocalizedString("Network status", comment: "alert title"),
|
||||
message: subStatus.statusExplanation
|
||||
)
|
||||
}
|
||||
}
|
||||
Button("Change receiving address") {
|
||||
alert = .switchAddressAlert
|
||||
}
|
||||
@@ -396,7 +404,6 @@ struct GroupMemberInfoView: View {
|
||||
ItemsModel.shared.loadOpenChat(memberContact.id) {
|
||||
dismissAllSheets(animated: true)
|
||||
}
|
||||
NetworkModel.shared.setContactNetworkStatus(memberContact, .connected)
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("createMemberContactButton apiCreateMemberContact error: \(responseError(error))")
|
||||
|
||||
@@ -460,12 +460,6 @@ struct ChatPreviewView: View {
|
||||
@ViewBuilder private func chatStatusImage() -> some View {
|
||||
let size = dynamicSize(userFont).incognitoSize
|
||||
switch chat.chatInfo {
|
||||
case let .direct(contact):
|
||||
if contact.active, let status = contact.activeConn?.connStatus, status == .ready || status == .sndReady {
|
||||
NetworkStatusView(contact: contact, size: size)
|
||||
} else {
|
||||
incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size)
|
||||
}
|
||||
case .group:
|
||||
if progressByTimeout {
|
||||
ProgressView()
|
||||
@@ -482,30 +476,6 @@ struct ChatPreviewView: View {
|
||||
incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size)
|
||||
}
|
||||
}
|
||||
|
||||
struct NetworkStatusView: View {
|
||||
@Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@ObservedObject var networkModel = NetworkModel.shared
|
||||
|
||||
let contact: Contact
|
||||
let size: CGFloat
|
||||
|
||||
var body: some View {
|
||||
let dynamicChatInfoSize = dynamicSize(userFont).chatInfoSize
|
||||
switch (networkModel.contactNetworkStatus(contact)) {
|
||||
case .connected: incognitoIcon(contact.contactConnIncognito, theme.colors.secondary, size: size)
|
||||
case .error:
|
||||
Image(systemName: "exclamationmark.circle")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: dynamicChatInfoSize, height: dynamicChatInfoSize)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
default:
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder func incognitoIcon(_ incognito: Bool, _ secondaryColor: Color, size: CGFloat) -> some View {
|
||||
|
||||
@@ -196,12 +196,13 @@ struct UserAddressView: View {
|
||||
progressIndicator = true
|
||||
Task {
|
||||
do {
|
||||
if let connLinkContact = try await apiCreateUserAddress() {
|
||||
DispatchQueue.main.async {
|
||||
let connLinkContact = try await apiCreateUserAddress()
|
||||
DispatchQueue.main.async {
|
||||
if let connLinkContact {
|
||||
chatModel.userAddress = UserContactLink(connLinkContact)
|
||||
alert = .shareOnCreate
|
||||
progressIndicator = false
|
||||
}
|
||||
progressIndicator = false
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("UserAddressView apiCreateUserAddress: \(responseError(error))")
|
||||
|
||||
@@ -178,8 +178,8 @@
|
||||
64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; };
|
||||
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; };
|
||||
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; };
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.6.0-1uYs2FKSnCtGti4vmlXmvH-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.6.0-1uYs2FKSnCtGti4vmlXmvH-ghc9.6.3.a */; };
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.6.0-1uYs2FKSnCtGti4vmlXmvH.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.6.0-1uYs2FKSnCtGti4vmlXmvH.a */; };
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.2-6qgCrd2M1igHbyytG3wCNC-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.2-6qgCrd2M1igHbyytG3wCNC-ghc9.6.3.a */; };
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.2-6qgCrd2M1igHbyytG3wCNC.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.2-6qgCrd2M1igHbyytG3wCNC.a */; };
|
||||
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; };
|
||||
64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; };
|
||||
64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; };
|
||||
@@ -545,8 +545,8 @@
|
||||
64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = "<group>"; };
|
||||
64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.6.0-1uYs2FKSnCtGti4vmlXmvH-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.6.0-1uYs2FKSnCtGti4vmlXmvH-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.6.0-1uYs2FKSnCtGti4vmlXmvH.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.6.0-1uYs2FKSnCtGti4vmlXmvH.a"; sourceTree = "<group>"; };
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.2-6qgCrd2M1igHbyytG3wCNC-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.2-6qgCrd2M1igHbyytG3wCNC-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.2-6qgCrd2M1igHbyytG3wCNC.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.2-6qgCrd2M1igHbyytG3wCNC.a"; sourceTree = "<group>"; };
|
||||
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = "<group>"; };
|
||||
64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = "<group>"; };
|
||||
@@ -708,8 +708,8 @@
|
||||
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */,
|
||||
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */,
|
||||
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */,
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.6.0-1uYs2FKSnCtGti4vmlXmvH-ghc9.6.3.a in Frameworks */,
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.6.0-1uYs2FKSnCtGti4vmlXmvH.a in Frameworks */,
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.2-6qgCrd2M1igHbyytG3wCNC-ghc9.6.3.a in Frameworks */,
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.2-6qgCrd2M1igHbyytG3wCNC.a in Frameworks */,
|
||||
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -795,8 +795,8 @@
|
||||
64C829992D54AEEE006B9E89 /* libffi.a */,
|
||||
64C829982D54AEED006B9E89 /* libgmp.a */,
|
||||
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */,
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.6.0-1uYs2FKSnCtGti4vmlXmvH-ghc9.6.3.a */,
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.6.0-1uYs2FKSnCtGti4vmlXmvH.a */,
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.2-6qgCrd2M1igHbyytG3wCNC-ghc9.6.3.a */,
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.2-6qgCrd2M1igHbyytG3wCNC.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
|
||||
@@ -545,6 +545,7 @@ public struct ConnectionStats: Decodable, Hashable {
|
||||
public var sndQueuesInfo: [SndQueueInfo]
|
||||
public var ratchetSyncState: RatchetSyncState
|
||||
public var ratchetSyncSupported: Bool
|
||||
public var subStatus: SubscriptionStatus?
|
||||
|
||||
public var ratchetSyncAllowed: Bool {
|
||||
ratchetSyncSupported && [.allowed, .required].contains(ratchetSyncState)
|
||||
@@ -559,25 +560,28 @@ public struct ConnectionStats: Decodable, Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct RcvQueueInfo: Codable, Hashable {
|
||||
public struct RcvQueueInfo: Decodable, Hashable {
|
||||
public var rcvServer: String
|
||||
public var status: QueueStatus
|
||||
public var rcvSwitchStatus: RcvSwitchStatus?
|
||||
public var canAbortSwitch: Bool
|
||||
public var subStatus: SubscriptionStatus
|
||||
}
|
||||
|
||||
public enum RcvSwitchStatus: String, Codable, Hashable {
|
||||
public enum RcvSwitchStatus: String, Decodable, Hashable {
|
||||
case switchStarted = "switch_started"
|
||||
case sendingQADD = "sending_qadd"
|
||||
case sendingQUSE = "sending_quse"
|
||||
case receivedMessage = "received_message"
|
||||
}
|
||||
|
||||
public struct SndQueueInfo: Codable, Hashable {
|
||||
public struct SndQueueInfo: Decodable, Hashable {
|
||||
public var sndServer: String
|
||||
public var status: QueueStatus
|
||||
public var sndSwitchStatus: SndSwitchStatus?
|
||||
}
|
||||
|
||||
public enum SndSwitchStatus: String, Codable, Hashable {
|
||||
public enum SndSwitchStatus: String, Decodable, Hashable {
|
||||
case sendingQKEY = "sending_qkey"
|
||||
case sendingQTEST = "sending_qtest"
|
||||
}
|
||||
@@ -606,6 +610,48 @@ public enum RatchetSyncState: String, Decodable {
|
||||
case agreed
|
||||
}
|
||||
|
||||
public enum QueueStatus: String, Decodable, Hashable {
|
||||
case new
|
||||
case confirmed
|
||||
case secured
|
||||
case active
|
||||
case disabled
|
||||
}
|
||||
|
||||
public enum SubscriptionStatus: Decodable, Hashable {
|
||||
case active
|
||||
case pending
|
||||
case removed(subError: String)
|
||||
case noSub
|
||||
|
||||
public var statusString: LocalizedStringKey {
|
||||
switch self {
|
||||
case .active: "connected"
|
||||
case .pending: "connecting"
|
||||
case .removed: "error"
|
||||
case .noSub: "no subscription"
|
||||
}
|
||||
}
|
||||
|
||||
public var statusExplanation: String {
|
||||
switch self {
|
||||
case .active: NSLocalizedString("You are connected to the server used to receive messages from this connection.", comment: "subscription status explanation")
|
||||
case .pending: NSLocalizedString("Trying to connect to the server used to receive messages from this connection.", comment: "subscription status explanation")
|
||||
case let .removed(err): String.localizedStringWithFormat(NSLocalizedString("Error connecting to the server used to receive messages from this connection: %@", comment: "subscription status explanation"), err)
|
||||
case .noSub: NSLocalizedString("You are not connected to the server used to receive messages from this connection (no subscription).", comment: "subscription status explanation")
|
||||
}
|
||||
}
|
||||
|
||||
public var imageName: String {
|
||||
switch self {
|
||||
case .active: "circle.fill"
|
||||
case .pending: "ellipsis.circle.fill"
|
||||
case .removed: "exclamationmark.circle.fill"
|
||||
case .noSub: "circle.dotted"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public protocol SelectableItem: Identifiable, Equatable {
|
||||
var label: LocalizedStringKey { get }
|
||||
static var values: [Self] { get }
|
||||
@@ -833,6 +879,7 @@ public enum AgentErrorType: Decodable, Hashable {
|
||||
case RCP(rcpErr: RCErrorType)
|
||||
case BROKER(brokerAddress: String, brokerErr: BrokerErrorType)
|
||||
case AGENT(agentErr: SMPAgentError)
|
||||
case NOTICE(server: String, preset: Bool, expiresAt: Date?)
|
||||
case INTERNAL(internalErr: String)
|
||||
case CRITICAL(offerRestart: Bool, criticalErr: String)
|
||||
case INACTIVE
|
||||
|
||||
+2
-40
@@ -96,11 +96,12 @@ object ChatModel {
|
||||
val dbMigrationInProgress = mutableStateOf(false)
|
||||
val incompleteInitializedDbRemoved = mutableStateOf(false)
|
||||
// map of connections network statuses, key is agent connection id
|
||||
val networkStatuses = mutableStateMapOf<String, NetworkStatus>()
|
||||
val switchingUsersAndHosts = mutableStateOf(false)
|
||||
|
||||
// current chat
|
||||
val chatId = mutableStateOf<String?>(null)
|
||||
val chatAgentConnId = mutableStateOf<String?>(null)
|
||||
val chatSubStatus = mutableStateOf<SubscriptionStatus?>(null)
|
||||
val openAroundItemId: MutableState<Long?> = mutableStateOf(null)
|
||||
val chatsContext = ChatsContext(null)
|
||||
val secondaryChatsContext = mutableStateOf<ChatsContext?>(null)
|
||||
@@ -1147,21 +1148,6 @@ object ChatModel {
|
||||
showingInvitation.value = showingInvitation.value?.copy(connChatUsed = true)
|
||||
}
|
||||
|
||||
fun setContactNetworkStatus(contact: Contact, status: NetworkStatus) {
|
||||
val conn = contact.activeConn
|
||||
if (conn != null) {
|
||||
networkStatuses[conn.agentConnId] = status
|
||||
}
|
||||
}
|
||||
|
||||
fun contactNetworkStatus(contact: Contact): NetworkStatus {
|
||||
val conn = contact.activeConn
|
||||
return if (conn != null)
|
||||
networkStatuses[conn.agentConnId] ?: NetworkStatus.Unknown()
|
||||
else
|
||||
NetworkStatus.Unknown()
|
||||
}
|
||||
|
||||
fun addTerminalItem(item: TerminalItem) {
|
||||
val maxItems = if (appPreferences.developerTools.get()) 500 else 200
|
||||
if (terminalsVisible.isNotEmpty()) {
|
||||
@@ -1721,30 +1707,6 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class NetworkStatus {
|
||||
val statusString: String get() =
|
||||
when (this) {
|
||||
is Connected -> generalGetString(MR.strings.server_connected)
|
||||
is Error -> generalGetString(MR.strings.server_error)
|
||||
else -> generalGetString(MR.strings.server_connecting)
|
||||
}
|
||||
val statusExplanation: String get() =
|
||||
when (this) {
|
||||
is Connected -> generalGetString(MR.strings.connected_to_server_to_receive_messages_from_contact)
|
||||
is Error -> String.format(generalGetString(MR.strings.trying_to_connect_to_server_to_receive_messages_with_error), connectionError)
|
||||
else -> generalGetString(MR.strings.trying_to_connect_to_server_to_receive_messages)
|
||||
}
|
||||
|
||||
@Serializable @SerialName("unknown") class Unknown: NetworkStatus()
|
||||
@Serializable @SerialName("connected") class Connected: NetworkStatus()
|
||||
@Serializable @SerialName("disconnected") class Disconnected: NetworkStatus()
|
||||
@Serializable @SerialName("error") class Error(val connectionError: String): NetworkStatus()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ConnNetworkStatus(val agentConnId: String, val networkStatus: NetworkStatus)
|
||||
|
||||
@Serializable
|
||||
data class Contact(
|
||||
val contactId: Long,
|
||||
|
||||
+103
-61
@@ -13,6 +13,7 @@ import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -27,12 +28,14 @@ import dev.icerock.moko.resources.compose.painterResource
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.call.*
|
||||
import chat.simplex.common.views.chat.item.contentModerationPostLink
|
||||
import chat.simplex.common.views.chat.item.showContentBlockedAlert
|
||||
import chat.simplex.common.views.chat.item.showQuotedItemDoesNotExistAlert
|
||||
import chat.simplex.common.views.chatlist.openGroupChat
|
||||
import chat.simplex.common.views.migration.MigrationFileLinkData
|
||||
import chat.simplex.common.views.onboarding.OnboardingStage
|
||||
import chat.simplex.common.views.usersettings.*
|
||||
import chat.simplex.common.views.usersettings.networkAndServers.defaultConditionsLink
|
||||
import chat.simplex.common.views.usersettings.networkAndServers.serverHostname
|
||||
import com.charleskorn.kaml.Yaml
|
||||
import com.charleskorn.kaml.YamlConfiguration
|
||||
@@ -46,12 +49,17 @@ import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toJavaLocalDateTime
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.builtins.*
|
||||
import kotlinx.serialization.descriptors.*
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.*
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
import java.util.Date
|
||||
|
||||
typealias ChatCtrl = Long
|
||||
@@ -1709,6 +1717,11 @@ object ChatController {
|
||||
val userId = kotlin.runCatching { currentUserId("apiCreateUserAddress") }.getOrElse { return null }
|
||||
val r = sendCmdWithRetry(rh, CC.ApiCreateMyAddress(userId))
|
||||
if (r is API.Result && r.res is CR.UserContactLinkCreated) return r.res.connLinkContact
|
||||
if (r is API.Error && r.err is ChatError.ChatErrorAgent && r.err.agentError is AgentErrorType.NOTICE) {
|
||||
val e = r.err.agentError
|
||||
showClientNoticeAlert(e.server, e.preset, e.expiresAt)
|
||||
return null
|
||||
}
|
||||
if (r == null) return null
|
||||
if (!(networkErrorAlert(r))) {
|
||||
apiErrorAlert("apiCreateUserAddress", generalGetString(MR.strings.error_creating_address), r)
|
||||
@@ -1842,13 +1855,6 @@ object ChatController {
|
||||
return r.result is CR.CmdOk
|
||||
}
|
||||
|
||||
suspend fun apiGetNetworkStatuses(rh: Long?): List<ConnNetworkStatus>? {
|
||||
val r = sendCmd(rh, CC.ApiGetNetworkStatuses())
|
||||
if (r is API.Result && r.res is CR.NetworkStatuses) return r.res.networkStatuses
|
||||
Log.e(TAG, "apiGetNetworkStatuses bad response: ${r.responseType} ${r.details}")
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiChatRead(rh: Long?, type: ChatType, id: Long): Boolean {
|
||||
val r = sendCmd(rh, CC.ApiChatRead(type, id, scope = null))
|
||||
if (r.result is CR.CmdOk) return true
|
||||
@@ -2175,6 +2181,11 @@ object ChatController {
|
||||
suspend fun apiCreateGroupLink(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): GroupLink? {
|
||||
val r = sendCmdWithRetry(rh, CC.APICreateGroupLink(groupId, memberRole))
|
||||
if (r is API.Result && r.res is CR.GroupLinkCreated) return r.res.groupLink
|
||||
if (r is API.Error && r.err is ChatError.ChatErrorAgent && r.err.agentError is AgentErrorType.NOTICE) {
|
||||
val e = r.err.agentError
|
||||
showClientNoticeAlert(e.server, e.preset, e.expiresAt)
|
||||
return null
|
||||
}
|
||||
if (r == null) return null
|
||||
if (!(networkErrorAlert(r))) {
|
||||
apiErrorAlert("apiCreateGroupLink", generalGetString(MR.strings.error_creating_link_for_group), r)
|
||||
@@ -2546,12 +2557,15 @@ object ChatController {
|
||||
chatModel.replaceConnReqView(conn.id, "@${r.contact.contactId}")
|
||||
chatModel.chatsContext.removeChat(rhId, conn.id)
|
||||
}
|
||||
if (r.contact.id == chatModel.chatId.value && conn != null) {
|
||||
chatModel.chatAgentConnId.value = conn.agentConnId
|
||||
chatModel.chatSubStatus.value = SubscriptionStatus.Active
|
||||
}
|
||||
}
|
||||
}
|
||||
if (r.contact.directOrUsed) {
|
||||
ntfManager.notifyContactConnected(r.user, r.contact)
|
||||
}
|
||||
chatModel.setContactNetworkStatus(r.contact, NetworkStatus.Connected())
|
||||
}
|
||||
is CR.ContactConnecting -> {
|
||||
if (active(r.user) && r.contact.directOrUsed) {
|
||||
@@ -2576,7 +2590,6 @@ object ChatController {
|
||||
}
|
||||
}
|
||||
}
|
||||
chatModel.setContactNetworkStatus(r.contact, NetworkStatus.Connected())
|
||||
}
|
||||
is CR.ReceivedContactRequest -> {
|
||||
val contactRequest = r.contactRequest
|
||||
@@ -2618,18 +2631,12 @@ object ChatController {
|
||||
}
|
||||
}
|
||||
}
|
||||
// ContactsSubscribed, ContactsDisconnected are only used in CLI,
|
||||
// They have to be used here for remote desktop to process these status updates.
|
||||
is CR.ContactsSubscribed -> updateContactsStatus(r.contactRefs, NetworkStatus.Connected())
|
||||
is CR.ContactsDisconnected -> updateContactsStatus(r.contactRefs, NetworkStatus.Disconnected())
|
||||
is CR.NetworkStatusResp -> {
|
||||
for (cId in r.connections) {
|
||||
chatModel.networkStatuses[cId] = r.networkStatus
|
||||
}
|
||||
}
|
||||
is CR.NetworkStatuses -> {
|
||||
for (s in r.networkStatuses) {
|
||||
chatModel.networkStatuses[s.agentConnId] = s.networkStatus
|
||||
is CR.SubscriptionStatusEvt -> {
|
||||
val chatAgentConnId = chatModel.chatAgentConnId.value
|
||||
if (chatAgentConnId != null && r.connections.contains(chatAgentConnId)) {
|
||||
withContext(Dispatchers.Main) {
|
||||
chatModel.chatSubStatus.value = r.subscriptionStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
is CR.ChatInfoUpdated ->
|
||||
@@ -2916,9 +2923,6 @@ object ChatController {
|
||||
chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member)
|
||||
}
|
||||
}
|
||||
if (r.memberContact != null) {
|
||||
chatModel.setContactNetworkStatus(r.memberContact, NetworkStatus.Connected())
|
||||
}
|
||||
}
|
||||
is CR.GroupUpdated ->
|
||||
if (active(r.user)) {
|
||||
@@ -3231,12 +3235,6 @@ object ChatController {
|
||||
m.users.clear()
|
||||
m.users.addAll(users)
|
||||
getUserChatData(null)
|
||||
val statuses = apiGetNetworkStatuses(null)
|
||||
if (statuses != null) {
|
||||
chatModel.networkStatuses.clear()
|
||||
val ss = statuses.associate { it.agentConnId to it.networkStatus }.toMap()
|
||||
chatModel.networkStatuses.putAll(ss)
|
||||
}
|
||||
}
|
||||
|
||||
private fun activeUser(rhId: Long?, user: UserLike): Boolean =
|
||||
@@ -3349,12 +3347,6 @@ object ChatController {
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateContactsStatus(contactRefs: List<ContactRef>, status: NetworkStatus) {
|
||||
for (c in contactRefs) {
|
||||
chatModel.networkStatuses[c.agentConnId] = status
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun switchUIRemoteHost(rhId: Long?) = showProgressIfNeeded {
|
||||
// TODO lock the switch so that two switches can't run concurrently?
|
||||
chatModel.chatId.value = null
|
||||
@@ -3381,12 +3373,6 @@ object ChatController {
|
||||
chatModel.secondaryChatsContext.value?.popChatCollector?.clear()
|
||||
}
|
||||
}
|
||||
val statuses = apiGetNetworkStatuses(rhId)
|
||||
if (statuses != null) {
|
||||
chatModel.networkStatuses.clear()
|
||||
val ss = statuses.associate { it.agentConnId to it.networkStatus }.toMap()
|
||||
chatModel.networkStatuses.putAll(ss)
|
||||
}
|
||||
getUserChatData(rhId)
|
||||
}
|
||||
|
||||
@@ -3645,7 +3631,6 @@ sealed class CC {
|
||||
class ApiSendCallExtraInfo(val contact: Contact, val extraInfo: WebRTCExtraInfo): CC()
|
||||
class ApiEndCall(val contact: Contact): CC()
|
||||
class ApiCallStatus(val contact: Contact, val callStatus: WebRTCCallStatus): CC()
|
||||
class ApiGetNetworkStatuses(): CC()
|
||||
class ApiAcceptContact(val incognito: Boolean, val contactReqId: Long): CC()
|
||||
class ApiRejectContact(val contactReqId: Long): CC()
|
||||
class ApiChatRead(val type: ChatType, val id: Long, val scope: GroupChatScope?): CC()
|
||||
@@ -3844,7 +3829,6 @@ sealed class CC {
|
||||
is ApiSendCallExtraInfo -> "/_call extra @${contact.apiId} ${json.encodeToString(extraInfo)}"
|
||||
is ApiEndCall -> "/_call end @${contact.apiId}"
|
||||
is ApiCallStatus -> "/_call status @${contact.apiId} ${callStatus.value}"
|
||||
is ApiGetNetworkStatuses -> "/_network_statuses"
|
||||
is ApiChatRead -> "/_read chat ${chatRef(type, id, scope)}"
|
||||
is ApiChatItemsRead -> "/_read chat items ${chatRef(type, id, scope)} ${itemIds.joinToString(",")}"
|
||||
is ApiChatUnread -> "/_unread chat ${chatRef(type, id, scope = null)} ${onOff(unreadChat)}"
|
||||
@@ -4019,7 +4003,6 @@ sealed class CC {
|
||||
is ApiSendCallExtraInfo -> "apiSendCallExtraInfo"
|
||||
is ApiEndCall -> "apiEndCall"
|
||||
is ApiCallStatus -> "apiCallStatus"
|
||||
is ApiGetNetworkStatuses -> "apiGetNetworkStatuses"
|
||||
is ApiChatRead -> "apiChatRead"
|
||||
is ApiChatItemsRead -> "apiChatItemsRead"
|
||||
is ApiChatUnread -> "apiChatUnread"
|
||||
@@ -6159,12 +6142,7 @@ sealed class CR {
|
||||
@Serializable @SerialName("contactRequestRejected") class ContactRequestRejected(val user: UserRef, val contactRequest: UserContactRequest, val contact_: Contact?): CR()
|
||||
@Serializable @SerialName("contactUpdated") class ContactUpdated(val user: UserRef, val toContact: Contact): CR()
|
||||
@Serializable @SerialName("groupMemberUpdated") class GroupMemberUpdated(val user: UserRef, val groupInfo: GroupInfo, val fromMember: GroupMember, val toMember: GroupMember): CR()
|
||||
// TODO remove below
|
||||
@Serializable @SerialName("contactsSubscribed") class ContactsSubscribed(val server: String, val contactRefs: List<ContactRef>): CR()
|
||||
@Serializable @SerialName("contactsDisconnected") class ContactsDisconnected(val server: String, val contactRefs: List<ContactRef>): CR()
|
||||
// TODO remove above
|
||||
@Serializable @SerialName("networkStatus") class NetworkStatusResp(val networkStatus: NetworkStatus, val connections: List<String>): CR()
|
||||
@Serializable @SerialName("networkStatuses") class NetworkStatuses(val user_: UserRef?, val networkStatuses: List<ConnNetworkStatus>): CR()
|
||||
@Serializable @SerialName("subscriptionStatus") class SubscriptionStatusEvt(val subscriptionStatus: SubscriptionStatus, val connections: List<String>): CR()
|
||||
@Serializable @SerialName("chatInfoUpdated") class ChatInfoUpdated(val user: UserRef, val chatInfo: ChatInfo): CR()
|
||||
@Serializable @SerialName("newChatItems") class NewChatItems(val user: UserRef, val chatItems: List<AChatItem>): CR()
|
||||
@Serializable @SerialName("chatItemsStatusesUpdated") class ChatItemsStatusesUpdated(val user: UserRef, val chatItems: List<AChatItem>): CR()
|
||||
@@ -6345,10 +6323,7 @@ sealed class CR {
|
||||
is ContactRequestRejected -> "contactRequestRejected"
|
||||
is ContactUpdated -> "contactUpdated"
|
||||
is GroupMemberUpdated -> "groupMemberUpdated"
|
||||
is ContactsSubscribed -> "contactsSubscribed"
|
||||
is ContactsDisconnected -> "contactsDisconnected"
|
||||
is NetworkStatusResp -> "networkStatus"
|
||||
is NetworkStatuses -> "networkStatuses"
|
||||
is SubscriptionStatusEvt -> "subscriptionStatus"
|
||||
is ChatInfoUpdated -> "chatInfoUpdated"
|
||||
is NewChatItems -> "newChatItems"
|
||||
is ChatItemsStatusesUpdated -> "chatItemsStatusesUpdated"
|
||||
@@ -6521,10 +6496,7 @@ sealed class CR {
|
||||
is ContactRequestRejected -> withUser(user, "contactRequest: ${json.encodeToString(contactRequest)}\ncontact_: ${json.encodeToString(contact_)}")
|
||||
is ContactUpdated -> withUser(user, json.encodeToString(toContact))
|
||||
is GroupMemberUpdated -> withUser(user, "groupInfo: $groupInfo\nfromMember: $fromMember\ntoMember: $toMember")
|
||||
is ContactsSubscribed -> "server: $server\ncontacts:\n${json.encodeToString(contactRefs)}"
|
||||
is ContactsDisconnected -> "server: $server\ncontacts:\n${json.encodeToString(contactRefs)}"
|
||||
is NetworkStatusResp -> "networkStatus $networkStatus\nconnections: $connections"
|
||||
is NetworkStatuses -> withUser(user_, json.encodeToString(networkStatuses))
|
||||
is SubscriptionStatusEvt -> "subscriptionStatus $subscriptionStatus\nconnections: $connections"
|
||||
is ChatInfoUpdated -> withUser(user, json.encodeToString(chatInfo))
|
||||
is NewChatItems -> withUser(user, chatItems.joinToString("\n") { json.encodeToString(it) })
|
||||
is ChatItemsStatusesUpdated -> withUser(user, chatItems.joinToString("\n") { json.encodeToString(it) })
|
||||
@@ -6747,7 +6719,8 @@ class ConnectionStats(
|
||||
val rcvQueuesInfo: List<RcvQueueInfo>,
|
||||
val sndQueuesInfo: List<SndQueueInfo>,
|
||||
val ratchetSyncState: RatchetSyncState,
|
||||
val ratchetSyncSupported: Boolean
|
||||
val ratchetSyncSupported: Boolean,
|
||||
var subStatus: SubscriptionStatus?
|
||||
) {
|
||||
val ratchetSyncAllowed: Boolean get() =
|
||||
ratchetSyncSupported && listOf(RatchetSyncState.Allowed, RatchetSyncState.Required).contains(ratchetSyncState)
|
||||
@@ -6762,8 +6735,10 @@ class ConnectionStats(
|
||||
@Serializable
|
||||
class RcvQueueInfo(
|
||||
val rcvServer: String,
|
||||
var status: QueueStatus,
|
||||
val rcvSwitchStatus: RcvSwitchStatus?,
|
||||
var canAbortSwitch: Boolean
|
||||
var canAbortSwitch: Boolean,
|
||||
var subStatus: SubscriptionStatus
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@@ -6777,6 +6752,7 @@ enum class RcvSwitchStatus {
|
||||
@Serializable
|
||||
class SndQueueInfo(
|
||||
val sndServer: String,
|
||||
var status: QueueStatus,
|
||||
val sndSwitchStatus: SndSwitchStatus?
|
||||
)
|
||||
|
||||
@@ -6814,6 +6790,39 @@ enum class RatchetSyncState {
|
||||
@SerialName("agreed") Agreed
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class QueueStatus {
|
||||
@SerialName("new") New,
|
||||
@SerialName("confirmed") Confirmed,
|
||||
@SerialName("secured") Secured,
|
||||
@SerialName("active") Active,
|
||||
@SerialName("disabled") Disabled
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class SubscriptionStatus {
|
||||
@Serializable @SerialName("active") object Active: SubscriptionStatus()
|
||||
@Serializable @SerialName("pending") object Pending: SubscriptionStatus()
|
||||
@Serializable @SerialName("removed") class Removed(val subError: String): SubscriptionStatus()
|
||||
@Serializable @SerialName("noSub") object NoSub: SubscriptionStatus()
|
||||
|
||||
val statusString: String get() =
|
||||
when (this) {
|
||||
is Active -> generalGetString(MR.strings.server_connected)
|
||||
is Pending -> generalGetString(MR.strings.server_connecting)
|
||||
is Removed -> generalGetString(MR.strings.server_error)
|
||||
is NoSub -> generalGetString(MR.strings.server_no_sub)
|
||||
}
|
||||
|
||||
val statusExplanation: String get() =
|
||||
when (this) {
|
||||
is Active -> generalGetString(MR.strings.connected_to_server_to_receive_messages_from_contact)
|
||||
is Pending -> generalGetString(MR.strings.trying_to_connect_to_server_to_receive_messages)
|
||||
is Removed -> String.format(generalGetString(MR.strings.error_connecting_to_server_to_receive_messages), subError)
|
||||
is NoSub -> generalGetString(MR.strings.not_connected_to_server_to_receive_messages_no_sub)
|
||||
}
|
||||
}
|
||||
|
||||
interface SimplexAddress {
|
||||
val connLinkContact: CreatedConnLink
|
||||
val shortLinkDataSet: Boolean
|
||||
@@ -7306,6 +7315,7 @@ sealed class AgentErrorType {
|
||||
is RCP -> "RCP ${rcpErr.string}"
|
||||
is BROKER -> "BROKER ${brokerErr.string}"
|
||||
is AGENT -> "AGENT ${agentErr.string}"
|
||||
is NOTICE -> "NOTICE $server $expiresAt"
|
||||
is INTERNAL -> "INTERNAL $internalErr"
|
||||
is CRITICAL -> "CRITICAL $offerRestart $criticalErr"
|
||||
is INACTIVE -> "INACTIVE"
|
||||
@@ -7319,6 +7329,7 @@ sealed class AgentErrorType {
|
||||
@Serializable @SerialName("RCP") class RCP(val rcpErr: RCErrorType): AgentErrorType()
|
||||
@Serializable @SerialName("BROKER") class BROKER(val brokerAddress: String, val brokerErr: BrokerErrorType): AgentErrorType()
|
||||
@Serializable @SerialName("AGENT") class AGENT(val agentErr: SMPAgentError): AgentErrorType()
|
||||
@Serializable @SerialName("NOTICE") class NOTICE(val server: String, val preset: Boolean, val expiresAt: Instant?): AgentErrorType()
|
||||
@Serializable @SerialName("INTERNAL") class INTERNAL(val internalErr: String): AgentErrorType()
|
||||
@Serializable @SerialName("CRITICAL") data class CRITICAL(val offerRestart: Boolean, val criticalErr: String): AgentErrorType()
|
||||
@Serializable @SerialName("INACTIVE") object INACTIVE: AgentErrorType()
|
||||
@@ -8046,3 +8057,34 @@ enum class MsgType {
|
||||
@SerialName("quota")
|
||||
QUOTA
|
||||
}
|
||||
|
||||
fun showClientNoticeAlert(server: String, preset: Boolean, expiresAt: Instant?) {
|
||||
var message = "Server: $server.\nConditions of use violation notice received from ${if (preset) "preset" else "this"} server.\nNo ID shared, see How it works."
|
||||
if (expiresAt != null) {
|
||||
val tz = TimeZone.currentSystemDefault()
|
||||
val formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)
|
||||
message += "\n\nNew addresses can be created after ${expiresAt.toLocalDateTime(tz).toJavaLocalDateTime().format(formatter)}."
|
||||
}
|
||||
AlertManager.shared.showAlertDialogButtonsColumn(title = "Not allowed", text = AnnotatedString(message)) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
Column {
|
||||
SectionItemView({ AlertManager.shared.hideAlert() }) {
|
||||
Text(generalGetString(MR.strings.ok), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
if (preset) {
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
uriHandler.openUriCatching(defaultConditionsLink)
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.operator_conditions_of_use), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
uriHandler.openUriCatching(contentModerationPostLink)
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.how_it_works), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+18
-22
@@ -72,9 +72,6 @@ fun ChatInfoView(
|
||||
val connStats = remember(contact.id, connectionStats) { mutableStateOf(connectionStats) }
|
||||
val developerTools = chatModel.controller.appPrefs.developerTools.get()
|
||||
if (chat != null && currentUser != null) {
|
||||
val contactNetworkStatus = remember(chatModel.networkStatuses.toMap(), contact) {
|
||||
mutableStateOf(chatModel.contactNetworkStatus(contact))
|
||||
}
|
||||
val chatRh = chat.remoteHostId
|
||||
val sendReceipts = remember(contact.id) { mutableStateOf(SendReceipts.fromBool(contact.chatSettings.sendRcpts, currentUser.sendRcptsContacts)) }
|
||||
val chatItemTTL = remember(contact.id) { mutableStateOf(if (contact.chatItemTTL != null) ChatItemTTL.fromSeconds(contact.chatItemTTL) else null) }
|
||||
@@ -101,7 +98,6 @@ fun ChatInfoView(
|
||||
setChatTTLAlert(chatsCtx, chat.remoteHostId, chat.chatInfo, chatItemTTL, previousChatTTL, deletingItems)
|
||||
},
|
||||
connStats = connStats,
|
||||
contactNetworkStatus.value,
|
||||
customUserProfile,
|
||||
localAlias,
|
||||
connectionCode,
|
||||
@@ -524,7 +520,6 @@ fun ChatInfoLayout(
|
||||
chatItemTTL: MutableState<ChatItemTTL?>,
|
||||
setChatItemTTL: (ChatItemTTL?) -> Unit,
|
||||
connStats: MutableState<ConnectionStats?>,
|
||||
contactNetworkStatus: NetworkStatus,
|
||||
customUserProfile: Profile?,
|
||||
localAlias: String,
|
||||
connectionCode: String?,
|
||||
@@ -643,13 +638,16 @@ fun ChatInfoLayout(
|
||||
|
||||
if (contact.ready && contact.active) {
|
||||
SectionView(title = stringResource(MR.strings.conn_stats_section_title_servers)) {
|
||||
SectionItemView({
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.network_status),
|
||||
contactNetworkStatus.statusExplanation
|
||||
)
|
||||
}) {
|
||||
NetworkStatusRow(contactNetworkStatus)
|
||||
val chatSubStatus = chatModel.chatSubStatus.value
|
||||
if (chatSubStatus != null) {
|
||||
SectionItemView({
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.network_status),
|
||||
chatSubStatus.statusExplanation
|
||||
)
|
||||
}) {
|
||||
SubStatusRow(chatSubStatus)
|
||||
}
|
||||
}
|
||||
if (cStats != null) {
|
||||
SwitchAddressButton(
|
||||
@@ -1063,7 +1061,7 @@ fun InfoViewActionButton(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NetworkStatusRow(networkStatus: NetworkStatus) {
|
||||
fun SubStatusRow(subStatus: SubscriptionStatus) {
|
||||
Row(
|
||||
Modifier.fillMaxSize(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
@@ -1086,25 +1084,24 @@ private fun NetworkStatusRow(networkStatus: NetworkStatus) {
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Text(
|
||||
networkStatus.statusString,
|
||||
subStatus.statusString,
|
||||
color = MaterialTheme.colors.secondary
|
||||
)
|
||||
ServerImage(networkStatus)
|
||||
ServerImage(subStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ServerImage(networkStatus: NetworkStatus) {
|
||||
private fun ServerImage(subStatus: SubscriptionStatus) {
|
||||
Box(Modifier.size(18.dp)) {
|
||||
when (networkStatus) {
|
||||
is NetworkStatus.Connected ->
|
||||
when (subStatus) {
|
||||
is SubscriptionStatus.Active ->
|
||||
Icon(painterResource(MR.images.ic_circle_filled), stringResource(MR.strings.icon_descr_server_status_connected), tint = Color.Green)
|
||||
is NetworkStatus.Disconnected ->
|
||||
is SubscriptionStatus.Pending ->
|
||||
Icon(painterResource(MR.images.ic_pending_filled), stringResource(MR.strings.icon_descr_server_status_disconnected), tint = MaterialTheme.colors.secondary)
|
||||
is NetworkStatus.Error ->
|
||||
is SubscriptionStatus.Removed, SubscriptionStatus.NoSub ->
|
||||
Icon(painterResource(MR.images.ic_error_filled), stringResource(MR.strings.icon_descr_server_status_error), tint = MaterialTheme.colors.secondary)
|
||||
else -> Icon(painterResource(MR.images.ic_circle), stringResource(MR.strings.icon_descr_server_status_pending), tint = MaterialTheme.colors.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1455,7 +1452,6 @@ fun PreviewChatInfoLayout() {
|
||||
connectionCode = "123",
|
||||
developerTools = false,
|
||||
connStats = remember { mutableStateOf(null) },
|
||||
contactNetworkStatus = NetworkStatus.Connected(),
|
||||
onLocalAliasChanged = {},
|
||||
customUserProfile = null,
|
||||
openPreferences = {},
|
||||
|
||||
+65
-2
@@ -102,6 +102,7 @@ fun ChatView(
|
||||
val chat = chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }
|
||||
// They have their own iterator inside for a reason to prevent crash "Reading a state that was created after the snapshot..."
|
||||
val remoteHostId = remember { derivedStateOf { chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.remoteHostId } }
|
||||
val chatRh = remoteHostId.value
|
||||
val activeChat = remember { derivedStateOf {
|
||||
var chat = chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }
|
||||
val chatInfo = chat?.chatInfo
|
||||
@@ -120,6 +121,8 @@ fun ChatView(
|
||||
if (chat == null || chatInfo == null || user == null) {
|
||||
LaunchedEffect(Unit) {
|
||||
chatModel.chatId.value = null
|
||||
chatModel.chatAgentConnId.value = null
|
||||
chatModel.chatSubStatus.value = null
|
||||
ModalManager.end.closeModals()
|
||||
}
|
||||
} else {
|
||||
@@ -168,6 +171,25 @@ fun ChatView(
|
||||
showSearch.value = false
|
||||
searchText.value = ""
|
||||
selectedChatItems.value = null
|
||||
if (chat.chatInfo is ChatInfo.Direct && chat.chatInfo.contact.activeConn != null) {
|
||||
withBGApi {
|
||||
val r = chatModel.controller.apiContactInfo(chatRh, chatInfo.apiId)
|
||||
if (r != null) {
|
||||
val contactStats = r.first
|
||||
if (contactStats != null)
|
||||
withContext(Dispatchers.Main) {
|
||||
chatModel.chatsContext.updateContactConnectionStats(chatRh, chat.chatInfo.contact, contactStats)
|
||||
chatModel.chatAgentConnId.value = chat.chatInfo.contact.activeConn.agentConnId
|
||||
chatModel.chatSubStatus.value = contactStats.subStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
withContext(Dispatchers.Main) {
|
||||
chatModel.chatAgentConnId.value = null
|
||||
chatModel.chatSubStatus.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,7 +206,6 @@ fun ChatView(
|
||||
}
|
||||
}
|
||||
val view = LocalMultiplatformView()
|
||||
val chatRh = remoteHostId.value
|
||||
// We need to have real unreadCount value for displaying it inside top right button
|
||||
// Having activeChat reloaded on every change in it is inefficient (UI lags)
|
||||
val unreadCount = remember {
|
||||
@@ -356,7 +377,7 @@ fun ChatView(
|
||||
if (chatInfo is ChatInfo.Direct) {
|
||||
var contactInfo: Pair<ConnectionStats?, Profile?>? by remember { mutableStateOf(preloadedContactInfo) }
|
||||
var code: String? by remember { mutableStateOf(preloadedCode) }
|
||||
KeyChangeEffect(chatInfo.id, ChatModel.networkStatuses.toMap()) {
|
||||
KeyChangeEffect(chatInfo.id) {
|
||||
contactInfo = chatModel.controller.apiContactInfo(chatRh, chatInfo.apiId)
|
||||
preloadedContactInfo = contactInfo
|
||||
code = chatModel.controller.apiGetContactCode(chatRh, chatInfo.apiId)?.second
|
||||
@@ -1281,9 +1302,51 @@ fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Colo
|
||||
)
|
||||
}
|
||||
}
|
||||
val chatSubStatus = chatModel.chatSubStatus.value
|
||||
if (
|
||||
cInfo is ChatInfo.Direct &&
|
||||
cInfo.contact.ready &&
|
||||
cInfo.contact.active &&
|
||||
chatSubStatus != null &&
|
||||
chatSubStatus != SubscriptionStatus.Active
|
||||
) {
|
||||
Box(
|
||||
Modifier.padding(start = 10.dp)
|
||||
) {
|
||||
SubStatusView(chatSubStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SubStatusView(status: SubscriptionStatus) {
|
||||
when (status) {
|
||||
SubscriptionStatus.Active ->
|
||||
Box {}
|
||||
SubscriptionStatus.Pending ->
|
||||
SubProgressView()
|
||||
is SubscriptionStatus.Removed, SubscriptionStatus.NoSub ->
|
||||
Icon(
|
||||
painterResource(MR.images.ic_error),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colors.secondary,
|
||||
modifier = Modifier
|
||||
.size(19.sp.toDp())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SubProgressView() {
|
||||
CircularProgressIndicator(
|
||||
Modifier
|
||||
.size(15.sp.toDp()),
|
||||
color = MaterialTheme.colors.secondary,
|
||||
strokeWidth = 1.5.dp
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SupportChatsCountToolbar(
|
||||
chatInfo: ChatInfo,
|
||||
|
||||
-1
@@ -162,7 +162,6 @@ fun acceptMemberContact(
|
||||
chatModel.chatsContext.updateContact(rhId, contact)
|
||||
inProgress?.value = false
|
||||
}
|
||||
chatModel.setContactNetworkStatus(contact, NetworkStatus.Connected())
|
||||
val chat = Chat(remoteHostId = rhId, ChatInfo.Direct(contact), listOf())
|
||||
close?.invoke(chat)
|
||||
} else {
|
||||
|
||||
-2
@@ -533,7 +533,6 @@ fun ComposeView(
|
||||
withContext(Dispatchers.Main) {
|
||||
chatsCtx.updateContact(chat.remoteHostId, contact)
|
||||
clearState()
|
||||
chatModel.setContactNetworkStatus(contact, NetworkStatus.Connected())
|
||||
}
|
||||
} else {
|
||||
composeState.value = composeState.value.copy(inProgress = false)
|
||||
@@ -554,7 +553,6 @@ fun ComposeView(
|
||||
withContext(Dispatchers.Main) {
|
||||
chatsCtx.updateContact(chat.remoteHostId, contact)
|
||||
clearState()
|
||||
chatModel.setContactNetworkStatus(contact, NetworkStatus.Connected())
|
||||
}
|
||||
} else {
|
||||
composeState.value = composeState.value.copy(inProgress = false)
|
||||
|
||||
+11
-1
@@ -109,7 +109,6 @@ fun GroupMemberInfoView(
|
||||
}
|
||||
openDirectChat(rhId, memberContact.contactId)
|
||||
closeAll()
|
||||
chatModel.setContactNetworkStatus(memberContact, NetworkStatus.Connected())
|
||||
}
|
||||
progressIndicator = false
|
||||
}
|
||||
@@ -495,6 +494,17 @@ fun GroupMemberInfoLayout(
|
||||
if (cStats != null) {
|
||||
SectionDividerSpaced()
|
||||
SectionView(title = stringResource(MR.strings.conn_stats_section_title_servers)) {
|
||||
val subStatus = cStats.subStatus
|
||||
if (subStatus != null) {
|
||||
SectionItemView({
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.network_status),
|
||||
subStatus.statusExplanation
|
||||
)
|
||||
}) {
|
||||
SubStatusRow(subStatus)
|
||||
}
|
||||
}
|
||||
SwitchAddressButton(
|
||||
disabled = cStats.rcvQueuesInfo.any { it.rcvSwitchStatus != null } || !member.sendMsgEnabled,
|
||||
switchAddress = switchMemberAddress
|
||||
|
||||
+3
-7
@@ -62,12 +62,11 @@ fun ChatListNavLinkView(chat: Chat, nextChatSelected: State<Boolean>) {
|
||||
|
||||
when (chat.chatInfo) {
|
||||
is ChatInfo.Direct -> {
|
||||
val contactNetworkStatus = chatModel.contactNetworkStatus(chat.chatInfo.contact)
|
||||
val defaultClickAction = { if (chatModel.chatId.value != chat.id) scope.launch { directChatAction(chat.remoteHostId, chat.chatInfo.contact, chatModel) } }
|
||||
ChatListNavLinkLayout(
|
||||
chatLinkPreview = {
|
||||
tryOrShowError("${chat.id}ChatListNavLink", error = { ErrorChatListItem() }) {
|
||||
ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, contactNetworkStatus, disabled, linkMode, inProgress = false, progressByTimeout = false, defaultClickAction)
|
||||
ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, disabled, linkMode, inProgress = false, progressByTimeout = false, defaultClickAction)
|
||||
}
|
||||
},
|
||||
click = defaultClickAction,
|
||||
@@ -87,7 +86,7 @@ fun ChatListNavLinkView(chat: Chat, nextChatSelected: State<Boolean>) {
|
||||
ChatListNavLinkLayout(
|
||||
chatLinkPreview = {
|
||||
tryOrShowError("${chat.id}ChatListNavLink", error = { ErrorChatListItem() }) {
|
||||
ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, null, disabled, linkMode, inProgress.value, progressByTimeout, defaultClickAction)
|
||||
ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, disabled, linkMode, inProgress.value, progressByTimeout, defaultClickAction)
|
||||
}
|
||||
},
|
||||
click = defaultClickAction,
|
||||
@@ -107,7 +106,7 @@ fun ChatListNavLinkView(chat: Chat, nextChatSelected: State<Boolean>) {
|
||||
ChatListNavLinkLayout(
|
||||
chatLinkPreview = {
|
||||
tryOrShowError("${chat.id}ChatListNavLink", error = { ErrorChatListItem() }) {
|
||||
ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, null, disabled, linkMode, inProgress = false, progressByTimeout = false, defaultClickAction)
|
||||
ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, disabled, linkMode, inProgress = false, progressByTimeout = false, defaultClickAction)
|
||||
}
|
||||
},
|
||||
click = defaultClickAction,
|
||||
@@ -744,7 +743,6 @@ fun acceptContactRequest(
|
||||
}
|
||||
inProgress?.value = false
|
||||
}
|
||||
chatModel.setContactNetworkStatus(contact, NetworkStatus.Connected())
|
||||
close?.invoke(chat)
|
||||
} else {
|
||||
inProgress?.value = false
|
||||
@@ -1057,7 +1055,6 @@ fun PreviewChatListNavLinkDirect() {
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
disabled = false,
|
||||
linkMode = SimplexLinkMode.DESCRIPTION,
|
||||
inProgress = false,
|
||||
@@ -1103,7 +1100,6 @@ fun PreviewChatListNavLinkGroup() {
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
disabled = false,
|
||||
linkMode = SimplexLinkMode.DESCRIPTION,
|
||||
inProgress = false,
|
||||
|
||||
+2
-29
@@ -42,7 +42,6 @@ fun ChatPreviewView(
|
||||
chatModelDraft: ComposeState?,
|
||||
chatModelDraftChatId: ChatId?,
|
||||
currentUserProfileDisplayName: String?,
|
||||
contactNetworkStatus: NetworkStatus?,
|
||||
disabled: Boolean,
|
||||
linkMode: SimplexLinkMode,
|
||||
inProgress: Boolean,
|
||||
@@ -349,33 +348,7 @@ fun ChatPreviewView(
|
||||
|
||||
@Composable
|
||||
fun chatStatusImage() {
|
||||
if (cInfo is ChatInfo.Direct) {
|
||||
if (
|
||||
cInfo.contact.active &&
|
||||
(cInfo.contact.activeConn?.connStatus == ConnStatus.Ready || cInfo.contact.activeConn?.connStatus == ConnStatus.SndReady)
|
||||
) {
|
||||
val descr = contactNetworkStatus?.statusString
|
||||
when (contactNetworkStatus) {
|
||||
is NetworkStatus.Connected ->
|
||||
IncognitoIcon(chat.chatInfo.incognito)
|
||||
|
||||
is NetworkStatus.Error ->
|
||||
Icon(
|
||||
painterResource(MR.images.ic_error),
|
||||
contentDescription = descr,
|
||||
tint = MaterialTheme.colors.secondary,
|
||||
modifier = Modifier
|
||||
.size(19.sp.toDp())
|
||||
.offset(x = 2.sp.toDp())
|
||||
)
|
||||
|
||||
else ->
|
||||
progressView()
|
||||
}
|
||||
} else {
|
||||
IncognitoIcon(chat.chatInfo.incognito)
|
||||
}
|
||||
} else if (cInfo is ChatInfo.Group) {
|
||||
if (cInfo is ChatInfo.Group) {
|
||||
if (progressByTimeout) {
|
||||
progressView()
|
||||
} else if (chat.chatStats.reportsCount > 0) {
|
||||
@@ -636,6 +609,6 @@ private data class ActiveVoicePreview(
|
||||
@Composable
|
||||
fun PreviewChatPreviewView() {
|
||||
SimpleXTheme {
|
||||
ChatPreviewView(Chat.sampleData, true, null, null, "", contactNetworkStatus = NetworkStatus.Connected(), disabled = false, linkMode = SimplexLinkMode.DESCRIPTION, inProgress = false, progressByTimeout = false, {})
|
||||
ChatPreviewView(Chat.sampleData, true, null, null, "", disabled = false, linkMode = SimplexLinkMode.DESCRIPTION, inProgress = false, progressByTimeout = false, {})
|
||||
}
|
||||
}
|
||||
|
||||
+7
-7
@@ -64,7 +64,7 @@ enum class SubscriptionColorType {
|
||||
ACTIVE, ACTIVE_SOCKS_PROXY, DISCONNECTED, ACTIVE_DISCONNECTED
|
||||
}
|
||||
|
||||
data class SubscriptionStatus(
|
||||
data class AppSubscriptionStatus(
|
||||
val color: SubscriptionColorType,
|
||||
val variableValue: Float,
|
||||
val statusPercent: Float
|
||||
@@ -75,7 +75,7 @@ fun subscriptionStatusColorAndPercentage(
|
||||
socksProxy: String?,
|
||||
subs: SMPServerSubs,
|
||||
hasSess: Boolean
|
||||
): SubscriptionStatus {
|
||||
): AppSubscriptionStatus {
|
||||
fun roundedToQuarter(n: Float): Float = when {
|
||||
n >= 1 -> 1f
|
||||
n <= 0 -> 0f
|
||||
@@ -83,25 +83,25 @@ fun subscriptionStatusColorAndPercentage(
|
||||
}
|
||||
|
||||
val activeColor: SubscriptionColorType = if (socksProxy != null) SubscriptionColorType.ACTIVE_SOCKS_PROXY else SubscriptionColorType.ACTIVE
|
||||
val noConnColorAndPercent = SubscriptionStatus(SubscriptionColorType.DISCONNECTED, 1f, 0f)
|
||||
val noConnColorAndPercent = AppSubscriptionStatus(SubscriptionColorType.DISCONNECTED, 1f, 0f)
|
||||
val activeSubsRounded = roundedToQuarter(subs.shareOfActive)
|
||||
|
||||
return if (!online)
|
||||
noConnColorAndPercent
|
||||
else if (subs.total == 0 && !hasSess)
|
||||
// On freshly installed app (without chats) and on app start
|
||||
SubscriptionStatus(activeColor, 0f, 0f)
|
||||
AppSubscriptionStatus(activeColor, 0f, 0f)
|
||||
else if (subs.ssActive == 0) {
|
||||
if (hasSess)
|
||||
SubscriptionStatus(activeColor, activeSubsRounded, subs.shareOfActive)
|
||||
AppSubscriptionStatus(activeColor, activeSubsRounded, subs.shareOfActive)
|
||||
else
|
||||
noConnColorAndPercent
|
||||
} else { // ssActive > 0
|
||||
if (hasSess)
|
||||
SubscriptionStatus(activeColor, activeSubsRounded, subs.shareOfActive)
|
||||
AppSubscriptionStatus(activeColor, activeSubsRounded, subs.shareOfActive)
|
||||
else
|
||||
// This would mean implementation error
|
||||
SubscriptionStatus(SubscriptionColorType.ACTIVE_DISCONNECTED, activeSubsRounded, subs.shareOfActive)
|
||||
AppSubscriptionStatus(SubscriptionColorType.ACTIVE_DISCONNECTED, activeSubsRounded, subs.shareOfActive)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -612,13 +612,14 @@ private fun SingleOperatorUsageConditionsView(
|
||||
}
|
||||
}
|
||||
|
||||
val defaultConditionsLink = "https://github.com/simplex-chat/simplex-chat/blob/stable/PRIVACY.md"
|
||||
|
||||
@Composable
|
||||
fun ConditionsTextView(
|
||||
rhId: Long?
|
||||
) {
|
||||
val conditionsData = remember { mutableStateOf<Triple<UsageConditionsDetail, String?, UsageConditionsDetail?>?>(null) }
|
||||
val failedToLoad = remember { mutableStateOf(false) }
|
||||
val defaultConditionsLink = "https://github.com/simplex-chat/simplex-chat/blob/stable/PRIVACY.md"
|
||||
val scope = rememberCoroutineScope()
|
||||
// can show conditions when animation between modals finishes to prevent glitches
|
||||
val canShowConditionsAt = remember { System.currentTimeMillis() + 300 }
|
||||
|
||||
@@ -1131,7 +1131,7 @@
|
||||
<string name="trying_to_connect_to_server_to_receive_messages">محاولة الاتصال بالخادم المستخدم لاستلام الرسائل من جهة الاتصال هذه.</string>
|
||||
<string name="choose_file_title">اختيار ملف</string>
|
||||
<string name="icon_descr_sent_msg_status_unauthorized_send">إرسال غير مصرح به</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">محاولة الاتصال بالخادم المستخدم لاستلام الرسائل من جهة الاتصال هذه (خطأ: %1$s).</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">محاولة الاتصال بالخادم المستخدم لاستلام الرسائل من جهة الاتصال هذه (خطأ: %1$s).</string>
|
||||
<string name="la_notice_turn_on">تشغيل</string>
|
||||
<string name="webrtc_ice_servers">خوادم WebRTC ICE</string>
|
||||
<string name="alert_title_cant_invite_contacts_descr">أنت تستخدم ملف تعريف متخفي لهذه المجموعة - لمنع مشاركة ملفك التعريفي الرئيسي الذي يدعو جهات الاتصال غير مسموح به</string>
|
||||
|
||||
@@ -32,9 +32,11 @@
|
||||
<string name="server_connected">connected</string>
|
||||
<string name="server_error">error</string>
|
||||
<string name="server_connecting">connecting</string>
|
||||
<string name="connected_to_server_to_receive_messages_from_contact">You are connected to the server used to receive messages from this contact.</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Trying to connect to the server used to receive messages from this contact (error: %1$s).</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages">Trying to connect to the server used to receive messages from this contact.</string>
|
||||
<string name="server_no_sub">no subscription</string>
|
||||
<string name="connected_to_server_to_receive_messages_from_contact">You are connected to the server used to receive messages from this connection.</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages">Trying to connect to the server used to receive messages from this connection.</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">Error connecting to the server used to receive messages from this connection: %1$s.</string>
|
||||
<string name="not_connected_to_server_to_receive_messages_no_sub">You are not connected to the server used to receive messages from this connection (no subscription).</string>
|
||||
|
||||
<!-- Item Content - ChatModel.kt -->
|
||||
<string name="deleted_description">deleted</string>
|
||||
|
||||
@@ -1182,7 +1182,7 @@
|
||||
<string name="messages_section_description">Тази настройка се прилага за съобщения в текущия ви профил</string>
|
||||
<string name="to_protect_privacy_simplex_has_ids_for_queues">За да се защити поверителността, SimpleX използва идентификатори за опашки от съобщения, отделни за всеки от вашите контакти.</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages">Опит за свързване със сървъра, използван за получаване на съобщения от този контакт.</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Опит за свързване със сървъра, използван за получаване на съобщения от този контакт (грешка: %1$s).</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">Опит за свързване със сървъра, използван за получаване на съобщения от този контакт (грешка: %1$s).</string>
|
||||
<string name="error_smp_test_failed_at_step">Тестът е неуспешен на стъпка %s.</string>
|
||||
<string name="database_initialization_error_desc">Базата данни не работи правилно. Докоснете, за да научите повече</string>
|
||||
<string name="image_decoding_exception_desc">Изображението не може да бъде декодирано. Моля, опитайте с друго изображение или се свържете с разработчиците.</string>
|
||||
|
||||
@@ -1556,7 +1556,7 @@
|
||||
<string name="error_parsing_uri_desc">Comproveu que l\'enllaç SimpleX sigui correcte.</string>
|
||||
<string name="sending_files_not_yet_supported">l\'enviament de fitxers encara no està suportat</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages">Intentant connectar-se al servidor utilitzat per rebre missatges d\'aquest contacte.</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">S\'està provant de connectar-se al servidor utilitzat per rebre missatges d\'aquest contacte (error: %1$s).</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">S\'està provant de connectar-se al servidor utilitzat per rebre missatges d\'aquest contacte (error: %1$s).</string>
|
||||
<string name="connect_use_current_profile">Usar perfil actual</string>
|
||||
<string name="connect_use_new_incognito_profile">Usar nou perfil incògnit</string>
|
||||
<string name="app_was_crashed">Error aplicació</string>
|
||||
|
||||
@@ -493,7 +493,7 @@
|
||||
<string name="you_will_join_group">Připojtíte se ke všem členům skupiny.</string>
|
||||
<string name="connect_via_link_verb">Připojení</string>
|
||||
<string name="connected_to_server_to_receive_messages_from_contact">Jste připojeni k serveru, který se používá k přijímání zpráv od tohoto kontaktu.</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Pokoušíte se připojit k serveru používaném pro příjem zpráv od tohoto kontaktu (chyba: %1$s).</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">Pokoušíte se připojit k serveru používaném pro příjem zpráv od tohoto kontaktu (chyba: %1$s).</string>
|
||||
<string name="marked_deleted_description">označeno jako smazáno</string>
|
||||
<string name="sending_files_not_yet_supported">Odesílání souborů zatím není podporováno</string>
|
||||
<string name="receiving_files_not_yet_supported">přijímání souborů zatím není podporováno</string>
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
<string name="server_error">fejl</string>
|
||||
<string name="server_connecting">forbinder</string>
|
||||
<string name="connected_to_server_to_receive_messages_from_contact">Du har forbindelse til den server, der bruges til at modtage beskeder fra denne kontakt.</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Forsøger at oprette forbindelse til den server, der bruges til at modtage beskeder fra denne kontakt (fejl: %1$s).</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">Forsøger at oprette forbindelse til den server, der bruges til at modtage beskeder fra denne kontakt (fejl: %1$s).</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages">Forsøger at oprette forbindelse til den server, der bruges til at modtage beskeder fra denne kontakt.</string>
|
||||
<string name="deleted_description">slettet</string>
|
||||
<string name="marked_deleted_description">markeret som slettet</string>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<string name="server_error">Fehler</string>
|
||||
<string name="server_connecting">Verbinde</string>
|
||||
<string name="connected_to_server_to_receive_messages_from_contact">Sie sind mit dem Server verbunden, der für den Empfang von Nachrichten mit diesem Kontakt genutzt wird.</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Beim Versuch, die Verbindung mit dem Server aufzunehmen, der für den Empfang von Nachrichten mit diesem Kontakt genutzt wird, ist ein Fehler aufgetreten (Fehler: %1$s).</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">Beim Versuch, die Verbindung mit dem Server aufzunehmen, der für den Empfang von Nachrichten mit diesem Kontakt genutzt wird, ist ein Fehler aufgetreten (Fehler: %1$s).</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages">Versuche die Verbindung mit dem Server aufzunehmen, der für den Empfang von Nachrichten mit diesem Kontakt genutzt wird.</string>
|
||||
<!-- Item Content - ChatModel.kt -->
|
||||
<string name="deleted_description">Gelöscht</string>
|
||||
|
||||
@@ -806,7 +806,7 @@
|
||||
<string name="update_network_settings_question">¿Actualizar la configuración de red\?</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages">Intentando conectar con el servidor para recibir mensajes de este contacto.</string>
|
||||
<string name="unknown_message_format">formato de mensaje desconocido</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Intentando conectar con el servidor para recibir mensajes de este contacto (error: %1$s).</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">Intentando conectar con el servidor para recibir mensajes de este contacto (error: %1$s).</string>
|
||||
<string name="error_smp_test_failed_at_step">Prueba no superada en el paso %s.</string>
|
||||
<string name="tap_to_start_new_chat">Pulsa para iniciar chat nuevo</string>
|
||||
<string name="share_message">Compartir mensaje…</string>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<string name="connect_via_link_verb">متصل شدن</string>
|
||||
<string name="non_content_uri_alert_title">مسیر نامعتبر فایل</string>
|
||||
<string name="app_was_crashed">برنامه از کار افتاد</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">در حال تلاش برای اتصال به سرور مورد استفاده برای دریافت پیامها از این مخاطب (خطا: %1$s).</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">در حال تلاش برای اتصال به سرور مورد استفاده برای دریافت پیامها از این مخاطب (خطا: %1$s).</string>
|
||||
<string name="deleted_description">حذف شد</string>
|
||||
<string name="marked_deleted_description">علامت گذاشته شده به عنوان حذف شده</string>
|
||||
<string name="moderated_item_description">توسط %s حذف شد</string>
|
||||
|
||||
@@ -1036,7 +1036,7 @@
|
||||
<string name="profile_will_be_sent_to_contact_sending_link">Profiilisi lähetetään kontaktille, jolta sait tämän linkin.</string>
|
||||
<string name="you_will_join_group">Liityt ryhmään, johon tämä linkki viittaa, ja muodostat yhteyden sen ryhmän jäseniin.</string>
|
||||
<string name="connected_to_server_to_receive_messages_from_contact">Olet yhteydessä palvelimeen, jota käytetään vastaanottamaan viestejä tältä kontaktilta.</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Yritetään muodostaa yhteys palvelimeen, jota käytetään viestien vastaanottamiseen tältä kontaktilta (virhe: %1$s).</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">Yritetään muodostaa yhteys palvelimeen, jota käytetään viestien vastaanottamiseen tältä kontaktilta (virhe: %1$s).</string>
|
||||
<string name="sender_you_pronoun">sinä</string>
|
||||
<string name="description_via_one_time_link">kertalinkillä</string>
|
||||
<string name="simplex_link_connection">%1$s:n kautta</string>
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<string name="please_check_correct_link_and_maybe_ask_for_a_new_one">Veuillez vérifier que vous avez utilisé le bon lien ou demandez à votre contact de vous en envoyer un autre.</string>
|
||||
<string name="connection_error">Erreur de connexion</string>
|
||||
<string name="error_adding_members">Erreur lors de l\'ajout de membre·s</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Tentative de connexion au serveur utilisé pour recevoir les messages de ce contact (erreur : %1$s).</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">Tentative de connexion au serveur utilisé pour recevoir les messages de ce contact (erreur : %1$s).</string>
|
||||
<string name="invalid_message_format">format de message invalide</string>
|
||||
<string name="simplex_link_mode_full">Lien entier</string>
|
||||
<string name="error_saving_smp_servers">Erreur lors de la sauvegarde des serveurs SMP</string>
|
||||
|
||||
@@ -1317,7 +1317,7 @@
|
||||
<string name="image_decoding_exception_desc">A kép nem dekódolható. Próbálja meg egy másik képpel, vagy lépjen kapcsolatba a fejlesztőkkel.</string>
|
||||
<string name="non_content_uri_alert_text">Érvénytelen fájlelérési útvonalat osztott meg. Jelentse a problémát az alkalmazás fejlesztőinek.</string>
|
||||
<string name="failed_to_create_user_duplicate_desc">Már van egy csevegési profil ugyanezzel a megjelenítendő névvel. Válasszon egy másik nevet.</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott partnerétől érkező üzenetek fogadására szolgál (hiba: %1$s).</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott partnerétől érkező üzenetek fogadására szolgál (hiba: %1$s).</string>
|
||||
<string name="stop_rcv_file__message">A fájl fogadása le fog állni.</string>
|
||||
<string name="la_please_remember_to_store_password">Ne felejtse el, vagy tárolja biztonságosan – az elveszett jelszót nem lehet visszaállítani!</string>
|
||||
<string name="video_will_be_received_when_contact_completes_uploading">A videó akkor érkezik meg, amikor a küldője befejezte annak feltöltését.</string>
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
<string name="error_parsing_uri_title">Tautan tidak valid</string>
|
||||
<string name="error_parsing_uri_desc">Periksa apakah tautan SimpleX sudah benar.</string>
|
||||
<string name="connected_to_server_to_receive_messages_from_contact">Anda terhubung ke server yang digunakan untuk menerima pesan dari kontak ini.</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Mencoba menyambung ke server yang digunakan untuk menerima pesan dari kontak ini (error: %1$s).</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">Mencoba menyambung ke server yang digunakan untuk menerima pesan dari kontak ini (error: %1$s).</string>
|
||||
<string name="database_migration_in_progress">Migrasi basis data sedang berlangsung,
|
||||
\nmemerlukan waktu beberapa menit.</string>
|
||||
<string name="server_connecting">menghubungkan</string>
|
||||
|
||||
@@ -201,7 +201,7 @@
|
||||
<string name="reset_verb">Ripristina</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="connect_via_contact_link">Connettere via indirizzo del contatto?</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Tentativo di connessione al server usato per ricevere messaggi da questo contatto (errore: %1$s).</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">Tentativo di connessione al server usato per ricevere messaggi da questo contatto (errore: %1$s).</string>
|
||||
<string name="you_will_join_group">Ti connetterai a tutti i membri del gruppo.</string>
|
||||
<string name="connection_local_display_name">connessione %1$d</string>
|
||||
<string name="simplex_link_mode_description">Descrizione</string>
|
||||
|
||||
@@ -1063,7 +1063,7 @@
|
||||
<string name="to_verify_compare">כדי לאמת הצפנה מקצה־לקצה עם איש הקשר שלכם, יש להשוות (או לסרוק) את הקוד במכשירים שלכם.</string>
|
||||
<string name="your_chat_profiles">פרופילי צ׳אט</string>
|
||||
<string name="update_network_session_mode_question">לעדכן מצב בידוד תעבורה\?</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">מנסה להתחבר לשרת המשמש לקבלת הודעות מאיש קשר זה (שגיאה: %1$s).</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">מנסה להתחבר לשרת המשמש לקבלת הודעות מאיש קשר זה (שגיאה: %1$s).</string>
|
||||
<string name="unknown_message_format">פורמט הודעה לא ידוע</string>
|
||||
<string name="simplex_link_mode_browser">דרך הדפדפן</string>
|
||||
<string name="unmute_chat">בטל השתקה</string>
|
||||
|
||||
@@ -887,7 +887,7 @@
|
||||
<string name="to_protect_privacy_simplex_has_ids_for_queues">あなたのプライバシーを守るために、他のアプリと違って、ユーザーIDの変わりに SimpleX メッセージ束毎にIDを配布し、各連絡先が別々と扱います。</string>
|
||||
<string name="group_main_profile_sent">あなたのチャットプロフィールが他のグループメンバーに公開されます。</string>
|
||||
<string name="to_verify_compare">エンドツーエンド暗号化を確認するには、ご自分の端末と連絡先の端末のコードを比べます (スキャンします)。</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">このコンタクトから受信するメッセージのサーバに接続しようとしてます。(エラー: %1$s)。</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">このコンタクトから受信するメッセージのサーバに接続しようとしてます。(エラー: %1$s)。</string>
|
||||
<string name="connection_error_auth_desc">使用済みリンク、または連絡先による接続の削除ではなければ、バッグの可能性があります。開発者にお伝えください。
|
||||
\n繋がるには、連絡先に新しくリンクを発行してもらって、電波が安定かどうかご確認ください。</string>
|
||||
<string name="alert_text_connection_pending_they_need_to_be_online_can_delete_and_retry">接続を完了するには、連絡相手がオンラインになる必要があります。
|
||||
|
||||
@@ -1404,7 +1404,7 @@
|
||||
<string name="profile_will_be_sent_to_contact_sending_link">Jūsų profilis bus išsiųstas kontaktui iš kurio gavote šią nuorodą.</string>
|
||||
<string name="you_will_join_group">Prisijungsite prie visų grupės narių.</string>
|
||||
<string name="connected_to_server_to_receive_messages_from_contact">Esate prisijungę prie serverio skirto gauti žinutes iš šio kontakto.</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Bandoma prisijungti prie serverio skirto žinučių gavimui iš šio kontakto (klaida: %1$s).</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">Bandoma prisijungti prie serverio skirto žinučių gavimui iš šio kontakto (klaida: %1$s).</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages">Bandoma prisijungti prie serverio skirto žinučių gavimui iš šio kontakto.</string>
|
||||
<string name="no_details">nėra detalių</string>
|
||||
<string name="to_preserve_privacy_simplex_has_background_service_instead_of_push_notifications_it_uses_a_few_pc_battery"><![CDATA[Kad išlaikyti jūsų privatumą, vietoj tiesioginių pranešimų, programėlė turi <b>SimpleX fono tarnybą</b> - ji naudoja kelis procentus akumuliatoriaus per dieną.]]></string>
|
||||
|
||||
@@ -913,7 +913,7 @@
|
||||
<string name="simplex_link_mode">SimpleX links</string>
|
||||
<string name="simplex_link_invitation">Eenmalige SimpleX uitnodiging</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages">Proberen verbinding te maken met de server die wordt gebruikt om berichten van dit contact te ontvangen.</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Er wordt geprobeerd verbinding te maken met de server die wordt gebruikt om berichten van dit contact te ontvangen (fout: %1$s).</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">Er wordt geprobeerd verbinding te maken met de server die wordt gebruikt om berichten van dit contact te ontvangen (fout: %1$s).</string>
|
||||
<string name="unknown_message_format">onbekend berichtformaat</string>
|
||||
<string name="simplex_link_mode_browser">Via browser</string>
|
||||
<string name="description_via_contact_address_link">via contact adres link</string>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<string name="sending_files_not_yet_supported">wysyłanie plików nie jest jeszcze obsługiwane</string>
|
||||
<string name="receiving_files_not_yet_supported">odbieranie plików nie jest jeszcze obsługiwane</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages">Próbowanie połączenia z serwerem używanym do odbierania wiadomości od tego kontaktu.</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Próbowanie połączenia z serwerem używanym do odbierania wiadomości od tego kontaktu (błąd: %1$s).</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">Próbowanie połączenia z serwerem używanym do odbierania wiadomości od tego kontaktu (błąd: %1$s).</string>
|
||||
<string name="unknown_message_format">nieznany format wiadomości</string>
|
||||
<string name="app_name">SimpleX</string>
|
||||
<string name="sender_you_pronoun">Ty</string>
|
||||
|
||||
@@ -971,7 +971,7 @@
|
||||
<string name="tap_to_activate_profile">Toque para ativar o perfil.</string>
|
||||
<string name="unhide_chat_profile">Mostrar perfil de chat</string>
|
||||
<string name="unhide_profile">Mostrar perfil</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Tentando se conectar ao servidor utilizado para receber mensagens deste contato (erro:%1$s).</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">Tentando se conectar ao servidor utilizado para receber mensagens deste contato (erro:%1$s).</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages">Tentando se conectar ao servidor utilizado para receber mensagens deste contato.</string>
|
||||
<string name="connected_to_server_to_receive_messages_from_contact">Você está conectado ao servidor usado para receber mensagens desse contato.</string>
|
||||
<string name="smp_servers_your_server">Seu servidor</string>
|
||||
|
||||
@@ -2077,7 +2077,7 @@
|
||||
<string name="image_decoding_exception_desc">Imaginea nu poate fi decodificată. Vă rugăm să încercați o altă imagine sau să contactați dezvoltatorii.</string>
|
||||
<string name="xftp_servers_per_user">Serverele pentru fișierele noi ale profilului tău de conversații actual</string>
|
||||
<string name="alert_message_no_group">Acest grup nu mai există.</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Se încearcă conectarea la serverul folosit pentru a primi mesaje de la acest contact (eroare: %1$s).</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">Se încearcă conectarea la serverul folosit pentru a primi mesaje de la acest contact (eroare: %1$s).</string>
|
||||
<string name="updating_settings_will_reconnect_client_to_all_servers">Actualizarea setărilor va reconecta clientul la toate serverele.</string>
|
||||
<string name="this_string_is_not_a_connection_link">Acest șir de caractere nu este un link!</string>
|
||||
<string name="remote_ctrl_error_timeout">S-a atins timpul de expirare la conectarea la desktop</string>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<string name="server_error">ошибка</string>
|
||||
<string name="server_connecting">соединяется</string>
|
||||
<string name="connected_to_server_to_receive_messages_from_contact">Установлено соединение с сервером, через который Вы получаете сообщения от этого контакта.</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Устанавливается соединение с сервером, через который Вы получаете сообщения от этого контакта (ошибка: %1$s).</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">Устанавливается соединение с сервером, через который Вы получаете сообщения от этого контакта (ошибка: %1$s).</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages">Устанавливается соединение с сервером, через который Вы получаете сообщения от этого контакта.</string>
|
||||
<!-- Item Content - ChatModel.kt -->
|
||||
<string name="deleted_description">удалено</string>
|
||||
|
||||
@@ -1043,7 +1043,7 @@
|
||||
<string name="you_are_invited_to_group">คุณได้รับเชิญให้เข้าร่วมกลุ่ม</string>
|
||||
<string name="incognito_random_profile">โปรไฟล์แบบสุ่มของคุณ</string>
|
||||
<string name="theme">ธีม</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">กำลังพยายามเชื่อมต่อกับเซิร์ฟเวอร์ที่ใช้รับข้อความจากผู้ติดต่อนี้ (ข้อผิดพลาด: %1$s)</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">กำลังพยายามเชื่อมต่อกับเซิร์ฟเวอร์ที่ใช้รับข้อความจากผู้ติดต่อนี้ (ข้อผิดพลาด: %1$s)</string>
|
||||
<string name="app_name">SimpleX</string>
|
||||
<string name="connected_to_server_to_receive_messages_from_contact">คุณเชื่อมต่อกับเซิร์ฟเวอร์ที่ใช้รับข้อความจากผู้ติดต่อนี้</string>
|
||||
<string name="profile_will_be_sent_to_contact_sending_link">โปรไฟล์ของคุณจะถูกส่งไปยังผู้ติดต่อที่ส่งลิงก์นี้มาให้คุณ</string>
|
||||
|
||||
@@ -1023,7 +1023,7 @@
|
||||
<string name="v4_6_hidden_chat_profiles_descr">Sohbet profillerini parola ile koru!</string>
|
||||
<string name="v4_5_reduced_battery_usage">Daha az pil kullanımı</string>
|
||||
<string name="to_protect_privacy_simplex_has_ids_for_queues">Gizliliği korumak için, SimpleX her bir konuşma için farklı bir ID kullanır.</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Bu kişiden mesaj almak için kullanılan sunucuya bağlanılmaya çalışılıyor (hata: %1$s).</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">Bu kişiden mesaj almak için kullanılan sunucuya bağlanılmaya çalışılıyor (hata: %1$s).</string>
|
||||
<string name="v4_4_live_messages_desc">Alıcılar güncellemeleri siz yazdıkça görürler.</string>
|
||||
<string name="auth_log_in_using_credential">Bilgilerinizi kullanarak giriş yapın</string>
|
||||
<string name="markdown_in_messages">Mesajlarda Markdown</string>
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
<string name="server_error">помилка</string>
|
||||
<string name="server_connecting">підключення</string>
|
||||
<string name="connected_to_server_to_receive_messages_from_contact">Ви підключені до сервера для отримання повідомлень від цього контакту.</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Спроба підключитися до сервера для отримання повідомлень від цього контакту (помилка: %1$s).</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">Спроба підключитися до сервера для отримання повідомлень від цього контакту (помилка: %1$s).</string>
|
||||
<string name="deleted_description">видалено</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages">Спроба підключитися до сервера для отримання повідомлень від цього контакту.</string>
|
||||
<string name="marked_deleted_description">відзначено як видалено</string>
|
||||
|
||||
@@ -2034,7 +2034,7 @@
|
||||
<string name="la_notice_turn_on">Bật</string>
|
||||
<string name="servers_info_subscriptions_total">Tổng</string>
|
||||
<string name="group_member_status_unknown_short">không xác định</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Đang cố gắng kết nối tới máy chủ dùng để nhận tin nhắn từ liên hệ này (lỗi: %1$s).</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">Đang cố gắng kết nối tới máy chủ dùng để nhận tin nhắn từ liên hệ này (lỗi: %1$s).</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages">Đang cố gắng kết nối tới máy chủ dùng để nhận tin nhắn từ liên hệ này.</string>
|
||||
<string name="to_verify_compare">Để xác minh mã hóa đầu cuối với liên hệ của bạn, so sánh (hoặc quét) mã trên các thiết bị của các bạn.</string>
|
||||
<string name="network_session_mode_transport_isolation">Cách ly truyền tải</string>
|
||||
|
||||
@@ -589,7 +589,7 @@
|
||||
<string name="rcv_group_event_member_deleted">已删除 %1$s</string>
|
||||
<string name="snd_group_event_member_deleted">你删除了 %1$s</string>
|
||||
<string name="profile_will_be_sent_to_contact_sending_link">你的个人资料将发送给你收到此链接的联系人。</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">正在尝试连接到用于从该联系人接收消息的服务器(错误:%1$s)。</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">正在尝试连接到用于从该联系人接收消息的服务器(错误:%1$s)。</string>
|
||||
<string name="connected_to_server_to_receive_messages_from_contact">你已连接到用于接收该联系人消息的服务器。</string>
|
||||
<string name="description_you_shared_one_time_link">你分享了一次性链接</string>
|
||||
<string name="message_delivery_error_desc">很可能此联系人已经删除了与你的联系。</string>
|
||||
|
||||
@@ -238,7 +238,7 @@
|
||||
<string name="server_error">錯誤</string>
|
||||
<string name="server_connecting">連接中</string>
|
||||
<string name="connected_to_server_to_receive_messages_from_contact">你已連接到此聯絡人使用的伺服器以接收訊息。</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">嘗試連接至用於接收此聯絡人訊息的伺服器 (錯誤:%1$s)。</string>
|
||||
<string name="error_connecting_to_server_to_receive_messages">嘗試連接至用於接收此聯絡人訊息的伺服器 (錯誤:%1$s)。</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages">正在嘗試連接到用於接收此聯絡人訊息伺服器。</string>
|
||||
<string name="deleted_description">已刪除</string>
|
||||
<string name="marked_deleted_description">已標記為已刪除</string>
|
||||
|
||||
@@ -47,6 +47,7 @@ This file is generated automatically.
|
||||
- [ChatType](#chattype)
|
||||
- [ChatWallpaper](#chatwallpaper)
|
||||
- [ChatWallpaperScale](#chatwallpaperscale)
|
||||
- [ClientNotice](#clientnotice)
|
||||
- [Color](#color)
|
||||
- [CommandError](#commanderror)
|
||||
- [CommandErrorType](#commanderrortype)
|
||||
@@ -288,6 +289,12 @@ AGENT:
|
||||
- type: "AGENT"
|
||||
- agentErr: [SMPAgentError](#smpagenterror)
|
||||
|
||||
NOTICE:
|
||||
- type: "NOTICE"
|
||||
- server: string
|
||||
- preset: bool
|
||||
- expiresAt: UTCTime?
|
||||
|
||||
INTERNAL:
|
||||
- type: "INTERNAL"
|
||||
- internalErr: string
|
||||
@@ -315,6 +322,7 @@ INACTIVE:
|
||||
|
||||
**Record type**:
|
||||
- reason: [BlockingReason](#blockingreason)
|
||||
- notice: [ClientNotice](#clientnotice)?
|
||||
|
||||
|
||||
---
|
||||
@@ -1378,6 +1386,14 @@ self == 'direct' ? '@' : self == 'group' ? '#' : self == 'local' ? '*' : '' // J
|
||||
- "repeat"
|
||||
|
||||
|
||||
---
|
||||
|
||||
## ClientNotice
|
||||
|
||||
**Record type**:
|
||||
- ttl: int64?
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Color
|
||||
|
||||
@@ -349,7 +349,6 @@ undocumentedCommands =
|
||||
"APIGetContactCode",
|
||||
"APIGetGroupMemberCode",
|
||||
"APIGetNetworkConfig",
|
||||
"APIGetNetworkStatuses",
|
||||
"APIGetNtfConns",
|
||||
"APIGetNtfToken",
|
||||
"APIGetReactionMembers",
|
||||
|
||||
@@ -180,7 +180,7 @@ undocumentedEvents =
|
||||
"CEvtGroupMemberSwitch",
|
||||
"CEvtHostConnected",
|
||||
"CEvtHostDisconnected",
|
||||
"CEvtNetworkStatus",
|
||||
"CEvtSubscriptionStatus",
|
||||
"CEvtNewRemoteHost",
|
||||
"CEvtNoMemberContactCreating",
|
||||
"CEvtNtfMessage",
|
||||
|
||||
@@ -167,7 +167,6 @@ undocumentedResponses =
|
||||
"CRMemberSupportChatDeleted",
|
||||
"CRMemberSupportChats",
|
||||
"CRNetworkConfig",
|
||||
"CRNetworkStatuses",
|
||||
"CRNewMemberContact",
|
||||
"CRNewMemberContactSentInv",
|
||||
"CRMemberContactAccepted",
|
||||
|
||||
@@ -42,6 +42,7 @@ import Simplex.Messaging.Client
|
||||
import Simplex.Messaging.Crypto.File
|
||||
import Simplex.Messaging.Parsers (dropPrefix, fstToLower)
|
||||
import Simplex.Messaging.Protocol (BlockingInfo (..), BlockingReason (..), CommandError (..), ErrorType (..), NetworkError (..), ProxyError (..))
|
||||
import Simplex.Messaging.Protocol.Types (ClientNotice (..))
|
||||
import Simplex.Messaging.Transport
|
||||
import Simplex.RemoteControl.Types
|
||||
import System.Console.ANSI.Types (Color (..))
|
||||
@@ -228,6 +229,7 @@ chatTypesDocsData =
|
||||
(sti @CIMentionMember, STRecord, "", [], "", ""),
|
||||
(sti @CIReactionCount, STRecord, "", [], "", ""),
|
||||
(sti @CITimed, STRecord, "", [], "", ""),
|
||||
(sti @ClientNotice, STRecord, "", [], "", ""),
|
||||
(sti @Color, STEnum, "", [], "", ""),
|
||||
(sti @CommandError, STUnion, "", [], "", ""),
|
||||
(sti @CommandErrorType, STUnion, "", [], "", ""),
|
||||
@@ -413,6 +415,7 @@ deriving instance Generic CIMention
|
||||
deriving instance Generic CIMentionMember
|
||||
deriving instance Generic CIReactionCount
|
||||
deriving instance Generic CITimed
|
||||
deriving instance Generic ClientNotice
|
||||
deriving instance Generic Color
|
||||
deriving instance Generic CommandError
|
||||
deriving instance Generic CommandErrorType
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
|
||||
source-repository-package
|
||||
type: git
|
||||
location: https://github.com/simplex-chat/simplexmq.git
|
||||
tag: 1dbc15b2e6225c0e254564747bc8412970273e85
|
||||
tag: 1329fc726ffb2e773935ad10f024a137dd887867
|
||||
|
||||
source-repository-package
|
||||
type: git
|
||||
|
||||
@@ -65,6 +65,7 @@ export type AgentErrorType =
|
||||
| AgentErrorType.RCP
|
||||
| AgentErrorType.BROKER
|
||||
| AgentErrorType.AGENT
|
||||
| AgentErrorType.NOTICE
|
||||
| AgentErrorType.INTERNAL
|
||||
| AgentErrorType.CRITICAL
|
||||
| AgentErrorType.INACTIVE
|
||||
@@ -82,6 +83,7 @@ export namespace AgentErrorType {
|
||||
| "RCP"
|
||||
| "BROKER"
|
||||
| "AGENT"
|
||||
| "NOTICE"
|
||||
| "INTERNAL"
|
||||
| "CRITICAL"
|
||||
| "INACTIVE"
|
||||
@@ -152,6 +154,13 @@ export namespace AgentErrorType {
|
||||
agentErr: SMPAgentError
|
||||
}
|
||||
|
||||
export interface NOTICE extends Interface {
|
||||
type: "NOTICE"
|
||||
server: string
|
||||
preset: boolean
|
||||
expiresAt?: string // ISO-8601 timestamp
|
||||
}
|
||||
|
||||
export interface INTERNAL extends Interface {
|
||||
type: "INTERNAL"
|
||||
internalErr: string
|
||||
@@ -174,6 +183,7 @@ export interface AutoAccept {
|
||||
|
||||
export interface BlockingInfo {
|
||||
reason: BlockingReason
|
||||
notice?: ClientNotice
|
||||
}
|
||||
|
||||
export enum BlockingReason {
|
||||
@@ -1587,6 +1597,10 @@ export enum ChatWallpaperScale {
|
||||
Repeat = "repeat",
|
||||
}
|
||||
|
||||
export interface ClientNotice {
|
||||
ttl?: number // int64
|
||||
}
|
||||
|
||||
export enum Color {
|
||||
Black = "black",
|
||||
Red = "red",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"https://github.com/simplex-chat/simplexmq.git"."1dbc15b2e6225c0e254564747bc8412970273e85" = "03hmlynixssyp0720h984slw4lkrzn3kr63k3mah50lbyxzsmnrs";
|
||||
"https://github.com/simplex-chat/simplexmq.git"."1329fc726ffb2e773935ad10f024a137dd887867" = "0wlpwr464i8dif5a94mfx31y3fm44gkc3h357dx8l1ii9q3sy05i";
|
||||
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
|
||||
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
|
||||
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
|
||||
|
||||
@@ -87,7 +87,6 @@ library
|
||||
Simplex.Chat.Types.Preferences
|
||||
Simplex.Chat.Types.Shared
|
||||
Simplex.Chat.Types.UITheme
|
||||
Simplex.Chat.Types.Util
|
||||
Simplex.Chat.Util
|
||||
if !flag(client_library)
|
||||
exposed-modules:
|
||||
|
||||
+1
-3
@@ -149,7 +149,6 @@ newChatController
|
||||
eventSeq <- newTVarIO 0
|
||||
inputQ <- newTBQueueIO tbqSize
|
||||
outputQ <- newTBQueueIO tbqSize
|
||||
connNetworkStatuses <- TM.emptyIO
|
||||
subscriptionMode <- newTVarIO SMSubscribe
|
||||
chatLock <- newEmptyTMVarIO
|
||||
entityLocks <- TM.emptyIO
|
||||
@@ -191,7 +190,6 @@ newChatController
|
||||
eventSeq,
|
||||
inputQ,
|
||||
outputQ,
|
||||
connNetworkStatuses,
|
||||
subscriptionMode,
|
||||
chatLock,
|
||||
entityLocks,
|
||||
@@ -252,7 +250,7 @@ newChatController
|
||||
ops <- getUpdateServerOperators db presetOps (null users)
|
||||
let opDomains = operatorDomains $ mapMaybe snd ops
|
||||
(smp', xftp') <- unzip <$> mapM (getServers ops opDomains) users
|
||||
pure InitialAgentServers {smp = M.fromList (optServers smp' smpServers), xftp = M.fromList (optServers xftp' xftpServers), ntf, netCfg, presetDomains}
|
||||
pure InitialAgentServers {smp = M.fromList (optServers smp' smpServers), xftp = M.fromList (optServers xftp' xftpServers), ntf, netCfg, presetDomains, presetServers = L.toList allPresetServers}
|
||||
where
|
||||
optServers :: [(UserId, NonEmpty (ServerCfg p))] -> [ProtoServerWithAuth p] -> [(UserId, NonEmpty (ServerCfg p))]
|
||||
optServers srvs overrides_ = case L.nonEmpty overrides_ of
|
||||
|
||||
@@ -231,7 +231,6 @@ data ChatController = ChatController
|
||||
eventSeq :: TVar Int,
|
||||
inputQ :: TBQueue String,
|
||||
outputQ :: TBQueue (Maybe RemoteHostId, Either ChatError ChatEvent),
|
||||
connNetworkStatuses :: TMap AgentConnId NetworkStatus,
|
||||
subscriptionMode :: TVar SubscriptionMode,
|
||||
chatLock :: Lock,
|
||||
entityLocks :: TMap ChatLockEntity Lock,
|
||||
@@ -354,7 +353,6 @@ data ChatCommand
|
||||
| APIEndCall ContactId
|
||||
| APIGetCallInvitations
|
||||
| APICallStatus ContactId WebRTCCallStatus
|
||||
| APIGetNetworkStatuses
|
||||
| APIUpdateProfile {userId :: UserId, profile :: Profile}
|
||||
| APISetContactPrefs {contactId :: ContactId, preferences :: Preferences}
|
||||
| APISetContactAlias {contactId :: ContactId, localAlias :: LocalAlias}
|
||||
@@ -729,7 +727,6 @@ data ChatResponse
|
||||
| CRGroupAliasUpdated {user :: User, toGroup :: GroupInfo}
|
||||
| CRConnectionAliasUpdated {user :: User, toConnection :: PendingContactConnection}
|
||||
| CRContactPrefsUpdated {user :: User, fromContact :: Contact, toContact :: Contact}
|
||||
| CRNetworkStatuses {user_ :: Maybe User, networkStatuses :: [ConnNetworkStatus]}
|
||||
| CRJoinedGroupMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember}
|
||||
| CRMemberAccepted {user :: User, groupInfo :: GroupInfo, member :: GroupMember}
|
||||
| CRMemberSupportChatRead {user :: User, groupInfo :: GroupInfo, member :: GroupMember}
|
||||
@@ -829,7 +826,7 @@ data ChatEvent
|
||||
| CEvtContactAnotherClient {user :: User, contact :: Contact}
|
||||
| CEvtConnectionsDiff {userIds :: DatabaseDiff AgentUserId, connIds :: DatabaseDiff AgentConnId}
|
||||
| CEvtSubscriptionEnd {user :: User, connectionEntity :: ConnectionEntity}
|
||||
| CEvtNetworkStatus {server :: SMPServer, networkStatus :: NetworkStatus, connections :: [AgentConnId]}
|
||||
| CEvtSubscriptionStatus {server :: SMPServer, subscriptionStatus :: SubscriptionStatus, connections :: [AgentConnId]}
|
||||
| CEvtHostConnected {protocol :: AProtocolType, transportHost :: TransportHost}
|
||||
| CEvtHostDisconnected {protocol :: AProtocolType, transportHost :: TransportHost}
|
||||
| CEvtReceivedGroupInvitation {user :: User, groupInfo :: GroupInfo, contact :: Contact, fromMemberRole :: GroupMemberRole, memberRole :: GroupMemberRole}
|
||||
@@ -909,7 +906,7 @@ allowRemoteEvent = \case
|
||||
|
||||
logEventToFile :: ChatEvent -> Bool
|
||||
logEventToFile = \case
|
||||
CEvtNetworkStatus {} -> True
|
||||
CEvtSubscriptionStatus {} -> True
|
||||
CEvtHostConnected {} -> True
|
||||
CEvtHostDisconnected {} -> True
|
||||
CEvtConnectionDisabled {} -> True
|
||||
@@ -1472,10 +1469,6 @@ chatModifyVar' :: (ChatController -> TVar a) -> (a -> a) -> CM' ()
|
||||
chatModifyVar' f newValue = asks f >>= atomically . (`modifyTVar'` newValue)
|
||||
{-# INLINE chatModifyVar' #-}
|
||||
|
||||
setContactNetworkStatus :: Contact -> NetworkStatus -> CM' ()
|
||||
setContactNetworkStatus Contact {activeConn = Nothing} _ = pure ()
|
||||
setContactNetworkStatus Contact {activeConn = Just Connection {agentConnId}} status = chatModifyVar' connNetworkStatuses $ M.insert agentConnId status
|
||||
|
||||
onChatError :: CM a -> CM b -> CM a
|
||||
a `onChatError` onErr = a `catchAllErrors` \e -> onErr >> throwError e
|
||||
{-# INLINE onChatError #-}
|
||||
|
||||
@@ -1368,8 +1368,6 @@ processChatCommand vr nm = \case
|
||||
user <- getUserByContactId db contactId
|
||||
contact <- getContact db vr user contactId
|
||||
pure RcvCallInvitation {user, contact, callType = peerCallType, sharedKey, callUUID, callTs}
|
||||
APIGetNetworkStatuses -> withUser $ \_ ->
|
||||
CRNetworkStatuses Nothing . map (uncurry ConnNetworkStatus) . M.toList <$> chatReadVar connNetworkStatuses
|
||||
APICallStatus contactId receivedStatus ->
|
||||
withCurrentCall contactId $ \user ct call ->
|
||||
updateCallItemStatus user ct call receivedStatus Nothing $> Just call
|
||||
@@ -1777,7 +1775,7 @@ processChatCommand vr nm = \case
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
let userData = contactShortLinkData (userProfileDirect user incognitoProfile Nothing True) Nothing
|
||||
-- TODO [certs rcv]
|
||||
(connId, (ccLink, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId user) True SCMInvitation (Just userData) Nothing IKPQOn subMode
|
||||
(connId, (ccLink, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId user) True False SCMInvitation (Just userData) Nothing IKPQOn subMode
|
||||
ccLink' <- shortenCreatedLink ccLink
|
||||
-- TODO PQ pass minVersion from the current range
|
||||
conn <- withFastStore' $ \db -> createDirectConnection db user connId ccLink' Nothing ConnNew incognitoProfile subMode initialChatVersion PQSupportOn
|
||||
@@ -1819,7 +1817,7 @@ processChatCommand vr nm = \case
|
||||
| short = Just $ contactShortLinkData (userProfileDirect newUser Nothing Nothing True) Nothing
|
||||
| otherwise = Nothing
|
||||
-- TODO [certs rcv]
|
||||
(agConnId, (ccLink, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId newUser) True SCMInvitation userData_ Nothing IKPQOn subMode
|
||||
(agConnId, (ccLink, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId newUser) True False SCMInvitation userData_ Nothing IKPQOn subMode
|
||||
ccLink' <- shortenCreatedLink ccLink
|
||||
conn' <- withFastStore' $ \db -> do
|
||||
deleteConnectionRecord db user connId
|
||||
@@ -2011,7 +2009,7 @@ processChatCommand vr nm = \case
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
let userData = contactShortLinkData (userProfileDirect user Nothing Nothing True) Nothing
|
||||
-- TODO [certs rcv]
|
||||
(connId, (ccLink, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId user) True SCMContact (Just userData) Nothing IKPQOn subMode
|
||||
(connId, (ccLink, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId user) True True SCMContact (Just userData) Nothing IKPQOn subMode
|
||||
ccLink' <- shortenCreatedLink ccLink
|
||||
withFastStore $ \db -> createUserContactLink db user connId ccLink' subMode
|
||||
pure $ CRUserContactLinkCreated user ccLink'
|
||||
@@ -2231,7 +2229,7 @@ processChatCommand vr nm = \case
|
||||
gVar <- asks random
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
-- TODO [certs rcv]
|
||||
(agentConnId, (CCLink cReq _, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId user) True SCMInvitation Nothing Nothing IKPQOff subMode
|
||||
(agentConnId, (CCLink cReq _, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId user) True False SCMInvitation Nothing Nothing IKPQOff subMode
|
||||
member <- withFastStore $ \db -> createNewContactMember db gVar user gInfo contact memRole agentConnId cReq subMode
|
||||
sendInvitation member cReq
|
||||
pure $ CRSentGroupInvitation user gInfo contact member
|
||||
@@ -2635,7 +2633,7 @@ processChatCommand vr nm = \case
|
||||
let userData = encodeShortLinkData $ GroupShortLinkData groupProfile
|
||||
crClientData = encodeJSON $ CRDataGroup groupLinkId
|
||||
-- TODO [certs rcv]
|
||||
(connId, (ccLink, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId user) True SCMContact (Just userData) (Just crClientData) IKPQOff subMode
|
||||
(connId, (ccLink, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId user) True True SCMContact (Just userData) (Just crClientData) IKPQOff subMode
|
||||
ccLink' <- createdGroupLink <$> shortenCreatedLink ccLink
|
||||
gVar <- asks random
|
||||
gLink <- withFastStore $ \db -> createGroupLink db gVar user gInfo connId ccLink' groupLinkId mRole subMode
|
||||
@@ -2676,12 +2674,11 @@ processChatCommand vr nm = \case
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
-- TODO PQ should negotitate contact connection with PQSupportOn?
|
||||
-- TODO [certs rcv]
|
||||
(connId, (CCLink cReq _, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId user) True SCMInvitation Nothing Nothing IKPQOff subMode
|
||||
(connId, (CCLink cReq _, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId user) True False SCMInvitation Nothing Nothing IKPQOff subMode
|
||||
-- [incognito] reuse membership incognito profile
|
||||
ct <- withFastStore' $ \db -> createMemberContact db user connId cReq g m mConn subMode
|
||||
void $ createChatItem user (CDDirectSnd ct) False CIChatBanner Nothing (Just epochStart)
|
||||
-- TODO not sure it is correct to set connections status here?
|
||||
lift $ setContactNetworkStatus ct NSConnected
|
||||
pure $ CRNewMemberContact user ct g m
|
||||
_ -> throwChatError CEGroupMemberNotActive
|
||||
APISendMemberContactInvitation contactId msgContent_ -> withUser $ \user -> do
|
||||
@@ -4392,7 +4389,6 @@ chatCommandP =
|
||||
"/_call end @" *> (APIEndCall <$> A.decimal),
|
||||
"/_call status @" *> (APICallStatus <$> A.decimal <* A.space <*> strP),
|
||||
"/_call get" $> APIGetCallInvitations,
|
||||
"/_network_statuses" $> APIGetNetworkStatuses,
|
||||
"/_profile " *> (APIUpdateProfile <$> A.decimal <* A.space <*> jsonP),
|
||||
"/_set alias @" *> (APISetContactAlias <$> A.decimal <*> (A.space *> textP <|> pure "")),
|
||||
"/_set alias #" *> (APISetGroupAlias <$> A.decimal <*> (A.space *> textP <|> pure "")),
|
||||
|
||||
@@ -131,19 +131,16 @@ processAgentMessageNoConn :: AEvent 'AENone -> CM ()
|
||||
processAgentMessageNoConn = \case
|
||||
CONNECT p h -> hostEvent $ CEvtHostConnected p h
|
||||
DISCONNECT p h -> hostEvent $ CEvtHostDisconnected p h
|
||||
DOWN srv conns -> serverEvent srv NSDisconnected conns
|
||||
UP srv conns -> serverEvent srv NSConnected conns
|
||||
DOWN srv conns -> serverEvent srv SSPending conns
|
||||
UP srv conns -> serverEvent srv SSActive conns
|
||||
SUSPENDED -> toView CEvtChatSuspended
|
||||
DEL_USER agentUserId -> toView $ CEvtAgentUserDeleted agentUserId
|
||||
ERRS cErrs -> errsEvent $ L.toList cErrs
|
||||
where
|
||||
hostEvent :: ChatEvent -> CM ()
|
||||
hostEvent = whenM (asks $ hostEvents . config) . toView
|
||||
serverEvent srv nsStatus conns = do
|
||||
chatModifyVar connNetworkStatuses $ \m -> foldl' (\m' cId -> M.insert cId nsStatus m') m connIds
|
||||
toView $ CEvtNetworkStatus srv nsStatus connIds
|
||||
where
|
||||
connIds = map AgentConnId conns
|
||||
serverEvent :: SMPServer -> SubscriptionStatus -> [ConnId] -> CM ()
|
||||
serverEvent srv nsStatus conns = toView $ CEvtSubscriptionStatus srv nsStatus $ map AgentConnId conns
|
||||
errsEvent :: [(ConnId, AgentErrorType)] -> CM ()
|
||||
errsEvent = toView . CEvtChatErrors . map (\(cId, e) -> ChatErrorAgent e (AgentConnId cId) Nothing)
|
||||
|
||||
@@ -559,7 +556,6 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
ct' = ct {activeConn = Just conn'} :: Contact
|
||||
-- [incognito] print incognito profile used for this contact
|
||||
incognitoProfile <- forM customUserProfileId $ \profileId -> withStore (\db -> getProfileById db userId profileId)
|
||||
lift $ setContactNetworkStatus ct' NSConnected
|
||||
toView $ CEvtContactConnected user ct' (fmap fromLocalProfile incognitoProfile)
|
||||
let createE2EItem = createInternalChatItem user (CDDirectRcv ct') (CIRcvDirectE2EEInfo $ E2EInfo $ Just pqEnc) Nothing
|
||||
-- TODO [short links] get contact request by contactRequestId, check encryption (UserContactRequest.pqSupport)?
|
||||
@@ -640,7 +636,6 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
-- [async agent commands] continuation on receiving JOINED
|
||||
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData ->
|
||||
when (directOrUsed ct && sqSecured) $ do
|
||||
lift $ setContactNetworkStatus ct NSConnected
|
||||
toView $ CEvtContactSndReady user ct
|
||||
when (connChatVersion >= batchSend2Version) $ forM_ viaUserContactLink $ \userContactLinkId -> do
|
||||
(ucl, _) <- withStore $ \db -> getUserContactLinkById db userId userContactLinkId
|
||||
@@ -1455,7 +1450,6 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
notifyMemberConnected gInfo m ct_ = do
|
||||
(gInfo', m', scopeInfo) <- mkGroupChatScope gInfo m
|
||||
memberConnectedChatItem gInfo' scopeInfo m'
|
||||
lift $ mapM_ (`setContactNetworkStatus` NSConnected) ct_
|
||||
toView $ CEvtConnectedToGroupMember user gInfo' m' ct_
|
||||
|
||||
probeMatchingMembers :: Contact -> IncognitoEnabled -> CM ()
|
||||
|
||||
@@ -49,7 +49,6 @@ import Simplex.Chat.Protocol
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Chat.Types.Preferences
|
||||
import Simplex.Chat.Types.Shared
|
||||
import Simplex.Chat.Types.Util (textParseJSON)
|
||||
import Simplex.Messaging.Agent.Protocol (AgentMsgId, MsgMeta (..), MsgReceiptStatus (..))
|
||||
import Simplex.Messaging.Agent.Store.DB (fromTextField_)
|
||||
import Simplex.Messaging.Crypto.File (CryptoFile (..))
|
||||
|
||||
@@ -46,7 +46,6 @@ import Data.Time.Clock (UTCTime, nominalDay)
|
||||
import Language.Haskell.TH.Syntax (lift)
|
||||
import Simplex.Chat.Operators.Conditions
|
||||
import Simplex.Chat.Types (User)
|
||||
import Simplex.Chat.Types.Util (textParseJSON)
|
||||
import Simplex.Messaging.Agent.Env.SQLite (ServerCfg (..), ServerRoles (..), allRoles)
|
||||
import Simplex.Messaging.Agent.Store.DB (FromField (..), ToField (..), fromTextField_)
|
||||
import Simplex.Messaging.Agent.Store.Entity
|
||||
|
||||
@@ -395,7 +395,7 @@ Query:
|
||||
JOIN connections c ON q.conn_id = c.conn_id
|
||||
WHERE c.deleted = 0 AND q.deleted = 0
|
||||
Plan:
|
||||
SCAN q USING INDEX idx_rcv_queues_link_id
|
||||
SCAN q USING INDEX idx_rcv_queues_client_notice_id
|
||||
SEARCH c USING PRIMARY KEY (conn_id=?)
|
||||
SEARCH s USING PRIMARY KEY (host=? AND port=?)
|
||||
USE TEMP B-TREE FOR DISTINCT
|
||||
@@ -477,6 +477,16 @@ Query:
|
||||
Plan:
|
||||
SEARCH inv_short_links USING INDEX idx_inv_short_links_link_id (host=? AND port=? AND link_id=?)
|
||||
|
||||
Query:
|
||||
SELECT n.host, n.port, n.entity_id, COALESCE(n.server_key_hash, s.key_hash), n.created_at, n.notice_ttl
|
||||
FROM client_notices n
|
||||
JOIN servers s ON n.host = s.host AND n.port = s.port
|
||||
WHERE n.protocol = 'smp'
|
||||
|
||||
Plan:
|
||||
SEARCH n USING INDEX idx_client_notices_entity (protocol=?)
|
||||
SEARCH s USING PRIMARY KEY (host=? AND port=?)
|
||||
|
||||
Query:
|
||||
SELECT s.internal_id, m.msg_type, s.internal_hash, s.rcpt_internal_id, s.rcpt_status
|
||||
FROM snd_messages s
|
||||
@@ -793,7 +803,7 @@ SEARCH s USING PRIMARY KEY (host=? AND port=?)
|
||||
|
||||
Query:
|
||||
SELECT c.user_id, COALESCE(q.server_key_hash, s.key_hash), q.conn_id, q.host, q.port, q.rcv_id, q.rcv_private_key, q.rcv_dh_secret,
|
||||
q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.queue_mode, q.status, c.enable_ntfs,
|
||||
q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.queue_mode, q.status, c.enable_ntfs, q.client_notice_id,
|
||||
q.rcv_queue_id, q.rcv_primary, q.replace_rcv_queue_id, q.switch_status, q.smp_client_version, q.delete_errors,
|
||||
q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret,
|
||||
q.link_id, q.link_key, q.link_priv_sig_key, q.link_enc_fixed_data
|
||||
@@ -808,7 +818,7 @@ SEARCH s USING PRIMARY KEY (host=? AND port=?)
|
||||
|
||||
Query:
|
||||
SELECT c.user_id, COALESCE(q.server_key_hash, s.key_hash), q.conn_id, q.host, q.port, q.rcv_id, q.rcv_private_key, q.rcv_dh_secret,
|
||||
q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.queue_mode, q.status, c.enable_ntfs,
|
||||
q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.queue_mode, q.status, c.enable_ntfs, q.client_notice_id,
|
||||
q.rcv_queue_id, q.rcv_primary, q.replace_rcv_queue_id, q.switch_status, q.smp_client_version, q.delete_errors,
|
||||
q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret,
|
||||
q.link_id, q.link_key, q.link_priv_sig_key, q.link_enc_fixed_data
|
||||
@@ -823,7 +833,7 @@ SEARCH c USING PRIMARY KEY (conn_id=?)
|
||||
|
||||
Query:
|
||||
SELECT c.user_id, COALESCE(q.server_key_hash, s.key_hash), q.conn_id, q.host, q.port, q.rcv_id, q.rcv_private_key, q.rcv_dh_secret,
|
||||
q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.queue_mode, q.status, c.enable_ntfs,
|
||||
q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.queue_mode, q.status, c.enable_ntfs, q.client_notice_id,
|
||||
q.rcv_queue_id, q.rcv_primary, q.replace_rcv_queue_id, q.switch_status, q.smp_client_version, q.delete_errors,
|
||||
q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret,
|
||||
q.link_id, q.link_key, q.link_priv_sig_key, q.link_enc_fixed_data
|
||||
@@ -838,7 +848,7 @@ SEARCH c USING PRIMARY KEY (conn_id=?)
|
||||
|
||||
Query:
|
||||
SELECT c.user_id, COALESCE(q.server_key_hash, s.key_hash), q.conn_id, q.host, q.port, q.rcv_id, q.rcv_private_key, q.rcv_dh_secret,
|
||||
q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.queue_mode, q.status, c.enable_ntfs,
|
||||
q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.queue_mode, q.status, c.enable_ntfs, q.client_notice_id,
|
||||
q.rcv_queue_id, q.rcv_primary, q.replace_rcv_queue_id, q.switch_status, q.smp_client_version, q.delete_errors,
|
||||
q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret,
|
||||
q.link_id, q.link_key, q.link_priv_sig_key, q.link_enc_fixed_data
|
||||
@@ -853,7 +863,7 @@ SEARCH s USING PRIMARY KEY (host=? AND port=?)
|
||||
|
||||
Query:
|
||||
SELECT c.user_id, COALESCE(q.server_key_hash, s.key_hash), q.conn_id, q.host, q.port, q.rcv_id, q.rcv_private_key, q.rcv_dh_secret,
|
||||
q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.queue_mode, q.status, c.enable_ntfs,
|
||||
q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.queue_mode, q.status, c.enable_ntfs, q.client_notice_id,
|
||||
q.rcv_queue_id, q.rcv_primary, q.replace_rcv_queue_id, q.switch_status, q.smp_client_version, q.delete_errors,
|
||||
q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret,
|
||||
q.link_id, q.link_key, q.link_priv_sig_key, q.link_enc_fixed_data
|
||||
@@ -867,7 +877,7 @@ SEARCH s USING PRIMARY KEY (host=? AND port=?)
|
||||
SEARCH c USING PRIMARY KEY (conn_id=?)
|
||||
|
||||
Query:
|
||||
SELECT c.user_id, q.conn_id, q.host, q.port, COALESCE(q.server_key_hash, s.key_hash), q.rcv_id, q.rcv_private_key, q.status, c.enable_ntfs,
|
||||
SELECT c.user_id, q.conn_id, q.host, q.port, COALESCE(q.server_key_hash, s.key_hash), q.rcv_id, q.rcv_private_key, q.status, c.enable_ntfs, q.client_notice_id,
|
||||
q.rcv_queue_id, q.rcv_primary, q.replace_rcv_queue_id
|
||||
FROM rcv_queues q
|
||||
JOIN servers s ON q.host = s.host AND q.port = s.port
|
||||
@@ -879,7 +889,7 @@ SEARCH q USING PRIMARY KEY (host=? AND port=?)
|
||||
SEARCH c USING PRIMARY KEY (conn_id=?)
|
||||
|
||||
Query:
|
||||
SELECT c.user_id, q.conn_id, q.host, q.port, COALESCE(q.server_key_hash, s.key_hash), q.rcv_id, q.rcv_private_key, q.status, c.enable_ntfs,
|
||||
SELECT c.user_id, q.conn_id, q.host, q.port, COALESCE(q.server_key_hash, s.key_hash), q.rcv_id, q.rcv_private_key, q.status, c.enable_ntfs, q.client_notice_id,
|
||||
q.rcv_queue_id, q.rcv_primary, q.replace_rcv_queue_id
|
||||
FROM rcv_queues q
|
||||
JOIN servers s ON q.host = s.host AND q.port = s.port
|
||||
|
||||
@@ -49,7 +49,6 @@ import Data.Word (Word16)
|
||||
import Simplex.Chat.Types.Preferences
|
||||
import Simplex.Chat.Types.Shared
|
||||
import Simplex.Chat.Types.UITheme
|
||||
import Simplex.Chat.Types.Util
|
||||
import Simplex.FileTransfer.Description (FileDigest)
|
||||
import Simplex.FileTransfer.Types (RcvFileId, SndFileId)
|
||||
import Simplex.Messaging.Agent.Protocol (ACorrId, ACreatedConnLink, AEventTag (..), AEvtTag (..), ConnId, ConnShortLink, ConnectionLink, ConnectionMode (..), ConnectionRequestUri, CreatedConnLink, InvitationId, SAEntity (..), UserId)
|
||||
@@ -1789,26 +1788,6 @@ serializeIntroStatus = \case
|
||||
GMIntroToConnected -> "to-con"
|
||||
GMIntroConnected -> "con"
|
||||
|
||||
data NetworkStatus
|
||||
= NSUnknown
|
||||
| NSConnected
|
||||
| NSDisconnected
|
||||
| NSError {connectionError :: String}
|
||||
deriving (Eq, Ord, Show)
|
||||
|
||||
netStatusStr :: NetworkStatus -> String
|
||||
netStatusStr = \case
|
||||
NSUnknown -> "unknown"
|
||||
NSConnected -> "subscribed"
|
||||
NSDisconnected -> "disconnected"
|
||||
NSError e -> "error: " <> e
|
||||
|
||||
data ConnNetworkStatus = ConnNetworkStatus
|
||||
{ agentConnId :: AgentConnId,
|
||||
networkStatus :: NetworkStatus
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
type CommandId = Int64
|
||||
|
||||
aCorrId :: CommandId -> ACorrId
|
||||
@@ -1998,10 +1977,6 @@ $(JQ.deriveJSON defaultJSON ''GroupMemberSettings)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''SecurityCode)
|
||||
|
||||
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "NS") ''NetworkStatus)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''ConnNetworkStatus)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''Connection)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''PendingContactConnection)
|
||||
|
||||
@@ -14,7 +14,6 @@ import Data.Char (toLower)
|
||||
import Data.Maybe (fromMaybe)
|
||||
import Data.Text (Text)
|
||||
import Simplex.Chat.Options.DB (FromField (..), ToField (..))
|
||||
import Simplex.Chat.Types.Util
|
||||
import Simplex.Messaging.Agent.Store.DB (fromTextField_)
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON)
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
module Simplex.Chat.Types.Util where
|
||||
|
||||
import qualified Data.Aeson as J
|
||||
import qualified Data.Aeson.Types as JT
|
||||
import Simplex.Messaging.Encoding.String
|
||||
|
||||
textParseJSON :: TextEncoding a => String -> J.Value -> JT.Parser a
|
||||
textParseJSON name = J.withText name $ maybe (fail $ "bad " <> name) pure . textDecode
|
||||
@@ -232,7 +232,6 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, testView} liveIte
|
||||
CRRcvStandaloneFileCreated u ft -> ttyUser u $ receivingFileStandalone "started" ft
|
||||
CRSndStandaloneFileCreated u ft -> ttyUser u $ uploadingFileStandalone "started" ft
|
||||
CRStandaloneFileInfo info_ -> maybe ["no file information in URI"] (\j -> [viewJSON j]) info_
|
||||
CRNetworkStatuses u statuses -> if testView then ttyUser' u $ viewNetworkStatuses statuses else []
|
||||
CRJoinedGroupMember u g m -> ttyUser u $ viewJoinedGroupMember g m
|
||||
CRMemberAccepted u g m -> ttyUser u $ viewMemberAccepted g m
|
||||
CRMemberSupportChatRead u g m -> ttyUser u $ viewSupportChatRead g m
|
||||
@@ -457,7 +456,7 @@ chatEventToView hu ChatConfig {logLevel, showReactions, showReceipts, testView}
|
||||
CEvtSubscriptionEnd u acEntity ->
|
||||
let Connection {connId} = entityConnection acEntity
|
||||
in ttyUser u [sShow connId <> ": END"]
|
||||
CEvtNetworkStatus srv status conns -> [plain $ netStatusStr status <> " " <> show (length conns) <> " connections on server " <> showSMPServer srv]
|
||||
CEvtSubscriptionStatus srv status conns -> [plain $ subStatusStr status <> " " <> show (length conns) <> " connections on server " <> showSMPServer srv]
|
||||
CEvtReceivedGroupInvitation {user = u, groupInfo = g, contact = c, memberRole = r} -> ttyUser u $ viewReceivedGroupInvitation g c r
|
||||
CEvtUserJoinedGroup u g _ -> ttyUser u $ viewUserJoinedGroup g
|
||||
CEvtJoinedGroupMember u g m -> ttyUser u $ viewJoinedGroupMember g m
|
||||
@@ -1174,12 +1173,6 @@ viewDirectMessagesProhibited :: MsgDirection -> Contact -> [StyledString]
|
||||
viewDirectMessagesProhibited MDSnd c = ["direct messages to indirect contact " <> ttyContact' c <> " are prohibited"]
|
||||
viewDirectMessagesProhibited MDRcv c = ["received prohibited direct message from indirect contact " <> ttyContact' c <> " (discarded)"]
|
||||
|
||||
viewNetworkStatuses :: [ConnNetworkStatus] -> [StyledString]
|
||||
viewNetworkStatuses = map viewStatuses . L.groupBy ((==) `on` netStatus) . sortOn netStatus
|
||||
where
|
||||
netStatus ConnNetworkStatus {networkStatus} = networkStatus
|
||||
viewStatuses ss@(s :| _) = plain $ show (L.length ss) <> " connections " <> netStatusStr (netStatus s)
|
||||
|
||||
viewUserJoinedGroup :: GroupInfo -> [StyledString]
|
||||
viewUserJoinedGroup g@GroupInfo {membership} =
|
||||
case incognitoMembershipProfile g of
|
||||
@@ -1464,6 +1457,13 @@ viewConnDiffIds userDiff connDiff
|
||||
where
|
||||
showIds = plain . T.intercalate ", " . map (tshow . unwrapId)
|
||||
|
||||
subStatusStr :: SubscriptionStatus -> String
|
||||
subStatusStr = \case
|
||||
SSActive -> "subscribed"
|
||||
SSPending -> "disconnected"
|
||||
SSRemoved e -> "removed: " <> e
|
||||
SSNoSub -> "no subscription"
|
||||
|
||||
viewUserServers :: UserOperatorServers -> [StyledString]
|
||||
viewUserServers (UserOperatorServers _ [] []) = []
|
||||
viewUserServers UserOperatorServers {operator, smpServers, xftpServers} =
|
||||
|
||||
@@ -180,8 +180,6 @@ chatDirectTests = do
|
||||
testReqVRange vr11 supportedChatVRange
|
||||
testReqVRange vr11 vr11
|
||||
it "update peer version range on received messages" testUpdatePeerChatVRange
|
||||
describe "network statuses" $ do
|
||||
it "should get network statuses" testGetNetworkStatuses
|
||||
where
|
||||
testInvVRange vr1 vr2 = it (vRangeStr vr1 <> " - " <> vRangeStr vr2) $ testConnInvChatVRange vr1 vr2
|
||||
testReqVRange vr1 vr2 = it (vRangeStr vr1 <> " - " <> vRangeStr vr2) $ testConnReqChatVRange vr1 vr2
|
||||
@@ -349,6 +347,7 @@ testRetryConnectingClientTimeout ps = do
|
||||
messageRetryInterval = RetryInterval2 {riFast = fastRetryInterval, riSlow = fastRetryInterval}
|
||||
}
|
||||
}
|
||||
ChatConfig {presetServers = presetSrvs@PresetServers {netCfg}} = testCfg
|
||||
cfgZeroTimeout =
|
||||
(testCfg :: ChatConfig)
|
||||
{ agentConfig =
|
||||
@@ -356,9 +355,7 @@ testRetryConnectingClientTimeout ps = do
|
||||
{ quotaExceededTimeout = 1,
|
||||
messageRetryInterval = RetryInterval2 {riFast = fastRetryInterval, riSlow = fastRetryInterval}
|
||||
},
|
||||
presetServers =
|
||||
let def@PresetServers {netCfg} = presetServers testCfg
|
||||
in def {netCfg = (netCfg :: NetworkConfig) {tcpTimeout = NetworkTimeout 10 10}}
|
||||
presetServers = presetSrvs {netCfg = (netCfg :: NetworkConfig) {tcpTimeout = NetworkTimeout 10 10}}
|
||||
}
|
||||
opts' =
|
||||
testOpts
|
||||
@@ -3324,18 +3321,6 @@ testUpdatePeerChatVRange ps =
|
||||
where
|
||||
cfg11 = testCfg {chatVRange = vr11} :: ChatConfig
|
||||
|
||||
testGetNetworkStatuses :: HasCallStack => TestParams -> IO ()
|
||||
testGetNetworkStatuses ps = do
|
||||
withNewTestChat ps "alice" aliceProfile $ \alice -> do
|
||||
withNewTestChat ps "bob" bobProfile $ \bob -> do
|
||||
connectUsers alice bob
|
||||
alice ##> "/_network_statuses"
|
||||
alice <## "1 connections subscribed"
|
||||
withTestChat ps "alice" $ \alice ->
|
||||
withTestChat ps "bob" $ \bob -> do
|
||||
alice <## "subscribed 1 connections on server localhost"
|
||||
bob <## "subscribed 1 connections on server localhost"
|
||||
|
||||
vr11 :: VersionRangeChat
|
||||
vr11 = mkVersionRange (VersionChat 1) (VersionChat 1)
|
||||
|
||||
|
||||
@@ -28,12 +28,6 @@ chatStartedSwift = "{\"result\":{\"_owsf\":true,\"chatStarted\":{}}}"
|
||||
chatStartedTagged :: LB.ByteString
|
||||
chatStartedTagged = "{\"result\":{\"type\":\"chatStarted\"}}"
|
||||
|
||||
networkStatusesSwift :: LB.ByteString
|
||||
networkStatusesSwift = "{\"result\":{\"_owsf\":true,\"networkStatuses\":{\"user_\":" <> userJSON <> ",\"networkStatuses\":[]}}}"
|
||||
|
||||
networkStatusesTagged :: LB.ByteString
|
||||
networkStatusesTagged = "{\"result\":{\"type\":\"networkStatuses\",\"user_\":" <> userJSON <> ",\"networkStatuses\":[]}}"
|
||||
|
||||
userJSON :: LB.ByteString
|
||||
userJSON = "{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"\",\"shortDescr\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"files\":{\"allow\":\"always\"},\"calls\":{\"allow\":\"yes\"},\"sessions\":{\"allow\":\"no\"},\"commands\":[]},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true,\"autoAcceptMemberContacts\":false}"
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ owsf2TaggedJSONTest = do
|
||||
activeUserExistsSwift `to` activeUserExistsTagged
|
||||
activeUserSwift `to` activeUserTagged
|
||||
chatStartedSwift `to` chatStartedTagged
|
||||
networkStatusesSwift `to` networkStatusesTagged
|
||||
parsedMarkdownSwift `to` parsedMarkdownTagged
|
||||
where
|
||||
to :: LB.ByteString -> LB.ByteString -> IO ()
|
||||
|
||||
Reference in New Issue
Block a user