mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-28 20:29:53 +00:00
ios, core: fix add contact screen, add logging, additional chat events (#380)
* ios, core: fix add contact screen, add logging, additional chat events * fix alert dialogues * fix precedence parsing error * update alert messages
This commit is contained in:
parent
1110a78e06
commit
0413865a3b
9 changed files with 72 additions and 39 deletions
|
@ -268,9 +268,8 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert
|
|||
// if chatModel.upsertChatItem(cInfo, cItem) {
|
||||
// NtfManager.shared.notifyMessageReceived(cInfo, cItem)
|
||||
// }
|
||||
// default:
|
||||
// logger.debug("unsupported event: \(res.responseType)")
|
||||
// }
|
||||
else ->
|
||||
Log.d("SIMPLEX" , "unsupported event: ${r.responseType}")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -394,6 +393,7 @@ class APIResponse(val resp: CR, val corr: String? = null) {
|
|||
sealed class CR {
|
||||
@Serializable @SerialName("activeUser") class ActiveUser(val user: User): CR()
|
||||
@Serializable @SerialName("chatStarted") class ChatStarted: CR()
|
||||
@Serializable @SerialName("chatRunning") class ChatRunning: CR()
|
||||
@Serializable @SerialName("apiChats") class ApiChats(val chats: List<Chat>): CR()
|
||||
@Serializable @SerialName("apiChat") class ApiChat(val chat: Chat): CR()
|
||||
@Serializable @SerialName("invitation") class Invitation(val connReqInvitation: String): CR()
|
||||
|
@ -430,6 +430,7 @@ sealed class CR {
|
|||
val responseType: String get() = when(this) {
|
||||
is ActiveUser -> "activeUser"
|
||||
is ChatStarted -> "chatStarted"
|
||||
is ChatRunning -> "chatRunning"
|
||||
is ApiChats -> "apiChats"
|
||||
is ApiChat -> "apiChats"
|
||||
is Invitation -> "invitation"
|
||||
|
@ -467,6 +468,7 @@ sealed class CR {
|
|||
val details: String get() = when(this) {
|
||||
is ActiveUser -> json.encodeToString(user)
|
||||
is ChatStarted -> noDetails()
|
||||
is ChatRunning -> noDetails()
|
||||
is ApiChats -> json.encodeToString(chats)
|
||||
is ApiChat -> json.encodeToString(chat)
|
||||
is Invitation -> connReqInvitation
|
||||
|
|
|
@ -13,32 +13,31 @@ struct ContentView: View {
|
|||
@State private var showNotificationAlert = false
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
if let user = chatModel.currentUser {
|
||||
ChatListView(user: user)
|
||||
.onAppear {
|
||||
do {
|
||||
try apiStartChat()
|
||||
chatModel.chats = try apiGetChats()
|
||||
} catch {
|
||||
fatalError("Failed to start or load chats: \(error)")
|
||||
}
|
||||
ChatReceiver.shared.start()
|
||||
NtfManager.shared.requestAuthorization(onDeny: {
|
||||
alertManager.showAlert(notificationAlert())
|
||||
})
|
||||
if let user = chatModel.currentUser {
|
||||
ChatListView(user: user)
|
||||
.onAppear {
|
||||
do {
|
||||
try apiStartChat()
|
||||
chatModel.chats = try apiGetChats()
|
||||
} catch {
|
||||
fatalError("Failed to start or load chats: \(error)")
|
||||
}
|
||||
} else {
|
||||
WelcomeView()
|
||||
}
|
||||
ChatReceiver.shared.start()
|
||||
NtfManager.shared.requestAuthorization(onDeny: {
|
||||
alertManager.showAlert(notificationAlert())
|
||||
})
|
||||
}
|
||||
.alert(isPresented: $alertManager.presentAlert) { alertManager.alertView! }
|
||||
} else {
|
||||
WelcomeView()
|
||||
.alert(isPresented: $alertManager.presentAlert) { alertManager.alertView! }
|
||||
}
|
||||
.alert(isPresented: $alertManager.presentAlert) { alertManager.alertView! }
|
||||
}
|
||||
|
||||
func notificationAlert() -> Alert {
|
||||
Alert(
|
||||
title: Text("Notification are disabled!"),
|
||||
message: Text("Please open settings to enable"),
|
||||
message: Text("The app can notify you when you receive messages or contact requests - please open settings to enable."),
|
||||
primaryButton: .default(Text("Open Settings")) {
|
||||
DispatchQueue.main.async {
|
||||
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
|
||||
|
|
|
@ -95,6 +95,7 @@ enum ChatResponse: Decodable, Error {
|
|||
case response(type: String, json: String)
|
||||
case activeUser(user: User)
|
||||
case chatStarted
|
||||
case chatRunning
|
||||
case apiChats(chats: [ChatData])
|
||||
case apiChat(chat: ChatData)
|
||||
case invitation(connReqInvitation: String)
|
||||
|
@ -131,6 +132,7 @@ enum ChatResponse: Decodable, Error {
|
|||
case let .response(type, _): return "* \(type)"
|
||||
case .activeUser: return "activeUser"
|
||||
case .chatStarted: return "chatStarted"
|
||||
case .chatRunning: return "chatRunning"
|
||||
case .apiChats: return "apiChats"
|
||||
case .apiChat: return "apiChat"
|
||||
case .invitation: return "invitation"
|
||||
|
@ -170,6 +172,7 @@ enum ChatResponse: Decodable, Error {
|
|||
case let .response(_, json): return json
|
||||
case let .activeUser(user): return String(describing: user)
|
||||
case .chatStarted: return noDetails
|
||||
case .chatRunning: return noDetails
|
||||
case let .apiChats(chats): return String(describing: chats)
|
||||
case let .apiChat(chat): return String(describing: chat)
|
||||
case let .invitation(connReqInvitation): return connReqInvitation
|
||||
|
@ -306,8 +309,8 @@ func apiSendMessage(type: ChatType, id: Int64, msg: MsgContent) async throws ->
|
|||
throw r
|
||||
}
|
||||
|
||||
func apiAddContact() async throws -> String {
|
||||
let r = await chatSendCmd(.addContact)
|
||||
func apiAddContact() throws -> String {
|
||||
let r = chatSendCmdSync(.addContact)
|
||||
if case let .invitation(connReqInvitation) = r { return connReqInvitation }
|
||||
throw r
|
||||
}
|
||||
|
@ -574,7 +577,10 @@ private func getChatCtrl() -> chat_ctrl {
|
|||
if let controller = chatController { return controller }
|
||||
let dataDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.path + "/mobile_v1"
|
||||
var cstr = dataDir.cString(using: .utf8)!
|
||||
logger.debug("getChatCtrl: chat_init")
|
||||
ChatModel.shared.terminalItems.append(.cmd(.now, .string("chat_init")))
|
||||
chatController = chat_init(&cstr)
|
||||
ChatModel.shared.terminalItems.append(.resp(.now, .response(type: "chat_controller", json: "chat_controller: no details")))
|
||||
return chatController!
|
||||
}
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ struct ChatListView: View {
|
|||
let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)")
|
||||
return Alert(
|
||||
title: Text("Connect via \(action) link?"),
|
||||
message: Text("Your profile will be sent to the contact that you received this link from: \(link)"),
|
||||
message: Text("Your profile will be sent to the contact that you received this link from"),
|
||||
primaryButton: .default(Text("Connect")) {
|
||||
DispatchQueue.main.async {
|
||||
Task {
|
||||
|
|
|
@ -22,7 +22,9 @@ struct AddContactView: View {
|
|||
.multilineTextAlignment(.center)
|
||||
QRCode(uri: connReqInvitation)
|
||||
.padding()
|
||||
Text("If you can't show QR code, you can share the invitation link via any channel")
|
||||
(Text("If you cannot meet in person, you can ") +
|
||||
Text("scan QR code in the video call").bold() +
|
||||
Text(", or you can share the invitation link via any other channel."))
|
||||
.font(.subheadline)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
|
|
|
@ -35,18 +35,20 @@ struct NewChatButton: View {
|
|||
}
|
||||
|
||||
func addContactAction() {
|
||||
Task {
|
||||
do {
|
||||
connReqInvitation = try await apiAddContact()
|
||||
addContact = true
|
||||
} catch {
|
||||
DispatchQueue.global().async {
|
||||
connectionErrorAlert(error)
|
||||
}
|
||||
logger.error("NewChatButton.addContactAction apiAddContact error: \(error.localizedDescription)")
|
||||
do {
|
||||
connReqInvitation = try apiAddContact()
|
||||
addContact = true
|
||||
} catch {
|
||||
DispatchQueue.global().async {
|
||||
connectionErrorAlert(error)
|
||||
}
|
||||
logger.error("NewChatButton.addContactAction apiAddContact error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
func addContactSheet() -> some View {
|
||||
AddContactView(connReqInvitation: connReqInvitation)
|
||||
}
|
||||
|
||||
func connectContactSheet() -> some View {
|
||||
ConnectContactView(completed: { err in
|
||||
|
|
|
@ -144,7 +144,10 @@ processChatCommand = \case
|
|||
user <- withStore $ \st -> createUser st p True
|
||||
atomically . writeTVar u $ Just user
|
||||
pure $ CRActiveUser user
|
||||
StartChat -> withUser' $ \user -> startChatController user $> CRChatStarted
|
||||
StartChat -> withUser' $ \user ->
|
||||
asks agentAsync >>= readTVarIO >>= \case
|
||||
Just _ -> pure CRChatRunning
|
||||
_ -> startChatController user $> CRChatStarted
|
||||
APIGetChats -> CRApiChats <$> withUser (\user -> withStore (`getChatPreviews` user))
|
||||
APIGetChat cType cId pagination -> withUser $ \user -> case cType of
|
||||
CTDirect -> CRApiChat . AChat SCTDirect <$> withStore (\st -> getDirectChat st user cId pagination)
|
||||
|
@ -528,7 +531,9 @@ subscribeUserConnections user@User {userId} = do
|
|||
subscribe cId `catchError` (toView . CRRcvFileSubError ft)
|
||||
subscribePendingConnections n = do
|
||||
cs <- withStore (`getPendingConnections` user)
|
||||
subscribeConns n cs `catchError` \_ -> pure ()
|
||||
summary <- pooledForConcurrentlyN n cs $ \Connection {agentConnId = acId@(AgentConnId cId)} ->
|
||||
PendingSubStatus acId <$> ((subscribe cId $> Nothing) `catchError` (pure . Just))
|
||||
toView $ CRPendingSubSummary summary
|
||||
subscribeUserContactLink n = do
|
||||
cs <- withStore (`getUserContactLinkConnections` userId)
|
||||
(subscribeConns n cs >> toView CRUserContactLinkSubscribed)
|
||||
|
|
|
@ -133,6 +133,7 @@ data ChatCommand
|
|||
data ChatResponse
|
||||
= CRActiveUser {user :: User}
|
||||
| CRChatStarted
|
||||
| CRChatRunning
|
||||
| CRApiChats {chats :: [AChat]}
|
||||
| CRApiChat {chat :: AChat}
|
||||
| CRNewChatItem {chatItem :: AChatItem}
|
||||
|
@ -204,6 +205,7 @@ data ChatResponse
|
|||
| CRMemberSubError {groupInfo :: GroupInfo, contactName :: ContactName, chatError :: ChatError} -- TODO Contact? or GroupMember?
|
||||
| CRMemberSubErrors {memberSubErrors :: [MemberSubError]}
|
||||
| CRGroupSubscribed {groupInfo :: GroupInfo}
|
||||
| CRPendingSubSummary {pendingSubStatus :: [PendingSubStatus]}
|
||||
| CRSndFileSubError {sndFileTransfer :: SndFileTransfer, chatError :: ChatError}
|
||||
| CRRcvFileSubError {rcvFileTransfer :: RcvFileTransfer, chatError :: ChatError}
|
||||
| CRUserContactLinkSubscribed
|
||||
|
@ -236,6 +238,16 @@ data MemberSubError = MemberSubError
|
|||
instance ToJSON MemberSubError where
|
||||
toEncoding = J.genericToEncoding J.defaultOptions
|
||||
|
||||
data PendingSubStatus = PendingSubStatus
|
||||
{ connId :: AgentConnId,
|
||||
connError :: Maybe ChatError
|
||||
}
|
||||
deriving (Show, Generic)
|
||||
|
||||
instance ToJSON PendingSubStatus where
|
||||
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
|
||||
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
|
||||
|
||||
data ChatError
|
||||
= ChatError {errorType :: ChatErrorType}
|
||||
| ChatErrorAgent {agentError :: AgentErrorType}
|
||||
|
|
|
@ -39,6 +39,7 @@ responseToView :: Bool -> ChatResponse -> [StyledString]
|
|||
responseToView testView = \case
|
||||
CRActiveUser User {profile} -> viewUserProfile profile
|
||||
CRChatStarted -> ["chat started"]
|
||||
CRChatRunning -> []
|
||||
CRApiChats chats -> if testView then testViewChats chats else [plain . bshow $ J.encode chats]
|
||||
CRApiChat chat -> if testView then testViewChat chat else [plain . bshow $ J.encode chat]
|
||||
CRNewChatItem (AChatItem _ _ chat item) -> viewChatItem chat item
|
||||
|
@ -103,9 +104,9 @@ responseToView testView = \case
|
|||
CRContactSubscribed c -> [ttyContact' c <> ": connected to server"]
|
||||
CRContactSubError c e -> [ttyContact' c <> ": contact error " <> sShow e]
|
||||
CRContactSubSummary summary ->
|
||||
(if null connected then [] else [sShow (length connected) <> " contacts connected (use " <> highlight' "/cs" <> " for the list)"]) <> viewErrorsSummary errors " contact errors"
|
||||
(if null subscribed then [] else [sShow (length subscribed) <> " contacts connected (use " <> highlight' "/cs" <> " for the list)"]) <> viewErrorsSummary errors " contact errors"
|
||||
where
|
||||
(errors, connected) = partition (isJust . contactError) summary
|
||||
(errors, subscribed) = partition (isJust . contactError) summary
|
||||
CRGroupInvitation GroupInfo {localDisplayName = ldn, groupProfile = GroupProfile {fullName}} ->
|
||||
[groupInvitation ldn fullName]
|
||||
CRReceivedGroupInvitation g c role -> viewReceivedGroupInvitation g c role
|
||||
|
@ -122,6 +123,10 @@ responseToView testView = \case
|
|||
CRMemberSubError g c e -> [ttyGroup' g <> " member " <> ttyContact c <> " error: " <> sShow e]
|
||||
CRMemberSubErrors summary -> viewErrorsSummary summary " group member errors"
|
||||
CRGroupSubscribed g -> [ttyFullGroup g <> ": connected to server(s)"]
|
||||
CRPendingSubSummary summary ->
|
||||
(if null subscribed then [] else [sShow (length subscribed) <> " pending connections subscribed"]) <> viewErrorsSummary errors " connection errors"
|
||||
where
|
||||
(errors, subscribed) = partition (isJust . connError) summary
|
||||
CRSndFileSubError SndFileTransfer {fileId, fileName} e ->
|
||||
["sent file " <> sShow fileId <> " (" <> plain fileName <> ") error: " <> sShow e]
|
||||
CRRcvFileSubError RcvFileTransfer {fileId, fileInvitation = FileInvitation {fileName}} e ->
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue