Merge branch 'master-ghc8107' into master-android
This commit is contained in:
commit
77ac972e09
@ -195,18 +195,18 @@ func moveTempFileFromURL(_ url: URL) -> CryptoFile? {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateNewFileName(_ prefix: String, _ ext: String) -> String {
|
func generateNewFileName(_ prefix: String, _ ext: String, fullPath: Bool = false) -> String {
|
||||||
uniqueCombine("\(prefix)_\(getTimestamp()).\(ext)")
|
uniqueCombine("\(prefix)_\(getTimestamp()).\(ext)", fullPath: fullPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func uniqueCombine(_ fileName: String) -> String {
|
private func uniqueCombine(_ fileName: String, fullPath: Bool = false) -> String {
|
||||||
func tryCombine(_ fileName: String, _ n: Int) -> String {
|
func tryCombine(_ fileName: String, _ n: Int) -> String {
|
||||||
let ns = fileName as NSString
|
let ns = fileName as NSString
|
||||||
let name = ns.deletingPathExtension
|
let name = ns.deletingPathExtension
|
||||||
let ext = ns.pathExtension
|
let ext = ns.pathExtension
|
||||||
let suffix = (n == 0) ? "" : "_\(n)"
|
let suffix = (n == 0) ? "" : "_\(n)"
|
||||||
let f = "\(name)\(suffix).\(ext)"
|
let f = "\(name)\(suffix).\(ext)"
|
||||||
return (FileManager.default.fileExists(atPath: getAppFilePath(f).path)) ? tryCombine(fileName, n + 1) : f
|
return (FileManager.default.fileExists(atPath: fullPath ? f : getAppFilePath(f).path)) ? tryCombine(fileName, n + 1) : f
|
||||||
}
|
}
|
||||||
return tryCombine(fileName, 0)
|
return tryCombine(fileName, 0)
|
||||||
}
|
}
|
||||||
|
@ -384,10 +384,10 @@ struct ComposeView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showMediaPicker) {
|
.sheet(isPresented: $showMediaPicker) {
|
||||||
LibraryMediaListPicker(media: $chosenMedia, selectionLimit: 10) { itemsSelected in
|
LibraryMediaListPicker(addMedia: addMediaContent, selectionLimit: 10) { itemsSelected in
|
||||||
showMediaPicker = false
|
await MainActor.run {
|
||||||
if itemsSelected {
|
showMediaPicker = false
|
||||||
DispatchQueue.main.async {
|
if itemsSelected {
|
||||||
composeState = composeState.copy(preview: .mediaPreviews(mediaPreviews: []))
|
composeState = composeState.copy(preview: .mediaPreviews(mediaPreviews: []))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -488,6 +488,21 @@ struct ComposeView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func addMediaContent(_ content: UploadContent) async {
|
||||||
|
if let img = resizeImageToStrSize(content.uiImage, maxDataSize: 14000) {
|
||||||
|
var newMedia: [(String, UploadContent?)] = []
|
||||||
|
if case var .mediaPreviews(media) = composeState.preview {
|
||||||
|
media.append((img, content))
|
||||||
|
newMedia = media
|
||||||
|
} else {
|
||||||
|
newMedia = [(img, content)]
|
||||||
|
}
|
||||||
|
await MainActor.run {
|
||||||
|
composeState = composeState.copy(preview: .mediaPreviews(mediaPreviews: newMedia))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var maxFileSize: Int64 {
|
private var maxFileSize: Int64 {
|
||||||
getMaxFileSize(.xftp)
|
getMaxFileSize(.xftp)
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@ struct GroupChatInfoView: View {
|
|||||||
@Environment(\.dismiss) var dismiss: DismissAction
|
@Environment(\.dismiss) var dismiss: DismissAction
|
||||||
@ObservedObject var chat: Chat
|
@ObservedObject var chat: Chat
|
||||||
@Binding var groupInfo: GroupInfo
|
@Binding var groupInfo: GroupInfo
|
||||||
@ObservedObject private var alertManager = AlertManager.shared
|
|
||||||
@State private var alert: GroupChatInfoViewAlert? = nil
|
@State private var alert: GroupChatInfoViewAlert? = nil
|
||||||
@State private var groupLink: String?
|
@State private var groupLink: String?
|
||||||
@State private var groupLinkMemberRole: GroupMemberRole = .member
|
@State private var groupLinkMemberRole: GroupMemberRole = .member
|
||||||
|
@ -188,17 +188,19 @@ struct GroupMemberInfoView: View {
|
|||||||
// this condition prevents re-setting picker
|
// this condition prevents re-setting picker
|
||||||
if !justOpened { return }
|
if !justOpened { return }
|
||||||
}
|
}
|
||||||
newRole = member.memberRole
|
|
||||||
do {
|
|
||||||
let (_, stats) = try apiGroupMemberInfo(groupInfo.apiId, member.groupMemberId)
|
|
||||||
let (mem, code) = member.memberActive ? try apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId) : (member, nil)
|
|
||||||
_ = chatModel.upsertGroupMember(groupInfo, mem)
|
|
||||||
connectionStats = stats
|
|
||||||
connectionCode = code
|
|
||||||
} catch let error {
|
|
||||||
logger.error("apiGroupMemberInfo or apiGetGroupMemberCode error: \(responseError(error))")
|
|
||||||
}
|
|
||||||
justOpened = false
|
justOpened = false
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
newRole = member.memberRole
|
||||||
|
do {
|
||||||
|
let (_, stats) = try apiGroupMemberInfo(groupInfo.apiId, member.groupMemberId)
|
||||||
|
let (mem, code) = member.memberActive ? try apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId) : (member, nil)
|
||||||
|
_ = chatModel.upsertGroupMember(groupInfo, mem)
|
||||||
|
connectionStats = stats
|
||||||
|
connectionCode = code
|
||||||
|
} catch let error {
|
||||||
|
logger.error("apiGroupMemberInfo or apiGetGroupMemberCode error: \(responseError(error))")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: newRole) { newRole in
|
.onChange(of: newRole) { newRole in
|
||||||
if newRole != member.memberRole {
|
if newRole != member.memberRole {
|
||||||
|
@ -103,8 +103,10 @@ struct GroupProfileView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showImagePicker) {
|
.sheet(isPresented: $showImagePicker) {
|
||||||
LibraryImagePicker(image: $chosenImage) {
|
LibraryImagePicker(image: $chosenImage) { _ in
|
||||||
didSelectItem in showImagePicker = false
|
await MainActor.run {
|
||||||
|
showImagePicker = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: chosenImage) { image in
|
.onChange(of: chosenImage) { image in
|
||||||
|
@ -17,7 +17,7 @@ struct ScanCodeView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
CodeScannerView(codeTypes: [.qr], completion: processQRCode)
|
CodeScannerView(codeTypes: [.qr], scanMode: .oncePerCode, completion: processQRCode)
|
||||||
.aspectRatio(1, contentMode: .fit)
|
.aspectRatio(1, contentMode: .fit)
|
||||||
.cornerRadius(12)
|
.cornerRadius(12)
|
||||||
Text("Scan security code from your contact's app.")
|
Text("Scan security code from your contact's app.")
|
||||||
|
@ -13,112 +13,122 @@ import SimpleXChat
|
|||||||
|
|
||||||
struct LibraryImagePicker: View {
|
struct LibraryImagePicker: View {
|
||||||
@Binding var image: UIImage?
|
@Binding var image: UIImage?
|
||||||
var didFinishPicking: (_ didSelectItems: Bool) -> Void
|
var didFinishPicking: (_ didSelectImage: Bool) async -> Void
|
||||||
@State var images: [UploadContent] = []
|
@State var mediaAdded = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
LibraryMediaListPicker(media: $images, selectionLimit: 1, didFinishPicking: didFinishPicking)
|
LibraryMediaListPicker(addMedia: addMedia, selectionLimit: 1, didFinishPicking: didFinishPicking)
|
||||||
.onChange(of: images) { _ in
|
}
|
||||||
if let img = images.first {
|
|
||||||
image = img.uiImage
|
private func addMedia(_ content: UploadContent) async {
|
||||||
}
|
if mediaAdded { return }
|
||||||
}
|
await MainActor.run {
|
||||||
|
mediaAdded = true
|
||||||
|
image = content.uiImage
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LibraryMediaListPicker: UIViewControllerRepresentable {
|
struct LibraryMediaListPicker: UIViewControllerRepresentable {
|
||||||
typealias UIViewControllerType = PHPickerViewController
|
typealias UIViewControllerType = PHPickerViewController
|
||||||
@Binding var media: [UploadContent]
|
var addMedia: (_ content: UploadContent) async -> Void
|
||||||
var selectionLimit: Int
|
var selectionLimit: Int
|
||||||
var didFinishPicking: (_ didSelectItems: Bool) -> Void
|
var didFinishPicking: (_ didSelectItems: Bool) async -> Void
|
||||||
|
|
||||||
class Coordinator: PHPickerViewControllerDelegate {
|
class Coordinator: PHPickerViewControllerDelegate {
|
||||||
let parent: LibraryMediaListPicker
|
let parent: LibraryMediaListPicker
|
||||||
let dispatchQueue = DispatchQueue(label: "chat.simplex.app.LibraryMediaListPicker")
|
let dispatchQueue = DispatchQueue(label: "chat.simplex.app.LibraryMediaListPicker")
|
||||||
var media: [UploadContent] = []
|
|
||||||
var mediaCount: Int = 0
|
|
||||||
|
|
||||||
init(_ parent: LibraryMediaListPicker) {
|
init(_ parent: LibraryMediaListPicker) {
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
}
|
}
|
||||||
|
|
||||||
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
|
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
|
||||||
parent.didFinishPicking(!results.isEmpty)
|
Task {
|
||||||
guard !results.isEmpty else {
|
await parent.didFinishPicking(!results.isEmpty)
|
||||||
return
|
if results.isEmpty { return }
|
||||||
|
for r in results {
|
||||||
|
await loadItem(r.itemProvider)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
parent.media = []
|
private func loadItem(_ p: NSItemProvider) async {
|
||||||
media = []
|
logger.debug("LibraryMediaListPicker result")
|
||||||
mediaCount = results.count
|
if p.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
|
||||||
for result in results {
|
if let video = await loadVideo(p) {
|
||||||
logger.log("LibraryMediaListPicker result")
|
await self.parent.addMedia(video)
|
||||||
let p = result.itemProvider
|
logger.debug("LibraryMediaListPicker: added video")
|
||||||
if p.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
|
}
|
||||||
p.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { url, error in
|
} else if p.hasItemConformingToTypeIdentifier(UTType.data.identifier) {
|
||||||
if let url = url {
|
if let img = await loadImageData(p) {
|
||||||
let tempUrl = URL(fileURLWithPath: getTempFilesDirectory().path + "/" + generateNewFileName("video", url.pathExtension))
|
await self.parent.addMedia(img)
|
||||||
if ((try? FileManager.default.copyItem(at: url, to: tempUrl)) != nil) {
|
logger.debug("LibraryMediaListPicker: added image")
|
||||||
ChatModel.shared.filesToDelete.insert(tempUrl)
|
}
|
||||||
self.loadVideo(url: tempUrl, error: error)
|
} else if p.canLoadObject(ofClass: UIImage.self) {
|
||||||
|
if let img = await loadImage(p) {
|
||||||
|
await self.parent.addMedia(.simpleImage(image: img))
|
||||||
|
logger.debug("LibraryMediaListPicker: added image")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadImageData(_ p: NSItemProvider) async -> UploadContent? {
|
||||||
|
await withCheckedContinuation { cont in
|
||||||
|
loadFileURL(p, type: UTType.data) { url in
|
||||||
|
if let url = url {
|
||||||
|
let img = UploadContent.loadFromURL(url: url)
|
||||||
|
cont.resume(returning: img)
|
||||||
|
} else {
|
||||||
|
cont.resume(returning: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadImage(_ p: NSItemProvider) async -> UIImage? {
|
||||||
|
await withCheckedContinuation { cont in
|
||||||
|
p.loadObject(ofClass: UIImage.self) { obj, err in
|
||||||
|
if let err = err {
|
||||||
|
logger.error("LibraryMediaListPicker result image error: \(err.localizedDescription)")
|
||||||
|
cont.resume(returning: nil)
|
||||||
|
} else {
|
||||||
|
cont.resume(returning: obj as? UIImage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadVideo(_ p: NSItemProvider) async -> UploadContent? {
|
||||||
|
await withCheckedContinuation { cont in
|
||||||
|
loadFileURL(p, type: UTType.movie) { url in
|
||||||
|
if let url = url {
|
||||||
|
let tempUrl = URL(fileURLWithPath: generateNewFileName(getTempFilesDirectory().path + "/" + "video", url.pathExtension, fullPath: true))
|
||||||
|
do {
|
||||||
|
// logger.debug("LibraryMediaListPicker copyItem \(url) to \(tempUrl)")
|
||||||
|
try FileManager.default.copyItem(at: url, to: tempUrl)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
_ = ChatModel.shared.filesToDelete.insert(tempUrl)
|
||||||
}
|
}
|
||||||
|
let video = UploadContent.loadVideoFromURL(url: tempUrl)
|
||||||
|
cont.resume(returning: video)
|
||||||
|
return
|
||||||
|
} catch let err {
|
||||||
|
logger.error("LibraryMediaListPicker copyItem error: \(err.localizedDescription)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if p.hasItemConformingToTypeIdentifier(UTType.data.identifier) {
|
cont.resume(returning: nil)
|
||||||
p.loadFileRepresentation(forTypeIdentifier: UTType.data.identifier) { url, error in
|
}
|
||||||
self.loadImage(object: url, error: error)
|
}
|
||||||
}
|
}
|
||||||
} else if p.canLoadObject(ofClass: UIImage.self) {
|
|
||||||
p.loadObject(ofClass: UIImage.self) { image, error in
|
private func loadFileURL(_ p: NSItemProvider, type: UTType, completion: @escaping (URL?) -> Void) {
|
||||||
DispatchQueue.main.async {
|
p.loadFileRepresentation(forTypeIdentifier: type.identifier) { url, err in
|
||||||
self.loadImage(object: image, error: error)
|
if let err = err {
|
||||||
}
|
logger.error("LibraryMediaListPicker loadFileURL error: \(err.localizedDescription)")
|
||||||
}
|
completion(nil)
|
||||||
} else {
|
} else {
|
||||||
dispatchQueue.sync { self.mediaCount -= 1}
|
completion(url)
|
||||||
}
|
|
||||||
}
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
|
|
||||||
self.dispatchQueue.sync {
|
|
||||||
if self.parent.media.count == 0 {
|
|
||||||
logger.log("LibraryMediaListPicker: added \(self.media.count) images out of \(results.count)")
|
|
||||||
self.parent.media = self.media
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadImage(object: Any?, error: Error? = nil) {
|
|
||||||
if let error = error {
|
|
||||||
logger.error("LibraryMediaListPicker: couldn't load image with error: \(error.localizedDescription)")
|
|
||||||
} else if let image = object as? UIImage {
|
|
||||||
media.append(.simpleImage(image: image))
|
|
||||||
logger.log("LibraryMediaListPicker: added image")
|
|
||||||
} else if let url = object as? URL, let image = UploadContent.loadFromURL(url: url) {
|
|
||||||
media.append(image)
|
|
||||||
}
|
|
||||||
dispatchQueue.sync {
|
|
||||||
self.mediaCount -= 1
|
|
||||||
if self.mediaCount == 0 && self.parent.media.count == 0 {
|
|
||||||
logger.log("LibraryMediaListPicker: added all media")
|
|
||||||
self.parent.media = self.media
|
|
||||||
self.media = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadVideo(url: URL?, error: Error? = nil) {
|
|
||||||
if let error = error {
|
|
||||||
logger.error("LibraryMediaListPicker: couldn't load video with error: \(error.localizedDescription)")
|
|
||||||
} else if let url = url as URL?, let video = UploadContent.loadVideoFromURL(url: url) {
|
|
||||||
media.append(video)
|
|
||||||
}
|
|
||||||
dispatchQueue.sync {
|
|
||||||
self.mediaCount -= 1
|
|
||||||
if self.mediaCount == 0 && self.parent.media.count == 0 {
|
|
||||||
logger.log("LibraryMediaListPicker: added all media")
|
|
||||||
self.parent.media = self.media
|
|
||||||
self.media = []
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,8 +130,10 @@ struct AddGroupView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showImagePicker) {
|
.sheet(isPresented: $showImagePicker) {
|
||||||
LibraryImagePicker(image: $chosenImage) {
|
LibraryImagePicker(image: $chosenImage) { _ in
|
||||||
didSelectItem in showImagePicker = false
|
await MainActor.run {
|
||||||
|
showImagePicker = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.alert(isPresented: $showInvalidNameAlert) {
|
.alert(isPresented: $showInvalidNameAlert) {
|
||||||
|
@ -74,6 +74,7 @@ struct QRCode: View {
|
|||||||
.onAppear {
|
.onAppear {
|
||||||
image = image ?? generateImage(uri, tintColor: tintColor)
|
image = image ?? generateImage(uri, tintColor: tintColor)
|
||||||
}
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ struct ScanToConnectView: View {
|
|||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
.padding(.vertical)
|
.padding(.vertical)
|
||||||
|
|
||||||
CodeScannerView(codeTypes: [.qr], completion: processQRCode)
|
CodeScannerView(codeTypes: [.qr], scanMode: .continuous, completion: processQRCode)
|
||||||
.aspectRatio(1, contentMode: .fit)
|
.aspectRatio(1, contentMode: .fit)
|
||||||
.cornerRadius(12)
|
.cornerRadius(12)
|
||||||
|
|
||||||
|
@ -332,7 +332,7 @@ struct ConnectDesktopView: View {
|
|||||||
|
|
||||||
private func scanDesctopAddressView() -> some View {
|
private func scanDesctopAddressView() -> some View {
|
||||||
Section("Scan QR code from desktop") {
|
Section("Scan QR code from desktop") {
|
||||||
CodeScannerView(codeTypes: [.qr], completion: processDesktopQRCode)
|
CodeScannerView(codeTypes: [.qr], scanMode: .oncePerCode, completion: processDesktopQRCode)
|
||||||
.aspectRatio(1, contentMode: .fit)
|
.aspectRatio(1, contentMode: .fit)
|
||||||
.cornerRadius(12)
|
.cornerRadius(12)
|
||||||
.listRowBackground(Color.clear)
|
.listRowBackground(Color.clear)
|
||||||
|
@ -51,9 +51,9 @@ struct AdvancedNetworkSettings: View {
|
|||||||
}
|
}
|
||||||
.disabled(currentNetCfg == NetCfg.proxyDefaults)
|
.disabled(currentNetCfg == NetCfg.proxyDefaults)
|
||||||
|
|
||||||
timeoutSettingPicker("TCP connection timeout", selection: $netCfg.tcpConnectTimeout, values: [5_000000, 7_500000, 10_000000, 15_000000, 20_000000, 30_000000, 45_000000], label: secondsLabel)
|
timeoutSettingPicker("TCP connection timeout", selection: $netCfg.tcpConnectTimeout, values: [7_500000, 10_000000, 15_000000, 20_000000, 30_000000, 45_000000], label: secondsLabel)
|
||||||
timeoutSettingPicker("Protocol timeout", selection: $netCfg.tcpTimeout, values: [3_000000, 5_000000, 7_000000, 10_000000, 15_000000, 20_000000, 30_000000], label: secondsLabel)
|
timeoutSettingPicker("Protocol timeout", selection: $netCfg.tcpTimeout, values: [5_000000, 7_000000, 10_000000, 15_000000, 20_000000, 30_000000], label: secondsLabel)
|
||||||
timeoutSettingPicker("Protocol timeout per KB", selection: $netCfg.tcpTimeoutPerKb, values: [15_000, 30_000, 60_000, 90_000, 120_000], label: secondsLabel)
|
timeoutSettingPicker("Protocol timeout per KB", selection: $netCfg.tcpTimeoutPerKb, values: [15_000, 30_000, 45_000, 60_000, 90_000, 120_000], label: secondsLabel)
|
||||||
timeoutSettingPicker("PING interval", selection: $netCfg.smpPingInterval, values: [120_000000, 300_000000, 600_000000, 1200_000000, 2400_000000, 3600_000000], label: secondsLabel)
|
timeoutSettingPicker("PING interval", selection: $netCfg.smpPingInterval, values: [120_000000, 300_000000, 600_000000, 1200_000000, 2400_000000, 3600_000000], label: secondsLabel)
|
||||||
intSettingPicker("PING count", selection: $netCfg.smpPingCount, values: [1, 2, 3, 5, 8], label: "")
|
intSettingPicker("PING count", selection: $netCfg.smpPingCount, values: [1, 2, 3, 5, 8], label: "")
|
||||||
Toggle("Enable TCP keep-alive", isOn: $enableKeepAlive)
|
Toggle("Enable TCP keep-alive", isOn: $enableKeepAlive)
|
||||||
|
@ -21,7 +21,7 @@ struct ScanProtocolServer: View {
|
|||||||
.font(.largeTitle)
|
.font(.largeTitle)
|
||||||
.bold()
|
.bold()
|
||||||
.padding(.vertical)
|
.padding(.vertical)
|
||||||
CodeScannerView(codeTypes: [.qr], completion: processQRCode)
|
CodeScannerView(codeTypes: [.qr], scanMode: .oncePerCode, completion: processQRCode)
|
||||||
.aspectRatio(1, contentMode: .fit)
|
.aspectRatio(1, contentMode: .fit)
|
||||||
.cornerRadius(12)
|
.cornerRadius(12)
|
||||||
.padding(.top)
|
.padding(.top)
|
||||||
|
@ -120,8 +120,10 @@ struct UserProfile: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showImagePicker) {
|
.sheet(isPresented: $showImagePicker) {
|
||||||
LibraryImagePicker(image: $chosenImage) {
|
LibraryImagePicker(image: $chosenImage) { _ in
|
||||||
didSelectItem in showImagePicker = false
|
await MainActor.run {
|
||||||
|
showImagePicker = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: chosenImage) { image in
|
.onChange(of: chosenImage) { image in
|
||||||
|
@ -63,11 +63,6 @@
|
|||||||
5C7505A527B679EE00BE3227 /* NavLinkPlain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7505A427B679EE00BE3227 /* NavLinkPlain.swift */; };
|
5C7505A527B679EE00BE3227 /* NavLinkPlain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7505A427B679EE00BE3227 /* NavLinkPlain.swift */; };
|
||||||
5C7505A827B6D34800BE3227 /* ChatInfoToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7505A727B6D34800BE3227 /* ChatInfoToolbar.swift */; };
|
5C7505A827B6D34800BE3227 /* ChatInfoToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7505A727B6D34800BE3227 /* ChatInfoToolbar.swift */; };
|
||||||
5C764E89279CBCB3000C6508 /* ChatModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C764E88279CBCB3000C6508 /* ChatModel.swift */; };
|
5C764E89279CBCB3000C6508 /* ChatModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C764E88279CBCB3000C6508 /* ChatModel.swift */; };
|
||||||
5C8EA13D2B25206A001DE5E4 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C8EA1382B25206A001DE5E4 /* libgmp.a */; };
|
|
||||||
5C8EA13E2B25206A001DE5E4 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C8EA1392B25206A001DE5E4 /* libgmpxx.a */; };
|
|
||||||
5C8EA13F2B25206A001DE5E4 /* libHSsimplex-chat-5.4.0.7-1uCDT6bmj7t4ctyD1vFaZX-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C8EA13A2B25206A001DE5E4 /* libHSsimplex-chat-5.4.0.7-1uCDT6bmj7t4ctyD1vFaZX-ghc9.6.3.a */; };
|
|
||||||
5C8EA1402B25206A001DE5E4 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C8EA13B2B25206A001DE5E4 /* libffi.a */; };
|
|
||||||
5C8EA1412B25206A001DE5E4 /* libHSsimplex-chat-5.4.0.7-1uCDT6bmj7t4ctyD1vFaZX.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C8EA13C2B25206A001DE5E4 /* libHSsimplex-chat-5.4.0.7-1uCDT6bmj7t4ctyD1vFaZX.a */; };
|
|
||||||
5C8F01CD27A6F0D8007D2C8D /* CodeScanner in Frameworks */ = {isa = PBXBuildFile; productRef = 5C8F01CC27A6F0D8007D2C8D /* CodeScanner */; };
|
5C8F01CD27A6F0D8007D2C8D /* CodeScanner in Frameworks */ = {isa = PBXBuildFile; productRef = 5C8F01CC27A6F0D8007D2C8D /* CodeScanner */; };
|
||||||
5C93292F29239A170090FFF9 /* ProtocolServersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C93292E29239A170090FFF9 /* ProtocolServersView.swift */; };
|
5C93292F29239A170090FFF9 /* ProtocolServersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C93292E29239A170090FFF9 /* ProtocolServersView.swift */; };
|
||||||
5C93293129239BED0090FFF9 /* ProtocolServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C93293029239BED0090FFF9 /* ProtocolServerView.swift */; };
|
5C93293129239BED0090FFF9 /* ProtocolServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C93293029239BED0090FFF9 /* ProtocolServerView.swift */; };
|
||||||
@ -121,6 +116,11 @@
|
|||||||
5CC2C0FF2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FD2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings */; };
|
5CC2C0FF2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FD2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings */; };
|
||||||
5CC868F329EB540C0017BBFD /* CIRcvDecryptionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */; };
|
5CC868F329EB540C0017BBFD /* CIRcvDecryptionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */; };
|
||||||
5CCB939C297EFCB100399E78 /* NavStackCompat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCB939B297EFCB100399E78 /* NavStackCompat.swift */; };
|
5CCB939C297EFCB100399E78 /* NavStackCompat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCB939B297EFCB100399E78 /* NavStackCompat.swift */; };
|
||||||
|
5CCD1A602B27927E001A4199 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCD1A5B2B27927E001A4199 /* libgmpxx.a */; };
|
||||||
|
5CCD1A612B27927E001A4199 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCD1A5C2B27927E001A4199 /* libgmp.a */; };
|
||||||
|
5CCD1A622B27927E001A4199 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCD1A5D2B27927E001A4199 /* libffi.a */; };
|
||||||
|
5CCD1A632B27927E001A4199 /* libHSsimplex-chat-5.4.0.7-EoJ0xKOyE47DlSpHXf0V4.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCD1A5E2B27927E001A4199 /* libHSsimplex-chat-5.4.0.7-EoJ0xKOyE47DlSpHXf0V4.a */; };
|
||||||
|
5CCD1A642B27927E001A4199 /* libHSsimplex-chat-5.4.0.7-EoJ0xKOyE47DlSpHXf0V4-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCD1A5F2B27927E001A4199 /* libHSsimplex-chat-5.4.0.7-EoJ0xKOyE47DlSpHXf0V4-ghc8.10.7.a */; };
|
||||||
5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403327A5F6DF00368C90 /* AddContactView.swift */; };
|
5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403327A5F6DF00368C90 /* AddContactView.swift */; };
|
||||||
5CCD403727A5F9A200368C90 /* ScanToConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */; };
|
5CCD403727A5F9A200368C90 /* ScanToConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */; };
|
||||||
5CD67B8F2B0E858A00C510B1 /* hs_init.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CD67B8D2B0E858A00C510B1 /* hs_init.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
5CD67B8F2B0E858A00C510B1 /* hs_init.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CD67B8D2B0E858A00C510B1 /* hs_init.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||||
@ -338,11 +338,6 @@
|
|||||||
5C8B41C929AF41BC00888272 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; };
|
5C8B41C929AF41BC00888272 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
5C8B41CB29AF44CF00888272 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = "cs.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
|
5C8B41CB29AF44CF00888272 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = "cs.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
|
||||||
5C8B41CC29AF44CF00888272 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
5C8B41CC29AF44CF00888272 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
5C8EA1382B25206A001DE5E4 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
|
||||||
5C8EA1392B25206A001DE5E4 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
|
||||||
5C8EA13A2B25206A001DE5E4 /* libHSsimplex-chat-5.4.0.7-1uCDT6bmj7t4ctyD1vFaZX-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.7-1uCDT6bmj7t4ctyD1vFaZX-ghc9.6.3.a"; sourceTree = "<group>"; };
|
|
||||||
5C8EA13B2B25206A001DE5E4 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
|
||||||
5C8EA13C2B25206A001DE5E4 /* libHSsimplex-chat-5.4.0.7-1uCDT6bmj7t4ctyD1vFaZX.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.7-1uCDT6bmj7t4ctyD1vFaZX.a"; sourceTree = "<group>"; };
|
|
||||||
5C93292E29239A170090FFF9 /* ProtocolServersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProtocolServersView.swift; sourceTree = "<group>"; };
|
5C93292E29239A170090FFF9 /* ProtocolServersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProtocolServersView.swift; sourceTree = "<group>"; };
|
||||||
5C93293029239BED0090FFF9 /* ProtocolServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProtocolServerView.swift; sourceTree = "<group>"; };
|
5C93293029239BED0090FFF9 /* ProtocolServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProtocolServerView.swift; sourceTree = "<group>"; };
|
||||||
5C93293E2928E0FD0090FFF9 /* AudioRecPlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecPlay.swift; sourceTree = "<group>"; };
|
5C93293E2928E0FD0090FFF9 /* AudioRecPlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecPlay.swift; sourceTree = "<group>"; };
|
||||||
@ -412,6 +407,11 @@
|
|||||||
5CC2C0FE2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = "ru.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
|
5CC2C0FE2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = "ru.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
|
||||||
5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIRcvDecryptionError.swift; sourceTree = "<group>"; };
|
5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIRcvDecryptionError.swift; sourceTree = "<group>"; };
|
||||||
5CCB939B297EFCB100399E78 /* NavStackCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavStackCompat.swift; sourceTree = "<group>"; };
|
5CCB939B297EFCB100399E78 /* NavStackCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavStackCompat.swift; sourceTree = "<group>"; };
|
||||||
|
5CCD1A5B2B27927E001A4199 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||||
|
5CCD1A5C2B27927E001A4199 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||||
|
5CCD1A5D2B27927E001A4199 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||||
|
5CCD1A5E2B27927E001A4199 /* libHSsimplex-chat-5.4.0.7-EoJ0xKOyE47DlSpHXf0V4.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.7-EoJ0xKOyE47DlSpHXf0V4.a"; sourceTree = "<group>"; };
|
||||||
|
5CCD1A5F2B27927E001A4199 /* libHSsimplex-chat-5.4.0.7-EoJ0xKOyE47DlSpHXf0V4-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.7-EoJ0xKOyE47DlSpHXf0V4-ghc8.10.7.a"; sourceTree = "<group>"; };
|
||||||
5CCD403327A5F6DF00368C90 /* AddContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactView.swift; sourceTree = "<group>"; };
|
5CCD403327A5F6DF00368C90 /* AddContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactView.swift; sourceTree = "<group>"; };
|
||||||
5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanToConnectView.swift; sourceTree = "<group>"; };
|
5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanToConnectView.swift; sourceTree = "<group>"; };
|
||||||
5CD67B8D2B0E858A00C510B1 /* hs_init.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hs_init.h; sourceTree = "<group>"; };
|
5CD67B8D2B0E858A00C510B1 /* hs_init.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hs_init.h; sourceTree = "<group>"; };
|
||||||
@ -527,13 +527,13 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
5C8EA13F2B25206A001DE5E4 /* libHSsimplex-chat-5.4.0.7-1uCDT6bmj7t4ctyD1vFaZX-ghc9.6.3.a in Frameworks */,
|
5CCD1A602B27927E001A4199 /* libgmpxx.a in Frameworks */,
|
||||||
5C8EA1402B25206A001DE5E4 /* libffi.a in Frameworks */,
|
|
||||||
5C8EA13E2B25206A001DE5E4 /* libgmpxx.a in Frameworks */,
|
|
||||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||||
5C8EA13D2B25206A001DE5E4 /* libgmp.a in Frameworks */,
|
5CCD1A612B27927E001A4199 /* libgmp.a in Frameworks */,
|
||||||
5C8EA1412B25206A001DE5E4 /* libHSsimplex-chat-5.4.0.7-1uCDT6bmj7t4ctyD1vFaZX.a in Frameworks */,
|
5CCD1A622B27927E001A4199 /* libffi.a in Frameworks */,
|
||||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||||
|
5CCD1A642B27927E001A4199 /* libHSsimplex-chat-5.4.0.7-EoJ0xKOyE47DlSpHXf0V4-ghc8.10.7.a in Frameworks */,
|
||||||
|
5CCD1A632B27927E001A4199 /* libHSsimplex-chat-5.4.0.7-EoJ0xKOyE47DlSpHXf0V4.a in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -595,11 +595,11 @@
|
|||||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
5C8EA13B2B25206A001DE5E4 /* libffi.a */,
|
5CCD1A5D2B27927E001A4199 /* libffi.a */,
|
||||||
5C8EA1382B25206A001DE5E4 /* libgmp.a */,
|
5CCD1A5C2B27927E001A4199 /* libgmp.a */,
|
||||||
5C8EA1392B25206A001DE5E4 /* libgmpxx.a */,
|
5CCD1A5B2B27927E001A4199 /* libgmpxx.a */,
|
||||||
5C8EA13A2B25206A001DE5E4 /* libHSsimplex-chat-5.4.0.7-1uCDT6bmj7t4ctyD1vFaZX-ghc9.6.3.a */,
|
5CCD1A5F2B27927E001A4199 /* libHSsimplex-chat-5.4.0.7-EoJ0xKOyE47DlSpHXf0V4-ghc8.10.7.a */,
|
||||||
5C8EA13C2B25206A001DE5E4 /* libHSsimplex-chat-5.4.0.7-1uCDT6bmj7t4ctyD1vFaZX.a */,
|
5CCD1A5E2B27927E001A4199 /* libHSsimplex-chat-5.4.0.7-EoJ0xKOyE47DlSpHXf0V4.a */,
|
||||||
);
|
);
|
||||||
path = Libraries;
|
path = Libraries;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1207,9 +1207,9 @@ public struct NetCfg: Codable, Equatable {
|
|||||||
public static let defaults: NetCfg = NetCfg(
|
public static let defaults: NetCfg = NetCfg(
|
||||||
socksProxy: nil,
|
socksProxy: nil,
|
||||||
sessionMode: TransportSessionMode.user,
|
sessionMode: TransportSessionMode.user,
|
||||||
tcpConnectTimeout: 15_000_000,
|
tcpConnectTimeout: 20_000_000,
|
||||||
tcpTimeout: 10_000_000,
|
tcpTimeout: 15_000_000,
|
||||||
tcpTimeoutPerKb: 30_000,
|
tcpTimeoutPerKb: 45_000,
|
||||||
tcpKeepAlive: KeepAliveOpts.defaults,
|
tcpKeepAlive: KeepAliveOpts.defaults,
|
||||||
smpPingInterval: 1200_000_000,
|
smpPingInterval: 1200_000_000,
|
||||||
smpPingCount: 3,
|
smpPingCount: 3,
|
||||||
|
@ -2800,9 +2800,9 @@ data class NetCfg(
|
|||||||
hostMode = HostMode.OnionViaSocks,
|
hostMode = HostMode.OnionViaSocks,
|
||||||
requiredHostMode = false,
|
requiredHostMode = false,
|
||||||
sessionMode = TransportSessionMode.User,
|
sessionMode = TransportSessionMode.User,
|
||||||
tcpConnectTimeout = 15_000_000,
|
tcpConnectTimeout = 20_000_000,
|
||||||
tcpTimeout = 10_000_000,
|
tcpTimeout = 15_000_000,
|
||||||
tcpTimeoutPerKb = 30_000,
|
tcpTimeoutPerKb = 45_000,
|
||||||
tcpKeepAlive = KeepAliveOpts.defaults,
|
tcpKeepAlive = KeepAliveOpts.defaults,
|
||||||
smpPingInterval = 1200_000_000,
|
smpPingInterval = 1200_000_000,
|
||||||
smpPingCount = 3
|
smpPingCount = 3
|
||||||
|
@ -154,20 +154,20 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
|
|||||||
SectionItemView {
|
SectionItemView {
|
||||||
TimeoutSettingRow(
|
TimeoutSettingRow(
|
||||||
stringResource(MR.strings.network_option_tcp_connection_timeout), networkTCPConnectTimeout,
|
stringResource(MR.strings.network_option_tcp_connection_timeout), networkTCPConnectTimeout,
|
||||||
listOf(5_000000, 7_500000, 10_000000, 15_000000, 20_000000, 30_000_000, 45_000_000), secondsLabel
|
listOf(7_500000, 10_000000, 15_000000, 20_000000, 30_000_000, 45_000_000), secondsLabel
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SectionItemView {
|
SectionItemView {
|
||||||
TimeoutSettingRow(
|
TimeoutSettingRow(
|
||||||
stringResource(MR.strings.network_option_protocol_timeout), networkTCPTimeout,
|
stringResource(MR.strings.network_option_protocol_timeout), networkTCPTimeout,
|
||||||
listOf(3_000000, 5_000000, 7_000000, 10_000000, 15_000000, 20_000_000, 30_000_000), secondsLabel
|
listOf(5_000000, 7_000000, 10_000000, 15_000000, 20_000_000, 30_000_000), secondsLabel
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SectionItemView {
|
SectionItemView {
|
||||||
// can't be higher than 130ms to avoid overflow on 32bit systems
|
// can't be higher than 130ms to avoid overflow on 32bit systems
|
||||||
TimeoutSettingRow(
|
TimeoutSettingRow(
|
||||||
stringResource(MR.strings.network_option_protocol_timeout_per_kb), networkTCPTimeoutPerKb,
|
stringResource(MR.strings.network_option_protocol_timeout_per_kb), networkTCPTimeoutPerKb,
|
||||||
listOf(15_000, 30_000, 60_000, 90_000, 120_000), secondsLabel
|
listOf(15_000, 30_000, 45_000, 60_000, 90_000, 120_000), secondsLabel
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SectionItemView {
|
SectionItemView {
|
||||||
|
@ -4,25 +4,23 @@ packages: .
|
|||||||
|
|
||||||
with-compiler: ghc-8.10.7
|
with-compiler: ghc-8.10.7
|
||||||
|
|
||||||
index-state: 2023-10-06T00:00:00Z
|
index-state: 2023-12-12T00:00:00Z
|
||||||
|
|
||||||
|
package cryptostore
|
||||||
|
flags: +use_crypton
|
||||||
|
|
||||||
constraints: zip +disable-bzip2 +disable-zstd
|
constraints: zip +disable-bzip2 +disable-zstd
|
||||||
|
|
||||||
source-repository-package
|
source-repository-package
|
||||||
type: git
|
type: git
|
||||||
location: https://github.com/simplex-chat/simplexmq.git
|
location: https://github.com/simplex-chat/simplexmq.git
|
||||||
tag: 560dc553127851fa1fb201d0a9c80dcf1ad6e5dc
|
tag: 18be2709f59a4cb20fe9758b899622092dba062e
|
||||||
|
|
||||||
source-repository-package
|
source-repository-package
|
||||||
type: git
|
type: git
|
||||||
location: https://github.com/simplex-chat/hs-socks.git
|
location: https://github.com/simplex-chat/hs-socks.git
|
||||||
tag: a30cc7a79a08d8108316094f8f2f82a0c5e1ac51
|
tag: a30cc7a79a08d8108316094f8f2f82a0c5e1ac51
|
||||||
|
|
||||||
source-repository-package
|
|
||||||
type: git
|
|
||||||
location: https://github.com/kazu-yamamoto/http2.git
|
|
||||||
tag: f5525b755ff2418e6e6ecc69e877363b0d0bcaeb
|
|
||||||
|
|
||||||
source-repository-package
|
source-repository-package
|
||||||
type: git
|
type: git
|
||||||
location: https://github.com/simplex-chat/direct-sqlcipher.git
|
location: https://github.com/simplex-chat/direct-sqlcipher.git
|
||||||
|
@ -7,7 +7,7 @@ revision: 25.11.2023
|
|||||||
| Updated 25.11.2023 | Languages: EN |
|
| Updated 25.11.2023 | Languages: EN |
|
||||||
# Download SimpleX apps
|
# Download SimpleX apps
|
||||||
|
|
||||||
The latest stable version is v5.4.0.
|
The latest stable version is v5.4.1.
|
||||||
|
|
||||||
You can get the latest beta releases from [GitHub](https://github.com/simplex-chat/simplex-chat/releases).
|
You can get the latest beta releases from [GitHub](https://github.com/simplex-chat/simplex-chat/releases).
|
||||||
|
|
||||||
@ -21,24 +21,24 @@ You can get the latest beta releases from [GitHub](https://github.com/simplex-ch
|
|||||||
|
|
||||||
Using the same profile as on mobile device is not yet supported – you need to create a separate profile to use desktop apps.
|
Using the same profile as on mobile device is not yet supported – you need to create a separate profile to use desktop apps.
|
||||||
|
|
||||||
**Linux**: [AppImage](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.0/simplex-desktop-x86_64.AppImage) (most Linux distros), [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.0/simplex-desktop-ubuntu-20_04-x86_64.deb) (and Debian-based distros), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.0/simplex-desktop-ubuntu-22_04-x86_64.deb).
|
**Linux**: [AppImage](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-desktop-x86_64.AppImage) (most Linux distros), [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-desktop-ubuntu-20_04-x86_64.deb) (and Debian-based distros), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-desktop-ubuntu-22_04-x86_64.deb).
|
||||||
|
|
||||||
**Mac**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.0/simplex-desktop-macos-x86_64.dmg) (Intel), [aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.0/simplex-desktop-macos-aarch64.dmg) (Apple Silicon).
|
**Mac**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-desktop-macos-x86_64.dmg) (Intel), [aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-desktop-macos-aarch64.dmg) (Apple Silicon).
|
||||||
|
|
||||||
**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.0/simplex-desktop-windows-x86_64.msi).
|
**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-desktop-windows-x86_64.msi).
|
||||||
|
|
||||||
## Mobile apps
|
## Mobile apps
|
||||||
|
|
||||||
**iOS**: [App store](https://apps.apple.com/us/app/simplex-chat/id1605771084), [TestFlight](https://testflight.apple.com/join/DWuT2LQu).
|
**iOS**: [App store](https://apps.apple.com/us/app/simplex-chat/id1605771084), [TestFlight](https://testflight.apple.com/join/DWuT2LQu).
|
||||||
|
|
||||||
**Android**: [Play store](https://play.google.com/store/apps/details?id=chat.simplex.app), [F-Droid](https://simplex.chat/fdroid/), [APK aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.0/simplex.apk), [APK armv7](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.0/simplex-armv7a.apk).
|
**Android**: [Play store](https://play.google.com/store/apps/details?id=chat.simplex.app), [F-Droid](https://simplex.chat/fdroid/), [APK aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex.apk), [APK armv7](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-armv7a.apk).
|
||||||
|
|
||||||
## Terminal (console) app
|
## Terminal (console) app
|
||||||
|
|
||||||
See [Using terminal app](/docs/CLI.md).
|
See [Using terminal app](/docs/CLI.md).
|
||||||
|
|
||||||
**Linux**: [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.0/simplex-chat-ubuntu-20_04-x86-64), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.0/simplex-chat-ubuntu-22_04-x86-64).
|
**Linux**: [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-chat-ubuntu-20_04-x86-64), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-chat-ubuntu-22_04-x86-64).
|
||||||
|
|
||||||
**Mac** [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.0/simplex-chat-macos-x86-64), aarch64 - [compile from source](./CLI.md#).
|
**Mac** [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-chat-macos-x86-64), aarch64 - [compile from source](./CLI.md#).
|
||||||
|
|
||||||
**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.0/simplex-chat-windows-x86-64).
|
**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-chat-windows-x86-64).
|
||||||
|
@ -288,11 +288,11 @@
|
|||||||
"hackage": {
|
"hackage": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1696724662,
|
"lastModified": 1702340598,
|
||||||
"narHash": "sha256-jV2ugSjZE0FjMYR2YIx0p2cDBqd+xxhZrRxp5BmieYk=",
|
"narHash": "sha256-CC0HI+6iKPtH+8r/ZfcpW5v/OYvL7zMwpr0xfkXV1zU=",
|
||||||
"owner": "input-output-hk",
|
"owner": "input-output-hk",
|
||||||
"repo": "hackage.nix",
|
"repo": "hackage.nix",
|
||||||
"rev": "df603bff8606d8653a0876ae0c3fd1f9014882f2",
|
"rev": "24617c569995e38bf3b83b48eec6628a50fdb4fb",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
let pkgs = haskellNix.legacyPackages.${system}.appendOverlays [android26]; in
|
let pkgs = haskellNix.legacyPackages.${system}.appendOverlays [android26]; in
|
||||||
let drv' = { extra-modules, pkgs', ... }: pkgs'.haskell-nix.project {
|
let drv' = { extra-modules, pkgs', ... }: pkgs'.haskell-nix.project {
|
||||||
compiler-nix-name = "ghc8107";
|
compiler-nix-name = "ghc8107";
|
||||||
index-state = "2023-10-06T00:00:00Z";
|
index-state = "2023-12-12T00:00:00Z";
|
||||||
# We need this, to specify we want the cabal project.
|
# We need this, to specify we want the cabal project.
|
||||||
# If the stack.yaml was dropped, this would not be necessary.
|
# If the stack.yaml was dropped, this would not be necessary.
|
||||||
projectFileName = "cabal.project";
|
projectFileName = "cabal.project";
|
||||||
|
@ -22,7 +22,7 @@ dependencies:
|
|||||||
- composition == 1.0.*
|
- composition == 1.0.*
|
||||||
- constraints >= 0.12 && < 0.14
|
- constraints >= 0.12 && < 0.14
|
||||||
- containers == 0.6.*
|
- containers == 0.6.*
|
||||||
- cryptonite == 0.30.*
|
- crypton == 0.34.*
|
||||||
- data-default >= 0.7 && < 0.8
|
- data-default >= 0.7 && < 0.8
|
||||||
- directory == 1.3.*
|
- directory == 1.3.*
|
||||||
- direct-sqlcipher == 2.3.*
|
- direct-sqlcipher == 2.3.*
|
||||||
@ -46,7 +46,7 @@ dependencies:
|
|||||||
- stm == 2.5.*
|
- stm == 2.5.*
|
||||||
- terminal == 0.2.*
|
- terminal == 0.2.*
|
||||||
- time == 1.9.*
|
- time == 1.9.*
|
||||||
- tls >= 1.6.0 && < 1.7
|
- tls >= 1.7.0 && < 1.8
|
||||||
- unliftio == 0.2.*
|
- unliftio == 0.2.*
|
||||||
- unliftio-core == 0.2.*
|
- unliftio-core == 0.2.*
|
||||||
- zip == 2.0.*
|
- zip == 2.0.*
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
{
|
{
|
||||||
"https://github.com/simplex-chat/simplexmq.git"."560dc553127851fa1fb201d0a9c80dcf1ad6e5dc" = "1xz3lw5dsh7gm136jzwmsbqjigsqsnjlbhg38mpc6lm586lg8f9x";
|
"https://github.com/simplex-chat/simplexmq.git"."18be2709f59a4cb20fe9758b899622092dba062e" = "08dr4vyg1wz2z768iikg8fks5zqf4dw5myr87hbpv964idda3pmj";
|
||||||
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
|
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
|
||||||
"https://github.com/kazu-yamamoto/http2.git"."f5525b755ff2418e6e6ecc69e877363b0d0bcaeb" = "0fyx0047gvhm99ilp212mmz37j84cwrfnpmssib5dw363fyb88b6";
|
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
|
||||||
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "0kiwhvml42g9anw4d2v0zd1fpc790pj9syg5x3ik4l97fnkbbwpp";
|
|
||||||
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
|
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
|
||||||
"https://github.com/simplex-chat/aeson.git"."aab7b5a14d6c5ea64c64dcaee418de1bb00dcc2b" = "0jz7kda8gai893vyvj96fy962ncv8dcsx71fbddyy8zrvc88jfrr";
|
"https://github.com/simplex-chat/aeson.git"."aab7b5a14d6c5ea64c64dcaee418de1bb00dcc2b" = "0jz7kda8gai893vyvj96fy962ncv8dcsx71fbddyy8zrvc88jfrr";
|
||||||
"https://github.com/simplex-chat/haskell-terminal.git"."f708b00009b54890172068f168bf98508ffcd495" = "0zmq7lmfsk8m340g47g5963yba7i88n4afa6z93sg9px5jv1mijj";
|
"https://github.com/simplex-chat/haskell-terminal.git"."f708b00009b54890172068f168bf98508ffcd495" = "0zmq7lmfsk8m340g47g5963yba7i88n4afa6z93sg9px5jv1mijj";
|
||||||
|
@ -175,7 +175,7 @@ library
|
|||||||
, composition ==1.0.*
|
, composition ==1.0.*
|
||||||
, constraints >=0.12 && <0.14
|
, constraints >=0.12 && <0.14
|
||||||
, containers ==0.6.*
|
, containers ==0.6.*
|
||||||
, cryptonite ==0.30.*
|
, crypton ==0.34.*
|
||||||
, data-default ==0.7.*
|
, data-default ==0.7.*
|
||||||
, direct-sqlcipher ==2.3.*
|
, direct-sqlcipher ==2.3.*
|
||||||
, directory ==1.3.*
|
, directory ==1.3.*
|
||||||
@ -199,7 +199,7 @@ library
|
|||||||
, stm ==2.5.*
|
, stm ==2.5.*
|
||||||
, terminal ==0.2.*
|
, terminal ==0.2.*
|
||||||
, time ==1.9.*
|
, time ==1.9.*
|
||||||
, tls >=1.6.0 && <1.7
|
, tls >=1.7.0 && <1.8
|
||||||
, unliftio ==0.2.*
|
, unliftio ==0.2.*
|
||||||
, unliftio-core ==0.2.*
|
, unliftio-core ==0.2.*
|
||||||
, zip ==2.0.*
|
, zip ==2.0.*
|
||||||
@ -234,7 +234,7 @@ executable simplex-bot
|
|||||||
, composition ==1.0.*
|
, composition ==1.0.*
|
||||||
, constraints >=0.12 && <0.14
|
, constraints >=0.12 && <0.14
|
||||||
, containers ==0.6.*
|
, containers ==0.6.*
|
||||||
, cryptonite ==0.30.*
|
, crypton ==0.34.*
|
||||||
, data-default ==0.7.*
|
, data-default ==0.7.*
|
||||||
, direct-sqlcipher ==2.3.*
|
, direct-sqlcipher ==2.3.*
|
||||||
, directory ==1.3.*
|
, directory ==1.3.*
|
||||||
@ -259,7 +259,7 @@ executable simplex-bot
|
|||||||
, stm ==2.5.*
|
, stm ==2.5.*
|
||||||
, terminal ==0.2.*
|
, terminal ==0.2.*
|
||||||
, time ==1.9.*
|
, time ==1.9.*
|
||||||
, tls >=1.6.0 && <1.7
|
, tls >=1.7.0 && <1.8
|
||||||
, unliftio ==0.2.*
|
, unliftio ==0.2.*
|
||||||
, unliftio-core ==0.2.*
|
, unliftio-core ==0.2.*
|
||||||
, zip ==2.0.*
|
, zip ==2.0.*
|
||||||
@ -294,7 +294,7 @@ executable simplex-bot-advanced
|
|||||||
, composition ==1.0.*
|
, composition ==1.0.*
|
||||||
, constraints >=0.12 && <0.14
|
, constraints >=0.12 && <0.14
|
||||||
, containers ==0.6.*
|
, containers ==0.6.*
|
||||||
, cryptonite ==0.30.*
|
, crypton ==0.34.*
|
||||||
, data-default ==0.7.*
|
, data-default ==0.7.*
|
||||||
, direct-sqlcipher ==2.3.*
|
, direct-sqlcipher ==2.3.*
|
||||||
, directory ==1.3.*
|
, directory ==1.3.*
|
||||||
@ -319,7 +319,7 @@ executable simplex-bot-advanced
|
|||||||
, stm ==2.5.*
|
, stm ==2.5.*
|
||||||
, terminal ==0.2.*
|
, terminal ==0.2.*
|
||||||
, time ==1.9.*
|
, time ==1.9.*
|
||||||
, tls >=1.6.0 && <1.7
|
, tls >=1.7.0 && <1.8
|
||||||
, unliftio ==0.2.*
|
, unliftio ==0.2.*
|
||||||
, unliftio-core ==0.2.*
|
, unliftio-core ==0.2.*
|
||||||
, zip ==2.0.*
|
, zip ==2.0.*
|
||||||
@ -356,7 +356,7 @@ executable simplex-broadcast-bot
|
|||||||
, composition ==1.0.*
|
, composition ==1.0.*
|
||||||
, constraints >=0.12 && <0.14
|
, constraints >=0.12 && <0.14
|
||||||
, containers ==0.6.*
|
, containers ==0.6.*
|
||||||
, cryptonite ==0.30.*
|
, crypton ==0.34.*
|
||||||
, data-default ==0.7.*
|
, data-default ==0.7.*
|
||||||
, direct-sqlcipher ==2.3.*
|
, direct-sqlcipher ==2.3.*
|
||||||
, directory ==1.3.*
|
, directory ==1.3.*
|
||||||
@ -381,7 +381,7 @@ executable simplex-broadcast-bot
|
|||||||
, stm ==2.5.*
|
, stm ==2.5.*
|
||||||
, terminal ==0.2.*
|
, terminal ==0.2.*
|
||||||
, time ==1.9.*
|
, time ==1.9.*
|
||||||
, tls >=1.6.0 && <1.7
|
, tls >=1.7.0 && <1.8
|
||||||
, unliftio ==0.2.*
|
, unliftio ==0.2.*
|
||||||
, unliftio-core ==0.2.*
|
, unliftio-core ==0.2.*
|
||||||
, zip ==2.0.*
|
, zip ==2.0.*
|
||||||
@ -417,7 +417,7 @@ executable simplex-chat
|
|||||||
, composition ==1.0.*
|
, composition ==1.0.*
|
||||||
, constraints >=0.12 && <0.14
|
, constraints >=0.12 && <0.14
|
||||||
, containers ==0.6.*
|
, containers ==0.6.*
|
||||||
, cryptonite ==0.30.*
|
, crypton ==0.34.*
|
||||||
, data-default ==0.7.*
|
, data-default ==0.7.*
|
||||||
, direct-sqlcipher ==2.3.*
|
, direct-sqlcipher ==2.3.*
|
||||||
, directory ==1.3.*
|
, directory ==1.3.*
|
||||||
@ -442,7 +442,7 @@ executable simplex-chat
|
|||||||
, stm ==2.5.*
|
, stm ==2.5.*
|
||||||
, terminal ==0.2.*
|
, terminal ==0.2.*
|
||||||
, time ==1.9.*
|
, time ==1.9.*
|
||||||
, tls >=1.6.0 && <1.7
|
, tls >=1.7.0 && <1.8
|
||||||
, unliftio ==0.2.*
|
, unliftio ==0.2.*
|
||||||
, unliftio-core ==0.2.*
|
, unliftio-core ==0.2.*
|
||||||
, websockets ==0.12.*
|
, websockets ==0.12.*
|
||||||
@ -482,7 +482,7 @@ executable simplex-directory-service
|
|||||||
, composition ==1.0.*
|
, composition ==1.0.*
|
||||||
, constraints >=0.12 && <0.14
|
, constraints >=0.12 && <0.14
|
||||||
, containers ==0.6.*
|
, containers ==0.6.*
|
||||||
, cryptonite ==0.30.*
|
, crypton ==0.34.*
|
||||||
, data-default ==0.7.*
|
, data-default ==0.7.*
|
||||||
, direct-sqlcipher ==2.3.*
|
, direct-sqlcipher ==2.3.*
|
||||||
, directory ==1.3.*
|
, directory ==1.3.*
|
||||||
@ -507,7 +507,7 @@ executable simplex-directory-service
|
|||||||
, stm ==2.5.*
|
, stm ==2.5.*
|
||||||
, terminal ==0.2.*
|
, terminal ==0.2.*
|
||||||
, time ==1.9.*
|
, time ==1.9.*
|
||||||
, tls >=1.6.0 && <1.7
|
, tls >=1.7.0 && <1.8
|
||||||
, unliftio ==0.2.*
|
, unliftio ==0.2.*
|
||||||
, unliftio-core ==0.2.*
|
, unliftio-core ==0.2.*
|
||||||
, zip ==2.0.*
|
, zip ==2.0.*
|
||||||
@ -571,7 +571,7 @@ test-suite simplex-chat-test
|
|||||||
, composition ==1.0.*
|
, composition ==1.0.*
|
||||||
, constraints >=0.12 && <0.14
|
, constraints >=0.12 && <0.14
|
||||||
, containers ==0.6.*
|
, containers ==0.6.*
|
||||||
, cryptonite ==0.30.*
|
, crypton ==0.34.*
|
||||||
, data-default ==0.7.*
|
, data-default ==0.7.*
|
||||||
, deepseq ==1.4.*
|
, deepseq ==1.4.*
|
||||||
, direct-sqlcipher ==2.3.*
|
, direct-sqlcipher ==2.3.*
|
||||||
@ -600,7 +600,7 @@ test-suite simplex-chat-test
|
|||||||
, stm ==2.5.*
|
, stm ==2.5.*
|
||||||
, terminal ==0.2.*
|
, terminal ==0.2.*
|
||||||
, time ==1.9.*
|
, time ==1.9.*
|
||||||
, tls >=1.6.0 && <1.7
|
, tls >=1.7.0 && <1.8
|
||||||
, unliftio ==0.2.*
|
, unliftio ==0.2.*
|
||||||
, unliftio-core ==0.2.*
|
, unliftio-core ==0.2.*
|
||||||
, zip ==2.0.*
|
, zip ==2.0.*
|
||||||
|
@ -26,9 +26,6 @@ CREATE INDEX idx_contacts_chat_ts ON contacts(user_id, chat_ts);
|
|||||||
CREATE INDEX idx_groups_chat_ts ON groups(user_id, chat_ts);
|
CREATE INDEX idx_groups_chat_ts ON groups(user_id, chat_ts);
|
||||||
CREATE INDEX idx_contact_requests_updated_at ON contact_requests(user_id, updated_at);
|
CREATE INDEX idx_contact_requests_updated_at ON contact_requests(user_id, updated_at);
|
||||||
CREATE INDEX idx_connections_updated_at ON connections(user_id, updated_at);
|
CREATE INDEX idx_connections_updated_at ON connections(user_id, updated_at);
|
||||||
|
|
||||||
CREATE INDEX idx_chat_items_contact_id_item_status ON chat_items(contact_id, item_status);
|
|
||||||
CREATE INDEX idx_chat_items_group_id_item_status ON chat_items(group_id, item_status);
|
|
||||||
|]
|
|]
|
||||||
|
|
||||||
down_m20231207_chat_list_pagination :: Query
|
down_m20231207_chat_list_pagination :: Query
|
||||||
@ -38,7 +35,4 @@ DROP INDEX idx_contacts_chat_ts;
|
|||||||
DROP INDEX idx_groups_chat_ts;
|
DROP INDEX idx_groups_chat_ts;
|
||||||
DROP INDEX idx_contact_requests_updated_at;
|
DROP INDEX idx_contact_requests_updated_at;
|
||||||
DROP INDEX idx_connections_updated_at;
|
DROP INDEX idx_connections_updated_at;
|
||||||
|
|
||||||
DROP INDEX idx_chat_items_contact_id_item_status;
|
|
||||||
DROP INDEX idx_chat_items_group_id_item_status;
|
|
||||||
|]
|
|]
|
||||||
|
@ -817,11 +817,3 @@ CREATE INDEX idx_contact_requests_updated_at ON contact_requests(
|
|||||||
updated_at
|
updated_at
|
||||||
);
|
);
|
||||||
CREATE INDEX idx_connections_updated_at ON connections(user_id, updated_at);
|
CREATE INDEX idx_connections_updated_at ON connections(user_id, updated_at);
|
||||||
CREATE INDEX idx_chat_items_contact_id_item_status ON chat_items(
|
|
||||||
contact_id,
|
|
||||||
item_status
|
|
||||||
);
|
|
||||||
CREATE INDEX idx_chat_items_group_id_item_status ON chat_items(
|
|
||||||
group_id,
|
|
||||||
item_status
|
|
||||||
);
|
|
||||||
|
@ -499,8 +499,8 @@ getChatPreviews db user withPCC pagination query = do
|
|||||||
where
|
where
|
||||||
ts :: AChatPreviewData -> UTCTime
|
ts :: AChatPreviewData -> UTCTime
|
||||||
ts (ACPD _ cpd) = case cpd of
|
ts (ACPD _ cpd) = case cpd of
|
||||||
(DirectChatPD t _ _) -> t
|
(DirectChatPD t _ _ _) -> t
|
||||||
(GroupChatPD t _ _) -> t
|
(GroupChatPD t _ _ _) -> t
|
||||||
(ContactRequestPD t _) -> t
|
(ContactRequestPD t _) -> t
|
||||||
(ContactConnectionPD t _) -> t
|
(ContactConnectionPD t _) -> t
|
||||||
sortTake = case pagination of
|
sortTake = case pagination of
|
||||||
@ -515,8 +515,8 @@ getChatPreviews db user withPCC pagination query = do
|
|||||||
SCTContactConnection -> let (ContactConnectionPD _ chat) = cpd in pure chat
|
SCTContactConnection -> let (ContactConnectionPD _ chat) = cpd in pure chat
|
||||||
|
|
||||||
data ChatPreviewData (c :: ChatType) where
|
data ChatPreviewData (c :: ChatType) where
|
||||||
DirectChatPD :: UTCTime -> ContactId -> Maybe ChatStats -> ChatPreviewData 'CTDirect
|
DirectChatPD :: UTCTime -> ContactId -> Maybe ChatItemId -> ChatStats -> ChatPreviewData 'CTDirect
|
||||||
GroupChatPD :: UTCTime -> GroupId -> Maybe ChatStats -> ChatPreviewData 'CTGroup
|
GroupChatPD :: UTCTime -> GroupId -> Maybe ChatItemId -> ChatStats -> ChatPreviewData 'CTGroup
|
||||||
ContactRequestPD :: UTCTime -> AChat -> ChatPreviewData 'CTContactRequest
|
ContactRequestPD :: UTCTime -> AChat -> ChatPreviewData 'CTContactRequest
|
||||||
ContactConnectionPD :: UTCTime -> AChat -> ChatPreviewData 'CTContactConnection
|
ContactConnectionPD :: UTCTime -> AChat -> ChatPreviewData 'CTContactConnection
|
||||||
|
|
||||||
@ -528,283 +528,200 @@ paginationByTimeFilter = \case
|
|||||||
PTAfter ts count -> ("\nAND ts > :ts ORDER BY ts ASC LIMIT :count", [":ts" := ts, ":count" := count])
|
PTAfter ts count -> ("\nAND ts > :ts ORDER BY ts ASC LIMIT :count", [":ts" := ts, ":count" := count])
|
||||||
PTBefore ts count -> ("\nAND ts < :ts ORDER BY ts DESC LIMIT :count", [":ts" := ts, ":count" := count])
|
PTBefore ts count -> ("\nAND ts < :ts ORDER BY ts DESC LIMIT :count", [":ts" := ts, ":count" := count])
|
||||||
|
|
||||||
type MaybeChatStatsRow = (Maybe Int, Maybe ChatItemId, Maybe Bool)
|
type ChatStatsRow = (Int, ChatItemId, Bool)
|
||||||
|
|
||||||
toMaybeChatStats :: MaybeChatStatsRow -> Maybe ChatStats
|
toChatStats :: ChatStatsRow -> ChatStats
|
||||||
toMaybeChatStats (Just unreadCount, Just minUnreadItemId, Just unreadChat) = Just ChatStats {unreadCount, minUnreadItemId, unreadChat}
|
toChatStats (unreadCount, minUnreadItemId, unreadChat) = ChatStats {unreadCount, minUnreadItemId, unreadChat}
|
||||||
toMaybeChatStats _ = Nothing
|
|
||||||
|
|
||||||
findDirectChatPreviews_ :: DB.Connection -> User -> PaginationByTime -> ChatListQuery -> IO [AChatPreviewData]
|
findDirectChatPreviews_ :: DB.Connection -> User -> PaginationByTime -> ChatListQuery -> IO [AChatPreviewData]
|
||||||
findDirectChatPreviews_ db User {userId} pagination clq =
|
findDirectChatPreviews_ db User {userId} pagination clq =
|
||||||
map toPreview <$> getPreviews
|
map toPreview <$> getPreviews
|
||||||
where
|
where
|
||||||
toPreview :: (ContactId, UTCTime) :. MaybeChatStatsRow -> AChatPreviewData
|
toPreview :: (ContactId, UTCTime, Maybe ChatItemId) :. ChatStatsRow -> AChatPreviewData
|
||||||
toPreview ((contactId, ts) :. statsRow_) =
|
toPreview ((contactId, ts, lastItemId_) :. statsRow) =
|
||||||
ACPD SCTDirect $ DirectChatPD ts contactId (toMaybeChatStats statsRow_)
|
ACPD SCTDirect $ DirectChatPD ts contactId lastItemId_ (toChatStats statsRow)
|
||||||
|
baseQuery =
|
||||||
|
[sql|
|
||||||
|
SELECT ct.contact_id, ct.chat_ts as ts, LastItems.chat_item_id, COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), ct.unread_chat
|
||||||
|
FROM contacts ct
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT contact_id, chat_item_id, MAX(created_at)
|
||||||
|
FROM chat_items
|
||||||
|
GROUP BY contact_id
|
||||||
|
) LastItems ON LastItems.contact_id = ct.contact_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT contact_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread
|
||||||
|
FROM chat_items
|
||||||
|
WHERE item_status = :rcv_new
|
||||||
|
GROUP BY contact_id
|
||||||
|
) ChatStats ON ChatStats.contact_id = ct.contact_id
|
||||||
|
|]
|
||||||
(pagQuery, pagParams) = paginationByTimeFilter pagination
|
(pagQuery, pagParams) = paginationByTimeFilter pagination
|
||||||
getPreviews = case clq of
|
getPreviews = case clq of
|
||||||
CLQFilters {favorite = False, unread = False} ->
|
CLQFilters {favorite = False, unread = False} ->
|
||||||
DB.queryNamed
|
DB.queryNamed
|
||||||
db
|
db
|
||||||
( [sql|
|
( baseQuery
|
||||||
SELECT ct.contact_id, ct.chat_ts as ts, NULL, NULL, NULL
|
<> [sql|
|
||||||
FROM contacts ct
|
WHERE ct.user_id = :user_id AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used
|
||||||
WHERE ct.user_id = :user_id AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used
|
|]
|
||||||
|]
|
|
||||||
<> pagQuery
|
<> pagQuery
|
||||||
)
|
)
|
||||||
([":user_id" := userId] <> pagParams)
|
([":user_id" := userId, ":rcv_new" := CISRcvNew] <> pagParams)
|
||||||
CLQFilters {favorite = True, unread = False} ->
|
CLQFilters {favorite = True, unread = False} ->
|
||||||
DB.queryNamed
|
DB.queryNamed
|
||||||
db
|
db
|
||||||
( [sql|
|
( baseQuery
|
||||||
SELECT ct.contact_id, ct.chat_ts as ts, NULL, NULL, NULL
|
<> [sql|
|
||||||
FROM contacts ct
|
WHERE ct.user_id = :user_id AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used
|
||||||
WHERE ct.user_id = :user_id AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used
|
AND ct.favorite = 1
|
||||||
AND ct.favorite = 1
|
|]
|
||||||
|]
|
|
||||||
<> pagQuery
|
<> pagQuery
|
||||||
)
|
)
|
||||||
([":user_id" := userId] <> pagParams)
|
([":user_id" := userId, ":rcv_new" := CISRcvNew] <> pagParams)
|
||||||
CLQFilters {favorite = False, unread = True} ->
|
CLQFilters {favorite = False, unread = True} ->
|
||||||
DB.queryNamed
|
DB.queryNamed
|
||||||
db
|
db
|
||||||
( [sql|
|
( baseQuery
|
||||||
SELECT ct.contact_id, ct.chat_ts as ts, COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), ct.unread_chat
|
<> [sql|
|
||||||
FROM contacts ct
|
WHERE ct.user_id = :user_id AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used
|
||||||
LEFT JOIN (
|
AND (ct.unread_chat = 1 OR ChatStats.UnreadCount > 0)
|
||||||
SELECT contact_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread
|
|]
|
||||||
FROM chat_items
|
|
||||||
WHERE item_status = :rcv_new
|
|
||||||
GROUP BY contact_id
|
|
||||||
) ChatStats ON ChatStats.contact_id = ct.contact_id
|
|
||||||
WHERE ct.user_id = :user_id AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used
|
|
||||||
AND (ct.unread_chat = 1 OR ChatStats.UnreadCount > 0)
|
|
||||||
|]
|
|
||||||
<> pagQuery
|
<> pagQuery
|
||||||
)
|
)
|
||||||
([":user_id" := userId, ":rcv_new" := CISRcvNew] <> pagParams)
|
([":user_id" := userId, ":rcv_new" := CISRcvNew] <> pagParams)
|
||||||
CLQFilters {favorite = True, unread = True} ->
|
CLQFilters {favorite = True, unread = True} ->
|
||||||
DB.queryNamed
|
DB.queryNamed
|
||||||
db
|
db
|
||||||
( [sql|
|
( baseQuery
|
||||||
SELECT ct.contact_id, ct.chat_ts as ts, COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), ct.unread_chat
|
<> [sql|
|
||||||
FROM contacts ct
|
WHERE ct.user_id = :user_id AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used
|
||||||
LEFT JOIN (
|
AND (ct.favorite = 1
|
||||||
SELECT contact_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread
|
OR ct.unread_chat = 1 OR ChatStats.UnreadCount > 0)
|
||||||
FROM chat_items
|
|]
|
||||||
WHERE item_status = :rcv_new
|
|
||||||
GROUP BY contact_id
|
|
||||||
) ChatStats ON ChatStats.contact_id = ct.contact_id
|
|
||||||
WHERE ct.user_id = :user_id AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used
|
|
||||||
AND (ct.favorite = 1
|
|
||||||
OR ct.unread_chat = 1 OR ChatStats.UnreadCount > 0)
|
|
||||||
|]
|
|
||||||
<> pagQuery
|
<> pagQuery
|
||||||
)
|
)
|
||||||
([":user_id" := userId, ":rcv_new" := CISRcvNew] <> pagParams)
|
([":user_id" := userId, ":rcv_new" := CISRcvNew] <> pagParams)
|
||||||
CLQSearch {search} ->
|
CLQSearch {search} ->
|
||||||
DB.queryNamed
|
DB.queryNamed
|
||||||
db
|
db
|
||||||
( [sql|
|
( baseQuery
|
||||||
SELECT ct.contact_id, ct.chat_ts as ts, NULL, NULL, NULL
|
<> [sql|
|
||||||
FROM contacts ct
|
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
|
||||||
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
|
WHERE ct.user_id = :user_id AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used
|
||||||
WHERE ct.user_id = :user_id AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used
|
AND (
|
||||||
AND (
|
ct.local_display_name LIKE '%' || :search || '%'
|
||||||
ct.local_display_name LIKE '%' || :search || '%'
|
OR cp.display_name LIKE '%' || :search || '%'
|
||||||
OR cp.display_name LIKE '%' || :search || '%'
|
OR cp.full_name LIKE '%' || :search || '%'
|
||||||
OR cp.full_name LIKE '%' || :search || '%'
|
OR cp.local_alias LIKE '%' || :search || '%'
|
||||||
OR cp.local_alias LIKE '%' || :search || '%'
|
)
|
||||||
)
|
|]
|
||||||
|]
|
|
||||||
<> pagQuery
|
<> pagQuery
|
||||||
)
|
)
|
||||||
([":user_id" := userId, ":search" := search] <> pagParams)
|
([":user_id" := userId, ":rcv_new" := CISRcvNew, ":search" := search] <> pagParams)
|
||||||
|
|
||||||
getDirectChatPreview_ :: DB.Connection -> User -> ChatPreviewData 'CTDirect -> ExceptT StoreError IO AChat
|
getDirectChatPreview_ :: DB.Connection -> User -> ChatPreviewData 'CTDirect -> ExceptT StoreError IO AChat
|
||||||
getDirectChatPreview_ db user (DirectChatPD _ contactId stats_) = do
|
getDirectChatPreview_ db user (DirectChatPD _ contactId lastItemId_ stats) = do
|
||||||
contact <- getContact db user contactId
|
contact <- getContact db user contactId
|
||||||
lastItem <- getLastItem
|
lastItem <- case lastItemId_ of
|
||||||
stats <- maybe getChatStats pure stats_
|
Just lastItemId -> (: []) <$> getDirectChatItem db user contactId lastItemId
|
||||||
|
Nothing -> pure []
|
||||||
pure $ AChat SCTDirect (Chat (DirectChat contact) lastItem stats)
|
pure $ AChat SCTDirect (Chat (DirectChat contact) lastItem stats)
|
||||||
where
|
|
||||||
getLastItem :: ExceptT StoreError IO [CChatItem 'CTDirect]
|
|
||||||
getLastItem =
|
|
||||||
liftIO getLastItemId >>= \case
|
|
||||||
Nothing -> pure []
|
|
||||||
Just lastItemId -> (: []) <$> getDirectChatItem db user contactId lastItemId
|
|
||||||
getLastItemId :: IO (Maybe ChatItemId)
|
|
||||||
getLastItemId =
|
|
||||||
maybeFirstRow fromOnly $
|
|
||||||
DB.query
|
|
||||||
db
|
|
||||||
[sql|
|
|
||||||
SELECT chat_item_id FROM (
|
|
||||||
SELECT contact_id, chat_item_id, MAX(created_at)
|
|
||||||
FROM chat_items
|
|
||||||
WHERE contact_id = ?
|
|
||||||
GROUP BY contact_id
|
|
||||||
)
|
|
||||||
|]
|
|
||||||
(Only contactId)
|
|
||||||
getChatStats :: ExceptT StoreError IO ChatStats
|
|
||||||
getChatStats = do
|
|
||||||
r_ <- liftIO getUnreadStats
|
|
||||||
let (unreadCount, minUnreadItemId) = maybe (0, 0) (\(_, unreadCnt, minId) -> (unreadCnt, minId)) r_
|
|
||||||
-- unread_chat could be read into contact to not search twice
|
|
||||||
unreadChat <-
|
|
||||||
ExceptT . firstRow fromOnly (SEInternalError $ "unread_chat not found for contact " <> show contactId) $
|
|
||||||
DB.query db "SELECT unread_chat FROM contacts WHERE contact_id = ?" (Only contactId)
|
|
||||||
pure ChatStats {unreadCount, minUnreadItemId, unreadChat}
|
|
||||||
getUnreadStats :: IO (Maybe (ContactId, Int, ChatItemId))
|
|
||||||
getUnreadStats =
|
|
||||||
maybeFirstRow id $
|
|
||||||
DB.query
|
|
||||||
db
|
|
||||||
[sql|
|
|
||||||
SELECT contact_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread
|
|
||||||
FROM chat_items
|
|
||||||
WHERE contact_id = ? AND item_status = ?
|
|
||||||
GROUP BY contact_id
|
|
||||||
|]
|
|
||||||
(contactId, CISRcvNew)
|
|
||||||
|
|
||||||
findGroupChatPreviews_ :: DB.Connection -> User -> PaginationByTime -> ChatListQuery -> IO [AChatPreviewData]
|
findGroupChatPreviews_ :: DB.Connection -> User -> PaginationByTime -> ChatListQuery -> IO [AChatPreviewData]
|
||||||
findGroupChatPreviews_ db User {userId} pagination clq =
|
findGroupChatPreviews_ db User {userId} pagination clq =
|
||||||
map toPreview <$> getPreviews
|
map toPreview <$> getPreviews
|
||||||
where
|
where
|
||||||
toPreview :: (GroupId, UTCTime) :. MaybeChatStatsRow -> AChatPreviewData
|
toPreview :: (GroupId, UTCTime, Maybe ChatItemId) :. ChatStatsRow -> AChatPreviewData
|
||||||
toPreview ((groupId, ts) :. statsRow_) =
|
toPreview ((groupId, ts, lastItemId_) :. statsRow) =
|
||||||
ACPD SCTGroup $ GroupChatPD ts groupId (toMaybeChatStats statsRow_)
|
ACPD SCTGroup $ GroupChatPD ts groupId lastItemId_ (toChatStats statsRow)
|
||||||
|
baseQuery =
|
||||||
|
[sql|
|
||||||
|
SELECT g.group_id, g.chat_ts as ts, LastItems.chat_item_id, COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat
|
||||||
|
FROM groups g
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT group_id, chat_item_id, MAX(item_ts)
|
||||||
|
FROM chat_items
|
||||||
|
GROUP BY group_id
|
||||||
|
) LastItems ON LastItems.group_id = g.group_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread
|
||||||
|
FROM chat_items
|
||||||
|
WHERE item_status = :rcv_new
|
||||||
|
GROUP BY group_id
|
||||||
|
) ChatStats ON ChatStats.group_id = g.group_id
|
||||||
|
|]
|
||||||
(pagQuery, pagParams) = paginationByTimeFilter pagination
|
(pagQuery, pagParams) = paginationByTimeFilter pagination
|
||||||
getPreviews = case clq of
|
getPreviews = case clq of
|
||||||
CLQFilters {favorite = False, unread = False} ->
|
CLQFilters {favorite = False, unread = False} ->
|
||||||
DB.queryNamed
|
DB.queryNamed
|
||||||
db
|
db
|
||||||
( [sql|
|
( baseQuery
|
||||||
SELECT g.group_id, g.chat_ts as ts, NULL, NULL, NULL
|
<> [sql|
|
||||||
FROM groups g
|
WHERE g.user_id = :user_id
|
||||||
WHERE g.user_id = :user_id
|
|]
|
||||||
|]
|
|
||||||
<> pagQuery
|
<> pagQuery
|
||||||
)
|
)
|
||||||
([":user_id" := userId] <> pagParams)
|
([":user_id" := userId, ":rcv_new" := CISRcvNew] <> pagParams)
|
||||||
CLQFilters {favorite = True, unread = False} ->
|
CLQFilters {favorite = True, unread = False} ->
|
||||||
DB.queryNamed
|
DB.queryNamed
|
||||||
db
|
db
|
||||||
( [sql|
|
( baseQuery
|
||||||
SELECT g.group_id, g.chat_ts as ts, NULL, NULL, NULL
|
<> [sql|
|
||||||
FROM groups g
|
WHERE g.user_id = :user_id
|
||||||
WHERE g.user_id = :user_id
|
AND g.favorite = 1
|
||||||
AND g.favorite = 1
|
|]
|
||||||
|]
|
|
||||||
<> pagQuery
|
<> pagQuery
|
||||||
)
|
)
|
||||||
([":user_id" := userId] <> pagParams)
|
([":user_id" := userId, ":rcv_new" := CISRcvNew] <> pagParams)
|
||||||
CLQFilters {favorite = False, unread = True} ->
|
CLQFilters {favorite = False, unread = True} ->
|
||||||
DB.queryNamed
|
DB.queryNamed
|
||||||
db
|
db
|
||||||
( [sql|
|
( baseQuery
|
||||||
SELECT g.group_id, g.chat_ts as ts, COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat
|
<> [sql|
|
||||||
FROM groups g
|
WHERE g.user_id = :user_id
|
||||||
LEFT JOIN (
|
AND (g.unread_chat = 1 OR ChatStats.UnreadCount > 0)
|
||||||
SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread
|
|]
|
||||||
FROM chat_items
|
|
||||||
WHERE item_status = :rcv_new
|
|
||||||
GROUP BY group_id
|
|
||||||
) ChatStats ON ChatStats.group_id = g.group_id
|
|
||||||
WHERE g.user_id = :user_id
|
|
||||||
AND (g.unread_chat = 1 OR ChatStats.UnreadCount > 0)
|
|
||||||
|]
|
|
||||||
<> pagQuery
|
<> pagQuery
|
||||||
)
|
)
|
||||||
([":user_id" := userId, ":rcv_new" := CISRcvNew] <> pagParams)
|
([":user_id" := userId, ":rcv_new" := CISRcvNew] <> pagParams)
|
||||||
CLQFilters {favorite = True, unread = True} ->
|
CLQFilters {favorite = True, unread = True} ->
|
||||||
DB.queryNamed
|
DB.queryNamed
|
||||||
db
|
db
|
||||||
( [sql|
|
( baseQuery
|
||||||
SELECT g.group_id, g.chat_ts as ts, COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat
|
<> [sql|
|
||||||
FROM groups g
|
WHERE g.user_id = :user_id
|
||||||
LEFT JOIN (
|
AND (g.favorite = 1
|
||||||
SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread
|
OR g.unread_chat = 1 OR ChatStats.UnreadCount > 0)
|
||||||
FROM chat_items
|
|]
|
||||||
WHERE item_status = :rcv_new
|
|
||||||
GROUP BY group_id
|
|
||||||
) ChatStats ON ChatStats.group_id = g.group_id
|
|
||||||
WHERE g.user_id = :user_id
|
|
||||||
AND (g.favorite = 1
|
|
||||||
OR g.unread_chat = 1 OR ChatStats.UnreadCount > 0)
|
|
||||||
|]
|
|
||||||
<> pagQuery
|
<> pagQuery
|
||||||
)
|
)
|
||||||
([":user_id" := userId, ":rcv_new" := CISRcvNew] <> pagParams)
|
([":user_id" := userId, ":rcv_new" := CISRcvNew] <> pagParams)
|
||||||
CLQSearch {search} ->
|
CLQSearch {search} ->
|
||||||
DB.queryNamed
|
DB.queryNamed
|
||||||
db
|
db
|
||||||
( [sql|
|
( baseQuery
|
||||||
SELECT g.group_id, g.chat_ts as ts, NULL, NULL, NULL
|
<> [sql|
|
||||||
FROM groups g
|
JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id
|
||||||
JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id
|
WHERE g.user_id = :user_id
|
||||||
WHERE g.user_id = :user_id
|
AND (
|
||||||
AND (
|
g.local_display_name LIKE '%' || :search || '%'
|
||||||
g.local_display_name LIKE '%' || :search || '%'
|
OR gp.display_name LIKE '%' || :search || '%'
|
||||||
OR gp.display_name LIKE '%' || :search || '%'
|
OR gp.full_name LIKE '%' || :search || '%'
|
||||||
OR gp.full_name LIKE '%' || :search || '%'
|
OR gp.description LIKE '%' || :search || '%'
|
||||||
OR gp.description LIKE '%' || :search || '%'
|
)
|
||||||
)
|
|]
|
||||||
|]
|
|
||||||
<> pagQuery
|
<> pagQuery
|
||||||
)
|
)
|
||||||
([":user_id" := userId, ":search" := search] <> pagParams)
|
([":user_id" := userId, ":rcv_new" := CISRcvNew, ":search" := search] <> pagParams)
|
||||||
|
|
||||||
getGroupChatPreview_ :: DB.Connection -> User -> ChatPreviewData 'CTGroup -> ExceptT StoreError IO AChat
|
getGroupChatPreview_ :: DB.Connection -> User -> ChatPreviewData 'CTGroup -> ExceptT StoreError IO AChat
|
||||||
getGroupChatPreview_ db user (GroupChatPD _ groupId stats_) = do
|
getGroupChatPreview_ db user (GroupChatPD _ groupId lastItemId_ stats) = do
|
||||||
groupInfo <- getGroupInfo db user groupId
|
groupInfo <- getGroupInfo db user groupId
|
||||||
lastItem <- getLastItem
|
lastItem <- case lastItemId_ of
|
||||||
stats <- maybe getChatStats pure stats_
|
Just lastItemId -> (: []) <$> getGroupChatItem db user groupId lastItemId
|
||||||
|
Nothing -> pure []
|
||||||
pure $ AChat SCTGroup (Chat (GroupChat groupInfo) lastItem stats)
|
pure $ AChat SCTGroup (Chat (GroupChat groupInfo) lastItem stats)
|
||||||
where
|
|
||||||
getLastItem :: ExceptT StoreError IO [CChatItem 'CTGroup]
|
|
||||||
getLastItem =
|
|
||||||
liftIO getLastItemId >>= \case
|
|
||||||
Nothing -> pure []
|
|
||||||
Just lastItemId -> (: []) <$> getGroupChatItem db user groupId lastItemId
|
|
||||||
getLastItemId :: IO (Maybe ChatItemId)
|
|
||||||
getLastItemId =
|
|
||||||
maybeFirstRow fromOnly $
|
|
||||||
DB.query
|
|
||||||
db
|
|
||||||
[sql|
|
|
||||||
SELECT chat_item_id FROM (
|
|
||||||
SELECT group_id, chat_item_id, MAX(item_ts)
|
|
||||||
FROM chat_items
|
|
||||||
WHERE group_id = ?
|
|
||||||
GROUP BY group_id
|
|
||||||
)
|
|
||||||
|]
|
|
||||||
(Only groupId)
|
|
||||||
getChatStats :: ExceptT StoreError IO ChatStats
|
|
||||||
getChatStats = do
|
|
||||||
r_ <- liftIO getUnreadStats
|
|
||||||
let (unreadCount, minUnreadItemId) = maybe (0, 0) (\(_, unreadCnt, minId) -> (unreadCnt, minId)) r_
|
|
||||||
-- unread_chat could be read into group to not search twice
|
|
||||||
unreadChat <-
|
|
||||||
ExceptT . firstRow fromOnly (SEInternalError $ "unread_chat not found for group " <> show groupId) $
|
|
||||||
DB.query db "SELECT unread_chat FROM groups WHERE group_id = ?" (Only groupId)
|
|
||||||
pure ChatStats {unreadCount, minUnreadItemId, unreadChat}
|
|
||||||
getUnreadStats :: IO (Maybe (GroupId, Int, ChatItemId))
|
|
||||||
getUnreadStats =
|
|
||||||
maybeFirstRow id $
|
|
||||||
DB.query
|
|
||||||
db
|
|
||||||
[sql|
|
|
||||||
SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread
|
|
||||||
FROM chat_items
|
|
||||||
WHERE group_id = ? AND item_status = ?
|
|
||||||
GROUP BY group_id
|
|
||||||
|]
|
|
||||||
(groupId, CISRcvNew)
|
|
||||||
|
|
||||||
getContactRequestChatPreviews_ :: DB.Connection -> User -> PaginationByTime -> ChatListQuery -> IO [AChatPreviewData]
|
getContactRequestChatPreviews_ :: DB.Connection -> User -> PaginationByTime -> ChatListQuery -> IO [AChatPreviewData]
|
||||||
getContactRequestChatPreviews_ db User {userId} pagination clq = case clq of
|
getContactRequestChatPreviews_ db User {userId} pagination clq = case clq of
|
||||||
|
Loading…
Reference in New Issue
Block a user