Compare commits

...

38 Commits

Author SHA1 Message Date
Evgeny Poberezkin 6adf8f29b0 6.0.4: ios 236, android 237, desktop 65 2024-08-30 19:59:40 +01:00
Stanislav Dmitrenko 4ca1b57e1b android, desktop: small enhancements to new chat sheet (#4803)
* android, desktop: small enhancements to new chat sheet

* padding

* normal view matching stable

* fix one hand layout

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
Co-authored-by: Diogo <diogofncunha@gmail.com>
2024-08-30 18:34:53 +01:00
Evgeny Poberezkin 9432a5e5cd ios: update core library 2024-08-30 17:09:49 +01:00
Evgeny Poberezkin a9ec1f9ec1 core: 6.0.4.0 (simplexmq 6.0.3.0) 2024-08-30 13:39:35 +01:00
Stanislav Dmitrenko 122387d180 android, desktop: fix loading chat items when search was not empty (#4802) 2024-08-30 11:11:26 +01:00
Stanislav Dmitrenko 6edea46dad android, desktop: improvement to a lock UI (#4769)
* android, desktop: improvement to a lock UI

* oneTime passcode screen which allows to pass verification while in call

* change

* unused line

* don't ask to set up auth if already has

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-08-29 13:15:11 +01:00
Diogo 2fe3acf4df fix android simulator build (#4795) 2024-08-29 12:01:29 +01:00
Stanislav Dmitrenko dfe16991d0 ios: make CallKit calls fire in time after cold start (#4787)
* ios: make CallKit calls fire in time after cold start

* longer wait period

* uncomment

* change

* change

* removed commented code

* ios: update core library

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-08-28 15:49:11 +01:00
Stanislav Dmitrenko acb372a4ce core: call uuid (#4777)
* core: call uuid

* fix

* text

* android, desktop

* ios

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-08-28 15:31:02 +01:00
sh 121eaf6073 flatpak: update metainfo (#4784)
* flatpak: update metainfo

* flatpak: change release link and ol to ul
2024-08-28 10:39:28 +01:00
Stanislav Dmitrenko 76cb9013f5 desktop: show only AppImage download option for those who running AppImage (#4774) 2024-08-27 11:21:00 +01:00
Stanislav Dmitrenko f1e8c65aa1 android, desktop: using SemVer when checking for updates (#4768)
* android, desktop: using SemVer when checking for updates

* simplify

* simplify

* no comment

* simplify

* change

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-08-26 21:06:21 +01:00
Stanislav Dmitrenko 0118e64ab4 android, desktop: items padding and min height (#4767) 2024-08-26 16:59:57 +01:00
Evgeny 4552860345 ios: remove unnecessary protocols (#4763) 2024-08-25 14:33:06 +01:00
Evgeny 4d18174b11 ui: fix Debug delivery (#4757) 2024-08-24 19:10:30 +01:00
Evgeny Poberezkin defd095a4f 6.0.3: ios 235, android 235, desktop 64 2024-08-24 16:32:32 +01:00
Evgeny Poberezkin ed60f28e56 ios: update core library 2024-08-24 15:44:08 +01:00
Evgeny Poberezkin f0b889ffcf core: 6.0.3.0 (simplexmq 6.0.2.0) 2024-08-24 15:07:32 +01:00
Evgeny Poberezkin a95415fa1a 6.0.2: ios 234, android 234, desktop 63 2024-08-22 12:22:28 +01:00
Evgeny 8d48c4b14c ui: translations (#4740)
* Translated using Weblate (Italian)

Currently translated at 100.0% (2032 of 2032 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/

* Translated using Weblate (Italian)

Currently translated at 100.0% (1785 of 1785 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 64.6% (1314 of 2032 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hant/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 62.8% (1121 of 1785 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hant/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (2032 of 2032 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (2032 of 2032 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1785 of 1785 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/

* Translated using Weblate (Vietnamese)

Currently translated at 33.5% (682 of 2032 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 81.6% (1659 of 2032 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hant/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 81.6% (1659 of 2032 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hant/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 62.8% (1121 of 1785 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hant/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (2032 of 2032 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1785 of 1785 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 49.6% (887 of 1785 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/pt_BR/

* Translated using Weblate (Polish)

Currently translated at 99.9% (2031 of 2032 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/

* Translated using Weblate (Polish)

Currently translated at 100.0% (1785 of 1785 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/pl/

* Translated using Weblate (Indonesian)

Currently translated at 5.3% (108 of 2032 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/

* Translated using Weblate (Vietnamese)

Currently translated at 34.2% (696 of 2032 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 85.4% (1736 of 2032 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hant/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (1785 of 1785 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/uk/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 86.8% (1764 of 2032 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hant/

* Translated using Weblate (Czech)

Currently translated at 88.6% (1801 of 2032 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/

* Translated using Weblate (Indonesian)

Currently translated at 5.8% (118 of 2032 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/

* Translated using Weblate (Vietnamese)

Currently translated at 34.4% (701 of 2032 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/

* Translated using Weblate (Vietnamese)

Currently translated at 34.9% (710 of 2032 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (2032 of 2032 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1785 of 1785 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/

* Translated using Weblate (Arabic)

Currently translated at 100.0% (2032 of 2032 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/

* Translated using Weblate (Vietnamese)

Currently translated at 35.6% (725 of 2032 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (2032 of 2032 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1785 of 1785 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (2032 of 2032 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1785 of 1785 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (2032 of 2032 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1785 of 1785 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* process localizations

* ru

* ru export

* hu

---------

Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: kumo <cloud_029@icloud.com>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: M1K4 <oomikaoo@gmail.com>
Co-authored-by: tuananh-ng <158744840+tuananh-ng@users.noreply.github.com>
Co-authored-by: Jiaa <jorden2895@gmail.com>
Co-authored-by: axmfs <axmfs@proton.me>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: billy appetie <billy_appetie@users.noreply.hosted.weblate.org>
Co-authored-by: Max <Prototypem95@users.noreply.hosted.weblate.org>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: summoner001 <summoner@vivaldi.net>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
2024-08-22 11:40:54 +01:00
Stanislav Dmitrenko 9f44242e4c ios: fix applying theme when no global theme edited yet (#4738)
* ios: fix applying theme when no global theme editer yet

* base color

* added check for base

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-08-22 08:34:17 +01:00
Evgeny Poberezkin 7e7d93c596 ios: update library 2024-08-21 22:43:24 +01:00
Stanislav Dmitrenko 073818db55 android, desktop: fix applying theme when no global theme edited yet (#4735) 2024-08-21 22:05:50 +01:00
Stanislav Dmitrenko 519dd9e219 android, desktop: fix onboarding layout (#4734) 2024-08-21 22:03:49 +01:00
Evgeny Poberezkin 94218a1a7e core: 6.0.2.0 (simplexmq 6.0.1.0) 2024-08-21 19:20:20 +01:00
Arturs Krumins 996c6efddd ios: prevent hangs when opening app from background with async api calls (#4730)
* ios: async api calls on entering foreground

* rename

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-08-21 19:16:57 +01:00
Arturs Krumins fd9c080103 ios: fix last message being hidden on load (#4733) 2024-08-21 17:04:12 +01:00
Stanislav Dmitrenko 5cb8badb22 android, desktop: layout changes for settings items (#4732)
* android, desktop: layout changes for settings items

* section paddings

* toggle

* padding and border

* padding

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-08-21 16:46:42 +01:00
Arturs Krumins 0438f35539 ios: fix notifying the recipient when hanging up a call in ios15 (#4731)
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-08-21 15:05:59 +01:00
Diogo d5eb7b7811 core: api to change user of pending connections (#4681)
* core: add api that enables change of owner user id for pending connections

* old user sends request, incognito handling and coverage

* call agent inside set connection api

* only set user id if servers match

* simplify

* reduce test noise

* return invitation when a newone is created

* add test for profile on different server

* refactor namings

* update simplexmq

* refactor

* test improvements and simplify

* remove fdescribes

* simplify and reduce vars scope

* put if back

* refactor, change error

* refactor view

* refactor

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2024-08-21 13:27:58 +04:00
Evgeny Poberezkin e04f74738e Revert "ios: asynchronous api calls when entering foreground (#4710)"
This reverts commit b52dfee078.
2024-08-20 18:24:53 +01:00
Stanislav Dmitrenko 5f0ccb9f17 ios: fix interface color without callKit (#4727)
* ios: fix interface color without callKit

* call area

* better hiding sheet when making a call without CallKit

* invert condition
2024-08-20 17:04:00 +01:00
Arturs Krumins b2d18f6960 ios: fix visual artifacts when opening a chat on iOS15 (#4726)
* ios: fix visual artifacts when opening a chat on iOS15

* fix separators

* cleanup
2024-08-20 13:54:52 +01:00
Arturs Krumins b52dfee078 ios: asynchronous api calls when entering foreground (#4710)
* ios: get call invitations asynchronously

* async update chats

* async user list on appear

* move model changes to main thread

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-08-20 11:28:06 +01:00
Evgeny 70991debfd ios: optimize performance of updating chats (#4723)
* ios: optimize performance of updating chats

* even simpler

* always use updateChats

* make chats readonly from outside of model
2024-08-20 09:29:52 +01:00
Stanislav Dmitrenko 885aa9cfa5 android, desktop: scrolling moves title to app bar (#4703)
* android, desktop: scrolling moves title to app bar

* one place should be without padding

* scroll related changes for both platforms

* adapt code to universal ColumnWithScrollBar

* show in center

* small adjustments

* new chat sheet fix

* divider + mix background color for desktop

* coerce

* different transition

* desktop title starts from left

* host starts from left too

* different coefficient

* settings title
2024-08-19 19:43:54 +01:00
Evgeny 75a468434c core: only start message delivery workers when there are pending messages (#4713)
* core: use threads instead of async (reduce memory)

* simplexmq

* core: only start message delivery workers when there are pending messages (#4714)

* core: only start message delivery workers when there are pending messages

* update tls

* simplexmq

* update ios, simplexmq

* simplexmq
2024-08-18 23:00:34 +01:00
Arturs Krumins 3b98032371 ios: filter agent subscriptions from chat console (#4708) 2024-08-17 15:52:35 +01:00
357 changed files with 5048 additions and 1601 deletions
+4 -4
View File
@@ -36,7 +36,7 @@ struct ContentView: View {
@State private var waitingForOrPassedAuth = true @State private var waitingForOrPassedAuth = true
@State private var chatListActionSheet: ChatListActionSheet? = nil @State private var chatListActionSheet: ChatListActionSheet? = nil
private let callTopPadding: CGFloat = 50 private let callTopPadding: CGFloat = 40
private enum ChatListActionSheet: Identifiable { private enum ChatListActionSheet: Identifiable {
case planAndConnectSheet(sheet: PlanAndConnectActionSheet) case planAndConnectSheet(sheet: PlanAndConnectActionSheet)
@@ -151,12 +151,12 @@ struct ContentView: View {
} }
} }
.onAppear { .onAppear {
reactOnDarkThemeChanges() reactOnDarkThemeChanges(systemInDarkThemeCurrently)
} }
.onChange(of: colorScheme) { scheme in .onChange(of: colorScheme) { scheme in
// It's needed to update UI colors when iOS wants to make screenshot after going to background, // It's needed to update UI colors when iOS wants to make screenshot after going to background,
// so when a user changes his global theme from dark to light or back, the app will adapt to it // so when a user changes his global theme from dark to light or back, the app will adapt to it
reactOnDarkThemeChanges() reactOnDarkThemeChanges(scheme == .dark)
} }
.onChange(of: theme.name) { _ in .onChange(of: theme.name) { _ in
ThemeManager.adjustWindowStyle() ThemeManager.adjustWindowStyle()
@@ -207,7 +207,7 @@ struct ContentView: View {
CallDuration(call: call) CallDuration(call: call)
} }
.padding(.horizontal) .padding(.horizontal)
.frame(height: callTopPadding - 10) .frame(height: callTopPadding)
.background(Color(uiColor: UIColor(red: 47/255, green: 208/255, blue: 88/255, alpha: 1))) .background(Color(uiColor: UIColor(red: 47/255, green: 208/255, blue: 88/255, alpha: 1)))
.onTapGesture { .onTapGesture {
chatModel.activeCallViewIsCollapsed = false chatModel.activeCallViewIsCollapsed = false
+3 -20
View File
@@ -143,7 +143,7 @@ final class ChatModel: ObservableObject {
@Published var contentViewAccessAuthenticated: Bool = false @Published var contentViewAccessAuthenticated: Bool = false
@Published var laRequest: LocalAuthRequest? @Published var laRequest: LocalAuthRequest?
// list of chat "previews" // list of chat "previews"
@Published var chats: [Chat] = [] @Published private(set) var chats: [Chat] = []
@Published var deletedChats: Set<String> = [] @Published var deletedChats: Set<String> = []
// current chat // current chat
@Published var chatId: String? @Published var chatId: String?
@@ -357,25 +357,8 @@ final class ChatModel: ObservableObject {
} }
} }
func updateChats(with newChats: [ChatData]) { func updateChats(_ newChats: [ChatData]) {
for i in 0..<newChats.count { chats = newChats.map { Chat($0) }
let c = newChats[i]
if let j = getChatIndex(c.id) {
let chat = chats[j]
chat.chatInfo = c.chatInfo
chat.chatItems = c.chatItems
chat.chatStats = c.chatStats
if i != j {
if chatId != c.chatInfo.id {
popChat_(j, to: i)
} else if i == 0 {
chatToTop = c.chatInfo.id
}
}
} else {
addChat_(Chat(c), at: i)
}
}
NtfManager.shared.setNtfBadgeCount(totalUnreadCountForAllUsers()) NtfManager.shared.setNtfBadgeCount(totalUnreadCountForAllUsers())
popChatCollector.clear() popChatCollector.clear()
} }
+41 -28
View File
@@ -112,9 +112,9 @@ func chatSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? =
return resp return resp
} }
func chatSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, _ ctrl: chat_ctrl? = nil) async -> ChatResponse { func chatSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, _ ctrl: chat_ctrl? = nil, log: Bool = true) async -> ChatResponse {
await withCheckedContinuation { cont in await withCheckedContinuation { cont in
cont.resume(returning: chatSendCmdSync(cmd, bgTask: bgTask, bgDelay: bgDelay, ctrl)) cont.resume(returning: chatSendCmdSync(cmd, bgTask: bgTask, bgDelay: bgDelay, ctrl, log: log))
} }
} }
@@ -582,13 +582,13 @@ func apiGroupMemberInfo(_ groupId: Int64, _ groupMemberId: Int64) throws -> (Gro
throw r throw r
} }
func apiContactQueueInfo(_ contactId: Int64) async throws -> (RcvMsgInfo?, QueueInfo) { func apiContactQueueInfo(_ contactId: Int64) async throws -> (RcvMsgInfo?, ServerQueueInfo) {
let r = await chatSendCmd(.apiContactQueueInfo(contactId: contactId)) let r = await chatSendCmd(.apiContactQueueInfo(contactId: contactId))
if case let .queueInfo(_, rcvMsgInfo, queueInfo) = r { return (rcvMsgInfo, queueInfo) } if case let .queueInfo(_, rcvMsgInfo, queueInfo) = r { return (rcvMsgInfo, queueInfo) }
throw r throw r
} }
func apiGroupMemberQueueInfo(_ groupId: Int64, _ groupMemberId: Int64) async throws -> (RcvMsgInfo?, QueueInfo) { func apiGroupMemberQueueInfo(_ groupId: Int64, _ groupMemberId: Int64) async throws -> (RcvMsgInfo?, ServerQueueInfo) {
let r = await chatSendCmd(.apiGroupMemberQueueInfo(groupId: groupId, groupMemberId: groupMemberId)) let r = await chatSendCmd(.apiGroupMemberQueueInfo(groupId: groupId, groupMemberId: groupMemberId))
if case let .queueInfo(_, rcvMsgInfo, queueInfo) = r { return (rcvMsgInfo, queueInfo) } if case let .queueInfo(_, rcvMsgInfo, queueInfo) = r { return (rcvMsgInfo, queueInfo) }
throw r throw r
@@ -1218,12 +1218,18 @@ func apiEndCall(_ contact: Contact) async throws {
try await sendCommandOkResp(.apiEndCall(contact: contact)) try await sendCommandOkResp(.apiEndCall(contact: contact))
} }
func apiGetCallInvitations() throws -> [RcvCallInvitation] { func apiGetCallInvitationsSync() throws -> [RcvCallInvitation] {
let r = chatSendCmdSync(.apiGetCallInvitations) let r = chatSendCmdSync(.apiGetCallInvitations)
if case let .callInvitations(invs) = r { return invs } if case let .callInvitations(invs) = r { return invs }
throw r throw r
} }
func apiGetCallInvitations() async throws -> [RcvCallInvitation] {
let r = await chatSendCmd(.apiGetCallInvitations)
if case let .callInvitations(invs) = r { return invs }
throw r
}
func apiCallStatus(_ contact: Contact, _ status: String) async throws { func apiCallStatus(_ contact: Contact, _ status: String) async throws {
if let callStatus = WebRTCCallStatus.init(rawValue: status) { if let callStatus = WebRTCCallStatus.init(rawValue: status) {
try await sendCommandOkResp(.apiCallStatus(contact: contact, callStatus: callStatus)) try await sendCommandOkResp(.apiCallStatus(contact: contact, callStatus: callStatus))
@@ -1422,7 +1428,7 @@ func apiGetVersion() throws -> CoreVersionInfo {
func getAgentSubsTotal() async throws -> (SMPServerSubs, Bool) { func getAgentSubsTotal() async throws -> (SMPServerSubs, Bool) {
let userId = try currentUserId("getAgentSubsTotal") let userId = try currentUserId("getAgentSubsTotal")
let r = await chatSendCmd(.getAgentSubsTotal(userId: userId)) let r = await chatSendCmd(.getAgentSubsTotal(userId: userId), log: false)
if case let .agentSubsTotal(_, subsTotal, hasSession) = r { return (subsTotal, hasSession) } if case let .agentSubsTotal(_, subsTotal, hasSession) = r { return (subsTotal, hasSession) }
logger.error("getAgentSubsTotal error: \(String(describing: r))") logger.error("getAgentSubsTotal error: \(String(describing: r))")
throw r throw r
@@ -1517,7 +1523,7 @@ func startChat(refreshInvitations: Bool = true) throws {
try getUserChatData() try getUserChatData()
NtfManager.shared.setNtfBadgeCount(m.totalUnreadCountForAllUsers()) NtfManager.shared.setNtfBadgeCount(m.totalUnreadCountForAllUsers())
if (refreshInvitations) { if (refreshInvitations) {
try refreshCallInvitations() Task { try await refreshCallInvitations() }
} }
(m.savedToken, m.tokenStatus, m.notificationMode, m.notificationServer) = apiGetNtfToken() (m.savedToken, m.tokenStatus, m.notificationMode, m.notificationServer) = apiGetNtfToken()
_ = try apiStartChat() _ = try apiStartChat()
@@ -1591,8 +1597,7 @@ func getUserChatData() throws {
m.userAddress = try apiGetUserAddress() m.userAddress = try apiGetUserAddress()
m.chatItemTTL = try getChatItemTTL() m.chatItemTTL = try getChatItemTTL()
let chats = try apiGetChats() let chats = try apiGetChats()
m.chats = chats.map { Chat.init($0) } m.updateChats(chats)
m.popChatCollector.clear()
} }
private func getUserChatDataAsync() async throws { private func getUserChatDataAsync() async throws {
@@ -1604,14 +1609,12 @@ private func getUserChatDataAsync() async throws {
await MainActor.run { await MainActor.run {
m.userAddress = userAddress m.userAddress = userAddress
m.chatItemTTL = chatItemTTL m.chatItemTTL = chatItemTTL
m.chats = chats.map { Chat.init($0) } m.updateChats(chats)
m.popChatCollector.clear()
} }
} else { } else {
await MainActor.run { await MainActor.run {
m.userAddress = nil m.userAddress = nil
m.chats = [] m.updateChats([])
m.popChatCollector.clear()
} }
} }
} }
@@ -2164,32 +2167,42 @@ func chatItemSimpleUpdate(_ user: any UserLike, _ aChatItem: AChatItem) async {
} }
} }
func refreshCallInvitations() throws { func refreshCallInvitations() async throws {
let m = ChatModel.shared let m = ChatModel.shared
let callInvitations = try justRefreshCallInvitations() let callInvitations = try await apiGetCallInvitations()
if let (chatId, ntfAction) = m.ntfCallInvitationAction, await MainActor.run {
let invitation = m.callInvitations.removeValue(forKey: chatId) { m.callInvitations = callsByChat(callInvitations)
m.ntfCallInvitationAction = nil if let (chatId, ntfAction) = m.ntfCallInvitationAction,
CallController.shared.callAction(invitation: invitation, action: ntfAction) let invitation = m.callInvitations.removeValue(forKey: chatId) {
} else if let invitation = callInvitations.last(where: { $0.user.showNotifications }) { m.ntfCallInvitationAction = nil
activateCall(invitation) CallController.shared.callAction(invitation: invitation, action: ntfAction)
} else if let invitation = callInvitations.last(where: { $0.user.showNotifications }) {
activateCall(invitation)
}
} }
} }
func justRefreshCallInvitations() throws -> [RcvCallInvitation] { func justRefreshCallInvitations() async throws {
let m = ChatModel.shared let callInvitations = try apiGetCallInvitationsSync()
let callInvitations = try apiGetCallInvitations() await MainActor.run {
m.callInvitations = callInvitations.reduce(into: [ChatId: RcvCallInvitation]()) { result, inv in result[inv.contact.id] = inv } ChatModel.shared.callInvitations = callsByChat(callInvitations)
return callInvitations }
}
private func callsByChat(_ callInvitations: [RcvCallInvitation]) -> [ChatId: RcvCallInvitation] {
callInvitations.reduce(into: [ChatId: RcvCallInvitation]()) {
result, inv in result[inv.contact.id] = inv
}
} }
func activateCall(_ callInvitation: RcvCallInvitation) { func activateCall(_ callInvitation: RcvCallInvitation) {
if !callInvitation.user.showNotifications { return }
let m = ChatModel.shared let m = ChatModel.shared
logger.debug("reportNewIncomingCall activeCallUUID \(String(describing: m.activeCall?.callUUID)) invitationUUID \(String(describing: callInvitation.callUUID))")
if !callInvitation.user.showNotifications || m.activeCall?.callUUID == callInvitation.callUUID { return }
CallController.shared.reportNewIncomingCall(invitation: callInvitation) { error in CallController.shared.reportNewIncomingCall(invitation: callInvitation) { error in
if let error = error { if let error = error {
DispatchQueue.main.async { DispatchQueue.main.async {
m.callInvitations[callInvitation.contact.id]?.callkitUUID = nil m.callInvitations[callInvitation.contact.id]?.callUUID = nil
} }
logger.error("reportNewIncomingCall error: \(error.localizedDescription)") logger.error("reportNewIncomingCall error: \(error.localizedDescription)")
} else { } else {
+11 -9
View File
@@ -83,9 +83,11 @@ struct SimpleXApp: App {
if appState != .stopped { if appState != .stopped {
startChatAndActivate { startChatAndActivate {
if appState.inactive && chatModel.chatRunning == true { if appState.inactive && chatModel.chatRunning == true {
updateChats() Task {
if !chatModel.showCallView && !CallController.shared.hasActiveCalls() { await updateChats()
updateCallInvitations() if !chatModel.showCallView && !CallController.shared.hasActiveCalls() {
await updateCallInvitations()
}
} }
} }
} }
@@ -130,16 +132,16 @@ struct SimpleXApp: App {
} }
} }
private func updateChats() { private func updateChats() async {
do { do {
let chats = try apiGetChats() let chats = try await apiGetChatsAsync()
chatModel.updateChats(with: chats) await MainActor.run { chatModel.updateChats(chats) }
if let id = chatModel.chatId, if let id = chatModel.chatId,
let chat = chatModel.getChat(id) { let chat = chatModel.getChat(id) {
Task { await loadChat(chat: chat, clearItems: false) } Task { await loadChat(chat: chat, clearItems: false) }
} }
if let ncr = chatModel.ntfContactRequest { if let ncr = chatModel.ntfContactRequest {
chatModel.ntfContactRequest = nil await MainActor.run { chatModel.ntfContactRequest = nil }
if case let .contactRequest(contactRequest) = chatModel.getChat(ncr.chatId)?.chatInfo { if case let .contactRequest(contactRequest) = chatModel.getChat(ncr.chatId)?.chatInfo {
Task { await acceptContactRequest(incognito: ncr.incognito, contactRequest: contactRequest) } Task { await acceptContactRequest(incognito: ncr.incognito, contactRequest: contactRequest) }
} }
@@ -149,9 +151,9 @@ struct SimpleXApp: App {
} }
} }
private func updateCallInvitations() { private func updateCallInvitations() async {
do { do {
try refreshCallInvitations() try await refreshCallInvitations()
} catch let error { } catch let error {
logger.error("apiGetCallInvitations: cannot update call invitations \(responseError(error))") logger.error("apiGetCallInvitations: cannot update call invitations \(responseError(error))")
} }
+2 -2
View File
@@ -91,8 +91,8 @@ var systemInDarkThemeCurrently: Bool {
return UITraitCollection.current.userInterfaceStyle == .dark return UITraitCollection.current.userInterfaceStyle == .dark
} }
func reactOnDarkThemeChanges() { func reactOnDarkThemeChanges(_ inDarkNow: Bool) {
if currentThemeDefault.get() == DefaultTheme.SYSTEM_THEME_NAME && CurrentColors.colors.isLight == systemInDarkThemeCurrently { if currentThemeDefault.get() == DefaultTheme.SYSTEM_THEME_NAME && CurrentColors.colors.isLight == inDarkNow {
// Change active colors from light to dark and back based on system theme // Change active colors from light to dark and back based on system theme
ThemeManager.applyTheme(DefaultTheme.SYSTEM_THEME_NAME) ThemeManager.applyTheme(DefaultTheme.SYSTEM_THEME_NAME)
} }
+1 -1
View File
@@ -53,7 +53,7 @@ class ThemeManager {
return perUserTheme return perUserTheme
} }
let defaultTheme = defaultActiveTheme(appSettingsTheme) let defaultTheme = defaultActiveTheme(appSettingsTheme)
return ThemeModeOverride(mode: CurrentColors.base.mode, colors: defaultTheme?.colors ?? ThemeColors(), wallpaper: defaultTheme?.wallpaper) return ThemeModeOverride(mode: CurrentColors.base.mode, colors: defaultTheme?.colors ?? ThemeColors(), wallpaper: defaultTheme?.wallpaper ?? ThemeWallpaper.from(PresetWallpaper.school.toType(CurrentColors.base), nil, nil))
} }
static func currentColors(_ themeOverridesForType: WallpaperType?, _ perChatTheme: ThemeModeOverride?, _ perUserTheme: ThemeModeOverrides?, _ appSettingsTheme: [ThemeOverrides]) -> ActiveTheme { static func currentColors(_ themeOverridesForType: WallpaperType?, _ perChatTheme: ThemeModeOverride?, _ perUserTheme: ThemeModeOverrides?, _ appSettingsTheme: [ThemeOverrides]) -> ActiveTheme {
@@ -185,7 +185,7 @@ struct ActiveCallView: View {
case .ended: case .ended:
closeCallView(client) closeCallView(client)
call.callState = .ended call.callState = .ended
if let uuid = call.callkitUUID { if let uuid = call.callUUID {
CallController.shared.endCall(callUUID: uuid) CallController.shared.endCall(callUUID: uuid)
} }
case .ok: case .ok:
@@ -382,7 +382,7 @@ struct ActiveCallOverlay: View {
private func endCallButton() -> some View { private func endCallButton() -> some View {
let cc = CallController.shared let cc = CallController.shared
return callButton("phone.down.fill", width: 60, height: 60) { return callButton("phone.down.fill", width: 60, height: 60) {
if let uuid = call.callkitUUID { if let uuid = call.callUUID {
cc.endCall(callUUID: uuid) cc.endCall(callUUID: uuid)
} else { } else {
cc.endCall(call: call) {} cc.endCall(call: call) {}
@@ -462,9 +462,9 @@ struct ActiveCallOverlay: View {
struct ActiveCallOverlay_Previews: PreviewProvider { struct ActiveCallOverlay_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
Group{ Group{
ActiveCallOverlay(call: Call(direction: .incoming, contact: Contact.sampleData, callkitUUID: UUID(), callState: .offerSent, localMedia: .video), client: WebRTCClient(Binding.constant(nil), { _ in }, Binding.constant(nil))) ActiveCallOverlay(call: Call(direction: .incoming, contact: Contact.sampleData, callUUID: UUID().uuidString.lowercased(), callState: .offerSent, localMedia: .video), client: WebRTCClient(Binding.constant(nil), { _ in }, Binding.constant(nil)))
.background(.black) .background(.black)
ActiveCallOverlay(call: Call(direction: .incoming, contact: Contact.sampleData, callkitUUID: UUID(), callState: .offerSent, localMedia: .audio), client: WebRTCClient(Binding.constant(nil), { _ in }, Binding.constant(nil))) ActiveCallOverlay(call: Call(direction: .incoming, contact: Contact.sampleData, callUUID: UUID().uuidString.lowercased(), callState: .offerSent, localMedia: .audio), client: WebRTCClient(Binding.constant(nil), { _ in }, Binding.constant(nil)))
.background(.black) .background(.black)
} }
} }
+96 -44
View File
@@ -51,7 +51,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
func provider(_ provider: CXProvider, perform action: CXStartCallAction) { func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
logger.debug("CallController.provider CXStartCallAction") logger.debug("CallController.provider CXStartCallAction")
if callManager.startOutgoingCall(callUUID: action.callUUID) { if callManager.startOutgoingCall(callUUID: action.callUUID.uuidString.lowercased()) {
action.fulfill() action.fulfill()
provider.reportOutgoingCall(with: action.callUUID, startedConnectingAt: nil) provider.reportOutgoingCall(with: action.callUUID, startedConnectingAt: nil)
} else { } else {
@@ -61,12 +61,30 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
logger.debug("CallController.provider CXAnswerCallAction") logger.debug("CallController.provider CXAnswerCallAction")
if callManager.answerIncomingCall(callUUID: action.callUUID) { Task {
// WebRTC call should be in connected state to fulfill. let chatIsReady = await waitUntilChatStarted(timeoutMs: 30_000, stepMs: 500)
// Otherwise no audio and mic working on lockscreen logger.debug("CallController chat started \(chatIsReady) \(ChatModel.shared.chatInitialized) \(ChatModel.shared.chatRunning == true) \(String(describing: AppChatState.shared.value))")
fulfillOnConnect = action if !chatIsReady {
} else { action.fail()
action.fail() return
}
if !ChatModel.shared.callInvitations.values.contains(where: { inv in inv.callUUID == action.callUUID.uuidString.lowercased() }) {
try? await justRefreshCallInvitations()
logger.debug("CallController: updated call invitations chat")
}
await MainActor.run {
logger.debug("CallController.provider will answer on call")
if callManager.answerIncomingCall(callUUID: action.callUUID.uuidString.lowercased()) {
logger.debug("CallController.provider answered on call")
// WebRTC call should be in connected state to fulfill.
// Otherwise no audio and mic working on lockscreen
fulfillOnConnect = action
} else {
logger.debug("CallController.provider will fail the call")
action.fail()
}
}
} }
} }
@@ -75,7 +93,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
// Should be nil here if connection was in connected state // Should be nil here if connection was in connected state
fulfillOnConnect?.fail() fulfillOnConnect?.fail()
fulfillOnConnect = nil fulfillOnConnect = nil
callManager.endCall(callUUID: action.callUUID) { ok in callManager.endCall(callUUID: action.callUUID.uuidString.lowercased()) { ok in
if ok { if ok {
action.fulfill() action.fulfill()
} else { } else {
@@ -86,7 +104,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
} }
func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) { func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
if callManager.enableMedia(media: .audio, enable: !action.isMuted, callUUID: action.callUUID) { if callManager.enableMedia(media: .audio, enable: !action.isMuted, callUUID: action.callUUID.uuidString.lowercased()) {
action.fulfill() action.fulfill()
} else { } else {
action.fail() action.fail()
@@ -156,6 +174,19 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
} }
} }
private func waitUntilChatStarted(timeoutMs: UInt64, stepMs: UInt64) async -> Bool {
logger.debug("CallController waiting until chat started")
var t: UInt64 = 0
repeat {
if ChatModel.shared.chatInitialized, ChatModel.shared.chatRunning == true, case .active = AppChatState.shared.value {
return true
}
_ = try? await Task.sleep(nanoseconds: stepMs * 1000000)
t += stepMs
} while t < timeoutMs
return false
}
@objc(pushRegistry:didUpdatePushCredentials:forType:) @objc(pushRegistry:didUpdatePushCredentials:forType:)
func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) { func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
logger.debug("CallController: didUpdate push credentials for type \(type.rawValue)") logger.debug("CallController: didUpdate push credentials for type \(type.rawValue)")
@@ -171,32 +202,19 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
self.reportExpiredCall(payload: payload, completion) self.reportExpiredCall(payload: payload, completion)
return return
} }
if (!ChatModel.shared.chatInitialized) {
logger.debug("CallController: initializing chat")
do {
try initializeChat(start: true, refreshInvitations: false)
} catch let error {
logger.error("CallController: initializing chat error: \(error)")
self.reportExpiredCall(payload: payload, completion)
return
}
}
logger.debug("CallController: initialized chat")
startChatForCall()
logger.debug("CallController: started chat")
self.shouldSuspendChat = true
// There are no invitations in the model, as it was processed by NSE
_ = try? justRefreshCallInvitations()
logger.debug("CallController: updated call invitations chat")
// logger.debug("CallController justRefreshCallInvitations: \(String(describing: m.callInvitations))")
// Extract the call information from the push notification payload // Extract the call information from the push notification payload
let m = ChatModel.shared let m = ChatModel.shared
if let contactId = payload.dictionaryPayload["contactId"] as? String, if let contactId = payload.dictionaryPayload["contactId"] as? String,
let invitation = m.callInvitations[contactId] { let displayName = payload.dictionaryPayload["displayName"] as? String,
let update = self.cxCallUpdate(invitation: invitation) let callUUID = payload.dictionaryPayload["callUUID"] as? String,
if let uuid = invitation.callkitUUID { let uuid = UUID(uuidString: callUUID),
let callTsInterval = payload.dictionaryPayload["callTs"] as? TimeInterval,
let mediaStr = payload.dictionaryPayload["media"] as? String,
let media = CallMediaType(rawValue: mediaStr) {
let update = self.cxCallUpdate(contactId, displayName, media)
let callTs = Date(timeIntervalSince1970: callTsInterval)
if callTs.timeIntervalSinceNow >= -180 {
logger.debug("CallController: report pushkit call via CallKit") logger.debug("CallController: report pushkit call via CallKit")
let update = self.cxCallUpdate(invitation: invitation)
self.provider.reportNewIncomingCall(with: uuid, update: update) { error in self.provider.reportNewIncomingCall(with: uuid, update: update) { error in
if error != nil { if error != nil {
m.callInvitations.removeValue(forKey: contactId) m.callInvitations.removeValue(forKey: contactId)
@@ -205,11 +223,31 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
completion() completion()
} }
} else { } else {
logger.debug("CallController will expire call 1")
self.reportExpiredCall(update: update, completion) self.reportExpiredCall(update: update, completion)
} }
} else { } else {
logger.debug("CallController will expire call 2")
self.reportExpiredCall(payload: payload, completion) self.reportExpiredCall(payload: payload, completion)
} }
//DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
if (!ChatModel.shared.chatInitialized) {
logger.debug("CallController: initializing chat")
do {
try initializeChat(start: true, refreshInvitations: false)
} catch let error {
logger.error("CallController: initializing chat error: \(error)")
if let call = ChatModel.shared.activeCall {
self.endCall(call: call, completed: completion)
}
return
}
}
logger.debug("CallController: initialized chat")
startChatForCall()
logger.debug("CallController: started chat")
self.shouldSuspendChat = true
} }
// This function fulfils the requirement to always report a call when PushKit notification is received, // This function fulfils the requirement to always report a call when PushKit notification is received,
@@ -239,8 +277,8 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
} }
func reportNewIncomingCall(invitation: RcvCallInvitation, completion: @escaping (Error?) -> Void) { func reportNewIncomingCall(invitation: RcvCallInvitation, completion: @escaping (Error?) -> Void) {
logger.debug("CallController.reportNewIncomingCall, UUID=\(String(describing: invitation.callkitUUID))") logger.debug("CallController.reportNewIncomingCall, UUID=\(String(describing: invitation.callUUID))")
if CallController.useCallKit(), let uuid = invitation.callkitUUID { if CallController.useCallKit(), let callUUID = invitation.callUUID, let uuid = UUID(uuidString: callUUID) {
if invitation.callTs.timeIntervalSinceNow >= -180 { if invitation.callTs.timeIntervalSinceNow >= -180 {
let update = cxCallUpdate(invitation: invitation) let update = cxCallUpdate(invitation: invitation)
provider.reportNewIncomingCall(with: uuid, update: update, completion: completion) provider.reportNewIncomingCall(with: uuid, update: update, completion: completion)
@@ -261,6 +299,14 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
return update return update
} }
private func cxCallUpdate(_ contactId: String, _ displayName: String, _ media: CallMediaType) -> CXCallUpdate {
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .generic, value: contactId)
update.hasVideo = media == .video
update.localizedCallerName = displayName
return update
}
func reportIncomingCall(call: Call, connectedAt dateConnected: Date?) { func reportIncomingCall(call: Call, connectedAt dateConnected: Date?) {
logger.debug("CallController: reporting incoming call connected") logger.debug("CallController: reporting incoming call connected")
if CallController.useCallKit() { if CallController.useCallKit() {
@@ -272,14 +318,14 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
func reportOutgoingCall(call: Call, connectedAt dateConnected: Date?) { func reportOutgoingCall(call: Call, connectedAt dateConnected: Date?) {
logger.debug("CallController: reporting outgoing call connected") logger.debug("CallController: reporting outgoing call connected")
if CallController.useCallKit(), let uuid = call.callkitUUID { if CallController.useCallKit(), let callUUID = call.callUUID, let uuid = UUID(uuidString: callUUID) {
provider.reportOutgoingCall(with: uuid, connectedAt: dateConnected) provider.reportOutgoingCall(with: uuid, connectedAt: dateConnected)
} }
} }
func reportCallRemoteEnded(invitation: RcvCallInvitation) { func reportCallRemoteEnded(invitation: RcvCallInvitation) {
logger.debug("CallController: reporting remote ended") logger.debug("CallController: reporting remote ended")
if CallController.useCallKit(), let uuid = invitation.callkitUUID { if CallController.useCallKit(), let callUUID = invitation.callUUID, let uuid = UUID(uuidString: callUUID) {
provider.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded) provider.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded)
} else if invitation.contact.id == activeCallInvitation?.contact.id { } else if invitation.contact.id == activeCallInvitation?.contact.id {
activeCallInvitation = nil activeCallInvitation = nil
@@ -288,14 +334,17 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
func reportCallRemoteEnded(call: Call) { func reportCallRemoteEnded(call: Call) {
logger.debug("CallController: reporting remote ended") logger.debug("CallController: reporting remote ended")
if CallController.useCallKit(), let uuid = call.callkitUUID { if CallController.useCallKit(), let callUUID = call.callUUID, let uuid = UUID(uuidString: callUUID) {
provider.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded) provider.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded)
} }
} }
func startCall(_ contact: Contact, _ media: CallMediaType) { func startCall(_ contact: Contact, _ media: CallMediaType) {
logger.debug("CallController.startCall") logger.debug("CallController.startCall")
let uuid = callManager.newOutgoingCall(contact, media) let callUUID = callManager.newOutgoingCall(contact, media)
guard let uuid = UUID(uuidString: callUUID) else {
return
}
if CallController.useCallKit() { if CallController.useCallKit() {
let handle = CXHandle(type: .generic, value: contact.id) let handle = CXHandle(type: .generic, value: contact.id)
let action = CXStartCallAction(call: uuid, handle: handle) let action = CXStartCallAction(call: uuid, handle: handle)
@@ -307,8 +356,8 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
update.localizedCallerName = contact.displayName update.localizedCallerName = contact.displayName
self.provider.reportCall(with: uuid, updated: update) self.provider.reportCall(with: uuid, updated: update)
} }
} else if callManager.startOutgoingCall(callUUID: uuid) { } else if callManager.startOutgoingCall(callUUID: callUUID) {
if callManager.startOutgoingCall(callUUID: uuid) { if callManager.startOutgoingCall(callUUID: callUUID) {
logger.debug("CallController.startCall: call started") logger.debug("CallController.startCall: call started")
} else { } else {
logger.error("CallController.startCall: no active call") logger.error("CallController.startCall: no active call")
@@ -318,8 +367,8 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
func answerCall(invitation: RcvCallInvitation) { func answerCall(invitation: RcvCallInvitation) {
logger.debug("CallController: answering a call") logger.debug("CallController: answering a call")
if CallController.useCallKit(), let callUUID = invitation.callkitUUID { if CallController.useCallKit(), let callUUID = invitation.callUUID, let uuid = UUID(uuidString: callUUID) {
requestTransaction(with: CXAnswerCallAction(call: callUUID)) requestTransaction(with: CXAnswerCallAction(call: uuid))
} else { } else {
callManager.answerIncomingCall(invitation: invitation) callManager.answerIncomingCall(invitation: invitation)
} }
@@ -328,10 +377,13 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
} }
} }
func endCall(callUUID: UUID) { func endCall(callUUID: String) {
logger.debug("CallController: ending the call with UUID \(callUUID.uuidString)") let uuid = UUID(uuidString: callUUID)
logger.debug("CallController: ending the call with UUID \(callUUID)")
if CallController.useCallKit() { if CallController.useCallKit() {
requestTransaction(with: CXEndCallAction(call: callUUID)) if let uuid {
requestTransaction(with: CXEndCallAction(call: uuid))
}
} else { } else {
callManager.endCall(callUUID: callUUID) { ok in callManager.endCall(callUUID: callUUID) { ok in
if ok { if ok {
+13 -13
View File
@@ -10,17 +10,17 @@ import Foundation
import SimpleXChat import SimpleXChat
class CallManager { class CallManager {
func newOutgoingCall(_ contact: Contact, _ media: CallMediaType) -> UUID { func newOutgoingCall(_ contact: Contact, _ media: CallMediaType) -> String {
let uuid = UUID() let uuid = UUID().uuidString.lowercased()
let call = Call(direction: .outgoing, contact: contact, callkitUUID: uuid, callState: .waitCapabilities, localMedia: media) let call = Call(direction: .outgoing, contact: contact, callUUID: uuid, callState: .waitCapabilities, localMedia: media)
call.speakerEnabled = media == .video call.speakerEnabled = media == .video
ChatModel.shared.activeCall = call ChatModel.shared.activeCall = call
return uuid return uuid
} }
func startOutgoingCall(callUUID: UUID) -> Bool { func startOutgoingCall(callUUID: String) -> Bool {
let m = ChatModel.shared let m = ChatModel.shared
if let call = m.activeCall, call.callkitUUID == callUUID { if let call = m.activeCall, call.callUUID == callUUID {
m.showCallView = true m.showCallView = true
Task { await m.callCommand.processCommand(.capabilities(media: call.localMedia)) } Task { await m.callCommand.processCommand(.capabilities(media: call.localMedia)) }
return true return true
@@ -28,7 +28,7 @@ class CallManager {
return false return false
} }
func answerIncomingCall(callUUID: UUID) -> Bool { func answerIncomingCall(callUUID: String) -> Bool {
if let invitation = getCallInvitation(callUUID) { if let invitation = getCallInvitation(callUUID) {
answerIncomingCall(invitation: invitation) answerIncomingCall(invitation: invitation)
return true return true
@@ -42,7 +42,7 @@ class CallManager {
let call = Call( let call = Call(
direction: .incoming, direction: .incoming,
contact: invitation.contact, contact: invitation.contact,
callkitUUID: invitation.callkitUUID, callUUID: invitation.callUUID,
callState: .invitationAccepted, callState: .invitationAccepted,
localMedia: invitation.callType.media, localMedia: invitation.callType.media,
sharedKey: invitation.sharedKey sharedKey: invitation.sharedKey
@@ -68,8 +68,8 @@ class CallManager {
} }
} }
func enableMedia(media: CallMediaType, enable: Bool, callUUID: UUID) -> Bool { func enableMedia(media: CallMediaType, enable: Bool, callUUID: String) -> Bool {
if let call = ChatModel.shared.activeCall, call.callkitUUID == callUUID { if let call = ChatModel.shared.activeCall, call.callUUID == callUUID {
let m = ChatModel.shared let m = ChatModel.shared
Task { await m.callCommand.processCommand(.media(media: media, enable: enable)) } Task { await m.callCommand.processCommand(.media(media: media, enable: enable)) }
return true return true
@@ -77,8 +77,8 @@ class CallManager {
return false return false
} }
func endCall(callUUID: UUID, completed: @escaping (Bool) -> Void) { func endCall(callUUID: String, completed: @escaping (Bool) -> Void) {
if let call = ChatModel.shared.activeCall, call.callkitUUID == callUUID { if let call = ChatModel.shared.activeCall, call.callUUID == callUUID {
endCall(call: call) { completed(true) } endCall(call: call) { completed(true) }
} else if let invitation = getCallInvitation(callUUID) { } else if let invitation = getCallInvitation(callUUID) {
endCall(invitation: invitation) { completed(true) } endCall(invitation: invitation) { completed(true) }
@@ -126,8 +126,8 @@ class CallManager {
} }
} }
private func getCallInvitation(_ callUUID: UUID) -> RcvCallInvitation? { private func getCallInvitation(_ callUUID: String) -> RcvCallInvitation? {
if let (_, invitation) = ChatModel.shared.callInvitations.first(where: { (_, inv) in inv.callkitUUID == callUUID }) { if let (_, invitation) = ChatModel.shared.callInvitations.first(where: { (_, inv) in inv.callUUID == callUUID }) {
return invitation return invitation
} }
return nil return nil
+3 -3
View File
@@ -18,7 +18,7 @@ class Call: ObservableObject, Equatable {
var direction: CallDirection var direction: CallDirection
var contact: Contact var contact: Contact
var callkitUUID: UUID? var callUUID: String?
var localMedia: CallMediaType var localMedia: CallMediaType
@Published var callState: CallState @Published var callState: CallState
@Published var localCapabilities: CallCapabilities? @Published var localCapabilities: CallCapabilities?
@@ -33,14 +33,14 @@ class Call: ObservableObject, Equatable {
init( init(
direction: CallDirection, direction: CallDirection,
contact: Contact, contact: Contact,
callkitUUID: UUID?, callUUID: String?,
callState: CallState, callState: CallState,
localMedia: CallMediaType, localMedia: CallMediaType,
sharedKey: String? = nil sharedKey: String? = nil
) { ) {
self.direction = direction self.direction = direction
self.contact = contact self.contact = contact
self.callkitUUID = callkitUUID self.callUUID = callUUID
self.callState = callState self.callState = callState
self.localMedia = localMedia self.localMedia = localMedia
self.sharedKey = sharedKey self.sharedKey = sharedKey
@@ -411,6 +411,15 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
} }
func endCall() { func endCall() {
if #available(iOS 16.0, *) {
_endCall()
} else {
// Fixes `connection.close()` getting locked up in iOS15
DispatchQueue.global(qos: .utility).async { self._endCall() }
}
}
private func _endCall() {
guard let call = activeCall.wrappedValue else { return } guard let call = activeCall.wrappedValue else { return }
logger.debug("WebRTCClient: ending the call") logger.debug("WebRTCClient: ending the call")
activeCall.wrappedValue = nil activeCall.wrappedValue = nil
@@ -671,7 +671,14 @@ private struct CallButton: View {
InfoViewButton(image: image, title: title, disabledLook: !canCall, width: width) { InfoViewButton(image: image, title: title, disabledLook: !canCall, width: width) {
if canCall { if canCall {
CallController.shared.startCall(contact, mediaType) if CallController.useCallKit() {
CallController.shared.startCall(contact, mediaType)
} else {
// When CallKit is not used, colorscheme will be changed and it will be visible if not hiding sheets first
dismissAllSheets(animated: true) {
CallController.shared.startCall(contact, mediaType)
}
}
} else if contact.nextSendGrpInv { } else if contact.nextSendGrpInv {
showAlert(SomeAlert( showAlert(SomeAlert(
alert: mkAlert( alert: mkAlert(
@@ -935,7 +942,7 @@ func syncConnectionForceAlert(_ syncConnectionForce: @escaping () -> Void) -> Al
) )
} }
func queueInfoText(_ info: (RcvMsgInfo?, QueueInfo)) -> String { func queueInfoText(_ info: (RcvMsgInfo?, ServerQueueInfo)) -> String {
let (rcvMsgInfo, qInfo) = info let (rcvMsgInfo, qInfo) = info
var msgInfo: String var msgInfo: String
if let rcvMsgInfo { msgInfo = encodeJSON(rcvMsgInfo) } else { msgInfo = "none" } if let rcvMsgInfo { msgInfo = encodeJSON(rcvMsgInfo) } else { msgInfo = "none" }
+2 -2
View File
@@ -568,8 +568,8 @@ struct ChatView: View {
private func endCallButton(_ call: Call) -> some View { private func endCallButton(_ call: Call) -> some View {
Button { Button {
if let uuid = call.callkitUUID { if CallController.useCallKit(), let callUUID = call.callUUID {
CallController.shared.endCall(callUUID: uuid) CallController.shared.endCall(callUUID: callUUID)
} else { } else {
CallController.shared.endCall(call: call) {} CallController.shared.endCall(call: call) {}
} }
@@ -180,6 +180,13 @@ struct ReverseList<Item: Identifiable & Hashable & Sendable, Content: View>: UIV
snapshot, snapshot,
animatingDifferences: itemCount != 0 && abs(items.count - itemCount) == 1 animatingDifferences: itemCount != 0 && abs(items.count - itemCount) == 1
) )
// Sets content offset on initial load
if itemCount == 0 {
tableView.setContentOffset(
CGPoint(x: 0, y: -InvertedTableView.inset),
animated: false
)
}
itemCount = items.count itemCount = items.count
} }
} }
@@ -217,14 +217,31 @@ struct ChatListView: View {
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
.listRowBackground(Color.clear) .listRowBackground(Color.clear)
} }
ForEach(cs, id: \.viewId) { chat in if #available(iOS 16.0, *) {
ChatListNavLink(chat: chat) ForEach(cs, id: \.viewId) { chat in
ChatListNavLink(chat: chat)
.scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center)
.padding(.trailing, -16)
.disabled(chatModel.chatRunning != true || chatModel.deletedChats.contains(chat.chatInfo.id))
.listRowBackground(Color.clear)
}
.offset(x: -8)
} else {
ForEach(cs, id: \.viewId) { chat in
VStack(spacing: .zero) {
Divider()
.padding(.leading, 16)
ChatListNavLink(chat: chat)
.padding(.horizontal, 8)
.padding(.vertical, 6)
}
.scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center) .scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center)
.padding(.trailing, -16) .listRowSeparator(.hidden)
.listRowInsets(EdgeInsets())
.background { theme.colors.background } // Hides default list selection colour
.disabled(chatModel.chatRunning != true || chatModel.deletedChats.contains(chat.chatInfo.id)) .disabled(chatModel.chatRunning != true || chatModel.deletedChats.contains(chat.chatInfo.id))
.listRowBackground(Color.clear) }
} }
.offset(x: -8)
} }
.listStyle(.plain) .listStyle(.plain)
.onChange(of: chatModel.chatId) { currentChatId in .onChange(of: chatModel.chatId) { currentChatId in
@@ -501,21 +518,21 @@ func chatStoppedIcon() -> some View {
struct ChatListView_Previews: PreviewProvider { struct ChatListView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
let chatModel = ChatModel() let chatModel = ChatModel()
chatModel.chats = [ chatModel.updateChats([
Chat( ChatData(
chatInfo: ChatInfo.sampleData.direct, chatInfo: ChatInfo.sampleData.direct,
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello")] chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello")]
), ),
Chat( ChatData(
chatInfo: ChatInfo.sampleData.group, chatInfo: ChatInfo.sampleData.group,
chatItems: [ChatItem.getSample(1, .directSnd, .now, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")] chatItems: [ChatItem.getSample(1, .directSnd, .now, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")]
), ),
Chat( ChatData(
chatInfo: ChatInfo.sampleData.contactRequest, chatInfo: ChatInfo.sampleData.contactRequest,
chatItems: [] chatItems: []
) )
] ])
return Group { return Group {
ChatListView(showSettings: Binding.constant(false)) ChatListView(showSettings: Binding.constant(false))
.environmentObject(chatModel) .environmentObject(chatModel)
@@ -85,15 +85,18 @@ struct UserPicker: View {
.padding(8) .padding(8)
.opacity(userPickerVisible ? 1.0 : 0.0) .opacity(userPickerVisible ? 1.0 : 0.0)
.onAppear { .onAppear {
do { // This check prevents the call of listUsers after the app is suspended, and the database is closed.
// This check prevents the call of listUsers after the app is suspended, and the database is closed. if case .active = scenePhase {
if case .active = scenePhase { Task {
m.users = try listUsers() do {
} let users = try await listUsersAsync()
} catch let error { await MainActor.run { m.users = users }
logger.error("Error loading users \(responseError(error))") } catch {
} logger.error("Error loading users \(responseError(error))")
} }
}
}
}
} }
private func userView(_ u: UserInfo) -> some View { private func userView(_ u: UserInfo) -> some View {
@@ -491,7 +491,7 @@ struct DatabaseView: View {
appFilesCountAndSize = directoryFileCountAndSize(getAppFilesDirectory()) appFilesCountAndSize = directoryFileCountAndSize(getAppFilesDirectory())
do { do {
let chats = try apiGetChats() let chats = try apiGetChats()
m.updateChats(with: chats) m.updateChats(chats)
} catch let error { } catch let error {
logger.error("apiGetChats: cannot update chats \(responseError(error))") logger.error("apiGetChats: cannot update chats \(responseError(error))")
} }
@@ -100,7 +100,7 @@ extension PresetWallpaper {
scale scale
} else if let type = ChatModel.shared.currentUser?.uiThemes?.preferredMode(base.mode == DefaultThemeMode.dark)?.wallpaper?.toAppWallpaper().type, type.sameType(WallpaperType.preset(filename, nil)) { } else if let type = ChatModel.shared.currentUser?.uiThemes?.preferredMode(base.mode == DefaultThemeMode.dark)?.wallpaper?.toAppWallpaper().type, type.sameType(WallpaperType.preset(filename, nil)) {
type.scale type.scale
} else if let scale = themeOverridesDefault.get().first(where: { $0.wallpaper != nil && $0.wallpaper!.preset == filename })?.wallpaper?.scale { } else if let scale = themeOverridesDefault.get().first(where: { $0.wallpaper != nil && $0.wallpaper!.preset == filename && $0.base == base })?.wallpaper?.scale {
scale scale
} else { } else {
Float(1.0) Float(1.0)
@@ -65,8 +65,7 @@ struct LocalAuthView: View {
// Clear sensitive data on screen just in case app fails to hide its views while new database is created // Clear sensitive data on screen just in case app fails to hide its views while new database is created
m.chatId = nil m.chatId = nil
ItemsModel.shared.reversedChatItems = [] ItemsModel.shared.reversedChatItems = []
m.chats = [] m.updateChats([])
m.popChatCollector.clear()
m.users = [] m.users = []
_ = kcAppPassword.set(password) _ = kcAppPassword.set(password)
_ = kcSelfDestructPassword.remove() _ = kcSelfDestructPassword.remove()
@@ -802,7 +802,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Allow to send SimpleX links." xml:space="preserve"> <trans-unit id="Allow to send SimpleX links." xml:space="preserve">
<source>Allow to send SimpleX links.</source> <source>Allow to send SimpleX links.</source>
<target>Permitir enviar enlaces SimpleX.</target> <target>Se permite enviar enlaces SimpleX.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Allow to send files and media." xml:space="preserve"> <trans-unit id="Allow to send files and media." xml:space="preserve">
@@ -2406,7 +2406,7 @@ This is your own one-time link!</source>
</trans-unit> </trans-unit>
<trans-unit id="Do not send history to new members." xml:space="preserve"> <trans-unit id="Do not send history to new members." xml:space="preserve">
<source>Do not send history to new members.</source> <source>Do not send history to new members.</source>
<target>No enviar historial a miembros nuevos.</target> <target>No se envía el historial a los miembros nuevos.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Don't create address" xml:space="preserve"> <trans-unit id="Don't create address" xml:space="preserve">
@@ -5068,7 +5068,7 @@ Error: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Prohibit sending SimpleX links." xml:space="preserve"> <trans-unit id="Prohibit sending SimpleX links." xml:space="preserve">
<source>Prohibit sending SimpleX links.</source> <source>Prohibit sending SimpleX links.</source>
<target>No permitir el envío de enlaces SimpleX.</target> <target>No se permite enviar enlaces SimpleX.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Prohibit sending direct messages to members." xml:space="preserve"> <trans-unit id="Prohibit sending direct messages to members." xml:space="preserve">
@@ -5841,7 +5841,7 @@ Actívalo en ajustes de *Servidores y Redes*.</target>
</trans-unit> </trans-unit>
<trans-unit id="Send up to 100 last messages to new members." xml:space="preserve"> <trans-unit id="Send up to 100 last messages to new members." xml:space="preserve">
<source>Send up to 100 last messages to new members.</source> <source>Send up to 100 last messages to new members.</source>
<target>Enviar hasta 100 últimos mensajes a los miembros nuevos.</target> <target>Se envían hasta 100 mensajes más recientes a los miembros nuevos.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Sender cancelled file transfer." xml:space="preserve"> <trans-unit id="Sender cancelled file transfer." xml:space="preserve">
@@ -7449,7 +7449,7 @@ Repeat join request?</source>
</trans-unit> </trans-unit>
<trans-unit id="You can change it in Appearance settings." xml:space="preserve"> <trans-unit id="You can change it in Appearance settings." xml:space="preserve">
<source>You can change it in Appearance settings.</source> <source>You can change it in Appearance settings.</source>
<target>Puede cambiarlo desde el menú Apariencia.</target> <target>Puedes cambiar la posición de la barra desde el menú Apariencia.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="You can create it later" xml:space="preserve"> <trans-unit id="You can create it later" xml:space="preserve">
@@ -1042,7 +1042,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Bad message hash" xml:space="preserve"> <trans-unit id="Bad message hash" xml:space="preserve">
<source>Bad message hash</source> <source>Bad message hash</source>
<target>Téves üzenet hash</target> <target>Hibás az üzenet ellenőrzőösszege</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Better groups" xml:space="preserve"> <trans-unit id="Better groups" xml:space="preserve">
@@ -2131,7 +2131,7 @@ Ez az egyszer használatos hivatkozása!</target>
</trans-unit> </trans-unit>
<trans-unit id="Delete member message?" xml:space="preserve"> <trans-unit id="Delete member message?" xml:space="preserve">
<source>Delete member message?</source> <source>Delete member message?</source>
<target>Csoporttag üzenet törlése?</target> <target>Csoporttag üzenetének törlése?</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Delete message?" xml:space="preserve"> <trans-unit id="Delete message?" xml:space="preserve">
@@ -2221,7 +2221,7 @@ Ez az egyszer használatos hivatkozása!</target>
</trans-unit> </trans-unit>
<trans-unit id="Delivery receipts!" xml:space="preserve"> <trans-unit id="Delivery receipts!" xml:space="preserve">
<source>Delivery receipts!</source> <source>Delivery receipts!</source>
<target>Kézbesítési igazolások!</target> <target>Üzenet kézbesítési jelentések!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Description" xml:space="preserve"> <trans-unit id="Description" xml:space="preserve">
@@ -2681,12 +2681,12 @@ Ez az egyszer használatos hivatkozása!</target>
</trans-unit> </trans-unit>
<trans-unit id="Enter welcome message…" xml:space="preserve"> <trans-unit id="Enter welcome message…" xml:space="preserve">
<source>Enter welcome message…</source> <source>Enter welcome message…</source>
<target>Üdvözlő üzenetet megadása…</target> <target>Üdvözlő üzenet megadása…</target>
<note>placeholder</note> <note>placeholder</note>
</trans-unit> </trans-unit>
<trans-unit id="Enter welcome message… (optional)" xml:space="preserve"> <trans-unit id="Enter welcome message… (optional)" xml:space="preserve">
<source>Enter welcome message… (optional)</source> <source>Enter welcome message… (optional)</source>
<target>Üdvözlő üzenetet megadása… (opcionális)</target> <target>Üdvözlő üzenet megadása… (opcionális)</target>
<note>placeholder</note> <note>placeholder</note>
</trans-unit> </trans-unit>
<trans-unit id="Enter your name…" xml:space="preserve"> <trans-unit id="Enter your name…" xml:space="preserve">
@@ -4131,7 +4131,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz!</target>
</trans-unit> </trans-unit>
<trans-unit id="Message delivery receipts!" xml:space="preserve"> <trans-unit id="Message delivery receipts!" xml:space="preserve">
<source>Message delivery receipts!</source> <source>Message delivery receipts!</source>
<target>Üzenetkézbesítési bizonylatok!</target> <target>Üzenet kézbesítési jelentések!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Message delivery warning" xml:space="preserve"> <trans-unit id="Message delivery warning" xml:space="preserve">
@@ -4176,7 +4176,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz!</target>
</trans-unit> </trans-unit>
<trans-unit id="Message reception" xml:space="preserve"> <trans-unit id="Message reception" xml:space="preserve">
<source>Message reception</source> <source>Message reception</source>
<target>Üzenetjelentés</target> <target>Üzenet kézbesítési jelentés</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Message servers" xml:space="preserve"> <trans-unit id="Message servers" xml:space="preserve">
@@ -6573,7 +6573,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő.</target>
</trans-unit> </trans-unit>
<trans-unit id="The hash of the previous message is different." xml:space="preserve"> <trans-unit id="The hash of the previous message is different." xml:space="preserve">
<source>The hash of the previous message is different.</source> <source>The hash of the previous message is different.</source>
<target>Az előző üzenet hash-e más.</target> <target>Az előző üzenet ellenőrzőösszege különbözik.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="The message will be deleted for all members." xml:space="preserve"> <trans-unit id="The message will be deleted for all members." xml:space="preserve">
@@ -7888,7 +7888,7 @@ A SimpleX kiszolgálók nem látjhatják profilját.</target>
</trans-unit> </trans-unit>
<trans-unit id="bad message hash" xml:space="preserve"> <trans-unit id="bad message hash" xml:space="preserve">
<source>bad message hash</source> <source>bad message hash</source>
<target>téves üzenet hash</target> <target>hibás az üzenet ellenőrzőösszege</target>
<note>integrity error chat item</note> <note>integrity error chat item</note>
</trans-unit> </trans-unit>
<trans-unit id="blocked" xml:space="preserve"> <trans-unit id="blocked" xml:space="preserve">
@@ -8490,7 +8490,7 @@ A SimpleX kiszolgálók nem látjhatják profilját.</target>
</trans-unit> </trans-unit>
<trans-unit id="removed %@" xml:space="preserve"> <trans-unit id="removed %@" xml:space="preserve">
<source>removed %@</source> <source>removed %@</source>
<target>%@ eltávolítva</target> <target>eltávolította őt: %@</target>
<note>rcv group event chat item</note> <note>rcv group event chat item</note>
</trans-unit> </trans-unit>
<trans-unit id="removed contact address" xml:space="preserve"> <trans-unit id="removed contact address" xml:space="preserve">
@@ -1617,7 +1617,7 @@ Questo è il tuo link una tantum!</target>
</trans-unit> </trans-unit>
<trans-unit id="Connection and servers status." xml:space="preserve"> <trans-unit id="Connection and servers status." xml:space="preserve">
<source>Connection and servers status.</source> <source>Connection and servers status.</source>
<target>Stato di connessione e server.</target> <target>Stato della connessione e dei server.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Connection error" xml:space="preserve"> <trans-unit id="Connection error" xml:space="preserve">
@@ -3906,7 +3906,7 @@ Dit is jouw link voor groep %@!</target>
</trans-unit> </trans-unit>
<trans-unit id="Keep conversation" xml:space="preserve"> <trans-unit id="Keep conversation" xml:space="preserve">
<source>Keep conversation</source> <source>Keep conversation</source>
<target>Blijf in gesprek</target> <target>Behoud het gesprek</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Keep the app open to use it from desktop" xml:space="preserve"> <trans-unit id="Keep the app open to use it from desktop" xml:space="preserve">
@@ -752,6 +752,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Allow calls?" xml:space="preserve"> <trans-unit id="Allow calls?" xml:space="preserve">
<source>Allow calls?</source> <source>Allow calls?</source>
<target>Zezwolić na połączenia?</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Allow disappearing messages only if your contact allows it to you." xml:space="preserve"> <trans-unit id="Allow disappearing messages only if your contact allows it to you." xml:space="preserve">
@@ -791,6 +792,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Allow sharing" xml:space="preserve"> <trans-unit id="Allow sharing" xml:space="preserve">
<source>Allow sharing</source> <source>Allow sharing</source>
<target>Zezwól na udostępnianie</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Allow to irreversibly delete sent messages. (24 hours)" xml:space="preserve"> <trans-unit id="Allow to irreversibly delete sent messages. (24 hours)" xml:space="preserve">
@@ -945,10 +947,12 @@
</trans-unit> </trans-unit>
<trans-unit id="Archive contacts to chat later." xml:space="preserve"> <trans-unit id="Archive contacts to chat later." xml:space="preserve">
<source>Archive contacts to chat later.</source> <source>Archive contacts to chat later.</source>
<target>Archiwizuj kontakty aby porozmawiać później.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Archived contacts" xml:space="preserve"> <trans-unit id="Archived contacts" xml:space="preserve">
<source>Archived contacts</source> <source>Archived contacts</source>
<target>Zarchiwizowane kontakty</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Archiving database" xml:space="preserve"> <trans-unit id="Archiving database" xml:space="preserve">
@@ -1053,6 +1057,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Better networking" xml:space="preserve"> <trans-unit id="Better networking" xml:space="preserve">
<source>Better networking</source> <source>Better networking</source>
<target>Lepsze sieciowanie</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Black" xml:space="preserve"> <trans-unit id="Black" xml:space="preserve">
@@ -1097,10 +1102,12 @@
</trans-unit> </trans-unit>
<trans-unit id="Blur for better privacy." xml:space="preserve"> <trans-unit id="Blur for better privacy." xml:space="preserve">
<source>Blur for better privacy.</source> <source>Blur for better privacy.</source>
<target>Rozmycie dla lepszej prywatności.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Blur media" xml:space="preserve"> <trans-unit id="Blur media" xml:space="preserve">
<source>Blur media</source> <source>Blur media</source>
<target>Rozmycie mediów</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve"> <trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
@@ -1150,6 +1157,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Calls prohibited!" xml:space="preserve"> <trans-unit id="Calls prohibited!" xml:space="preserve">
<source>Calls prohibited!</source> <source>Calls prohibited!</source>
<target>Połączenia zakazane!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Camera not available" xml:space="preserve"> <trans-unit id="Camera not available" xml:space="preserve">
@@ -1159,10 +1167,12 @@
</trans-unit> </trans-unit>
<trans-unit id="Can't call contact" xml:space="preserve"> <trans-unit id="Can't call contact" xml:space="preserve">
<source>Can't call contact</source> <source>Can't call contact</source>
<target>Nie można zadzwonić do kontaktu</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Can't call member" xml:space="preserve"> <trans-unit id="Can't call member" xml:space="preserve">
<source>Can't call member</source> <source>Can't call member</source>
<target>Nie można zadzwonić do członka</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Can't invite contact!" xml:space="preserve"> <trans-unit id="Can't invite contact!" xml:space="preserve">
@@ -1177,6 +1187,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Can't message member" xml:space="preserve"> <trans-unit id="Can't message member" xml:space="preserve">
<source>Can't message member</source> <source>Can't message member</source>
<target>Nie można wysłać wiadomości do członka</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Cancel" xml:space="preserve"> <trans-unit id="Cancel" xml:space="preserve">
@@ -1292,6 +1303,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Chat database exported" xml:space="preserve"> <trans-unit id="Chat database exported" xml:space="preserve">
<source>Chat database exported</source> <source>Chat database exported</source>
<target>Wyeksportowano bazę danych czatu</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Chat database imported" xml:space="preserve"> <trans-unit id="Chat database imported" xml:space="preserve">
@@ -1316,6 +1328,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Chat list" xml:space="preserve"> <trans-unit id="Chat list" xml:space="preserve">
<source>Chat list</source> <source>Chat list</source>
<target>Lista czatów</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Chat migrated!" xml:space="preserve"> <trans-unit id="Chat migrated!" xml:space="preserve">
@@ -1405,6 +1418,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Color chats with the new themes." xml:space="preserve"> <trans-unit id="Color chats with the new themes." xml:space="preserve">
<source>Color chats with the new themes.</source> <source>Color chats with the new themes.</source>
<target>Koloruj czaty z nowymi motywami.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Color mode" xml:space="preserve"> <trans-unit id="Color mode" xml:space="preserve">
@@ -1449,6 +1463,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Confirm contact deletion?" xml:space="preserve"> <trans-unit id="Confirm contact deletion?" xml:space="preserve">
<source>Confirm contact deletion?</source> <source>Confirm contact deletion?</source>
<target>Potwierdzić usunięcie kontaktu?</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Confirm database upgrades" xml:space="preserve"> <trans-unit id="Confirm database upgrades" xml:space="preserve">
@@ -1508,6 +1523,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Connect to your friends faster." xml:space="preserve"> <trans-unit id="Connect to your friends faster." xml:space="preserve">
<source>Connect to your friends faster.</source> <source>Connect to your friends faster.</source>
<target>Szybciej łącz się ze znajomymi.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Connect to yourself?" xml:space="preserve"> <trans-unit id="Connect to yourself?" xml:space="preserve">
@@ -1586,6 +1602,7 @@ To jest twój jednorazowy link!</target>
</trans-unit> </trans-unit>
<trans-unit id="Connecting to contact, please wait or check later!" xml:space="preserve"> <trans-unit id="Connecting to contact, please wait or check later!" xml:space="preserve">
<source>Connecting to contact, please wait or check later!</source> <source>Connecting to contact, please wait or check later!</source>
<target>Łączenie z kontaktem, poczekaj lub sprawdź później!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Connecting to desktop" xml:space="preserve"> <trans-unit id="Connecting to desktop" xml:space="preserve">
@@ -1600,6 +1617,7 @@ To jest twój jednorazowy link!</target>
</trans-unit> </trans-unit>
<trans-unit id="Connection and servers status." xml:space="preserve"> <trans-unit id="Connection and servers status." xml:space="preserve">
<source>Connection and servers status.</source> <source>Connection and servers status.</source>
<target>Stan połączenia i serwerów.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Connection error" xml:space="preserve"> <trans-unit id="Connection error" xml:space="preserve">
@@ -1614,6 +1632,7 @@ To jest twój jednorazowy link!</target>
</trans-unit> </trans-unit>
<trans-unit id="Connection notifications" xml:space="preserve"> <trans-unit id="Connection notifications" xml:space="preserve">
<source>Connection notifications</source> <source>Connection notifications</source>
<target>Powiadomienia o połączeniu</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Connection request sent!" xml:space="preserve"> <trans-unit id="Connection request sent!" xml:space="preserve">
@@ -1653,6 +1672,7 @@ To jest twój jednorazowy link!</target>
</trans-unit> </trans-unit>
<trans-unit id="Contact deleted!" xml:space="preserve"> <trans-unit id="Contact deleted!" xml:space="preserve">
<source>Contact deleted!</source> <source>Contact deleted!</source>
<target>Kontakt usunięty!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Contact hidden:" xml:space="preserve"> <trans-unit id="Contact hidden:" xml:space="preserve">
@@ -1667,6 +1687,7 @@ To jest twój jednorazowy link!</target>
</trans-unit> </trans-unit>
<trans-unit id="Contact is deleted." xml:space="preserve"> <trans-unit id="Contact is deleted." xml:space="preserve">
<source>Contact is deleted.</source> <source>Contact is deleted.</source>
<target>Kontakt jest usunięty.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Contact name" xml:space="preserve"> <trans-unit id="Contact name" xml:space="preserve">
@@ -1681,6 +1702,7 @@ To jest twój jednorazowy link!</target>
</trans-unit> </trans-unit>
<trans-unit id="Contact will be deleted - this cannot be undone!" xml:space="preserve"> <trans-unit id="Contact will be deleted - this cannot be undone!" xml:space="preserve">
<source>Contact will be deleted - this cannot be undone!</source> <source>Contact will be deleted - this cannot be undone!</source>
<target>Kontakt zostanie usunięty nie można tego cofnąć!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Contacts" xml:space="preserve"> <trans-unit id="Contacts" xml:space="preserve">
@@ -1700,6 +1722,7 @@ To jest twój jednorazowy link!</target>
</trans-unit> </trans-unit>
<trans-unit id="Conversation deleted!" xml:space="preserve"> <trans-unit id="Conversation deleted!" xml:space="preserve">
<source>Conversation deleted!</source> <source>Conversation deleted!</source>
<target>Rozmowa usunięta!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Copy" xml:space="preserve"> <trans-unit id="Copy" xml:space="preserve">
@@ -1978,6 +2001,7 @@ To jest twój jednorazowy link!</target>
</trans-unit> </trans-unit>
<trans-unit id="Delete %lld messages of members?" xml:space="preserve"> <trans-unit id="Delete %lld messages of members?" xml:space="preserve">
<source>Delete %lld messages of members?</source> <source>Delete %lld messages of members?</source>
<target>Usunąć %lld wiadomości członków?</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Delete %lld messages?" xml:space="preserve"> <trans-unit id="Delete %lld messages?" xml:space="preserve">
@@ -2042,6 +2066,7 @@ To jest twój jednorazowy link!</target>
</trans-unit> </trans-unit>
<trans-unit id="Delete contact?" xml:space="preserve"> <trans-unit id="Delete contact?" xml:space="preserve">
<source>Delete contact?</source> <source>Delete contact?</source>
<target>Usunąć kontakt?</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Delete database" xml:space="preserve"> <trans-unit id="Delete database" xml:space="preserve">
@@ -2151,6 +2176,7 @@ To jest twój jednorazowy link!</target>
</trans-unit> </trans-unit>
<trans-unit id="Delete up to 20 messages at once." xml:space="preserve"> <trans-unit id="Delete up to 20 messages at once." xml:space="preserve">
<source>Delete up to 20 messages at once.</source> <source>Delete up to 20 messages at once.</source>
<target>Usuń do 20 wiadomości na raz.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Delete user profile?" xml:space="preserve"> <trans-unit id="Delete user profile?" xml:space="preserve">
@@ -2160,6 +2186,7 @@ To jest twój jednorazowy link!</target>
</trans-unit> </trans-unit>
<trans-unit id="Delete without notification" xml:space="preserve"> <trans-unit id="Delete without notification" xml:space="preserve">
<source>Delete without notification</source> <source>Delete without notification</source>
<target>Usuń bez powiadomienia</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Deleted" xml:space="preserve"> <trans-unit id="Deleted" xml:space="preserve">
@@ -2219,6 +2246,7 @@ To jest twój jednorazowy link!</target>
</trans-unit> </trans-unit>
<trans-unit id="Destination server address of %@ is incompatible with forwarding server %@ settings." xml:space="preserve"> <trans-unit id="Destination server address of %@ is incompatible with forwarding server %@ settings." xml:space="preserve">
<source>Destination server address of %@ is incompatible with forwarding server %@ settings.</source> <source>Destination server address of %@ is incompatible with forwarding server %@ settings.</source>
<target>Adres serwera docelowego %@ jest niekompatybilny z ustawieniami serwera przekazującego %@.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Destination server error: %@" xml:space="preserve"> <trans-unit id="Destination server error: %@" xml:space="preserve">
@@ -2228,6 +2256,7 @@ To jest twój jednorazowy link!</target>
</trans-unit> </trans-unit>
<trans-unit id="Destination server version of %@ is incompatible with forwarding server %@." xml:space="preserve"> <trans-unit id="Destination server version of %@ is incompatible with forwarding server %@." xml:space="preserve">
<source>Destination server version of %@ is incompatible with forwarding server %@.</source> <source>Destination server version of %@ is incompatible with forwarding server %@.</source>
<target>Wersja serwera docelowego %@ jest niekompatybilna z serwerem przekierowującym %@.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Detailed statistics" xml:space="preserve"> <trans-unit id="Detailed statistics" xml:space="preserve">
@@ -2247,6 +2276,7 @@ To jest twój jednorazowy link!</target>
</trans-unit> </trans-unit>
<trans-unit id="Developer options" xml:space="preserve"> <trans-unit id="Developer options" xml:space="preserve">
<source>Developer options</source> <source>Developer options</source>
<target>Opcje deweloperskie</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Developer tools" xml:space="preserve"> <trans-unit id="Developer tools" xml:space="preserve">
@@ -2301,6 +2331,7 @@ To jest twój jednorazowy link!</target>
</trans-unit> </trans-unit>
<trans-unit id="Disabled" xml:space="preserve"> <trans-unit id="Disabled" xml:space="preserve">
<source>Disabled</source> <source>Disabled</source>
<target>Wyłączony</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Disappearing message" xml:space="preserve"> <trans-unit id="Disappearing message" xml:space="preserve">
@@ -2530,6 +2561,7 @@ To jest twój jednorazowy link!</target>
</trans-unit> </trans-unit>
<trans-unit id="Enabled" xml:space="preserve"> <trans-unit id="Enabled" xml:space="preserve">
<source>Enabled</source> <source>Enabled</source>
<target>Włączony</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Enabled for" xml:space="preserve"> <trans-unit id="Enabled for" xml:space="preserve">
@@ -2704,6 +2736,7 @@ To jest twój jednorazowy link!</target>
</trans-unit> </trans-unit>
<trans-unit id="Error connecting to forwarding server %@. Please try later." xml:space="preserve"> <trans-unit id="Error connecting to forwarding server %@. Please try later." xml:space="preserve">
<source>Error connecting to forwarding server %@. Please try later.</source> <source>Error connecting to forwarding server %@. Please try later.</source>
<target>Błąd połączenia z serwerem przekierowania %@. Spróbuj ponownie później.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Error creating address" xml:space="preserve"> <trans-unit id="Error creating address" xml:space="preserve">
@@ -3209,14 +3242,17 @@ To jest twój jednorazowy link!</target>
</trans-unit> </trans-unit>
<trans-unit id="Forwarding server %@ failed to connect to destination server %@. Please try later." xml:space="preserve"> <trans-unit id="Forwarding server %@ failed to connect to destination server %@. Please try later." xml:space="preserve">
<source>Forwarding server %@ failed to connect to destination server %@. Please try later.</source> <source>Forwarding server %@ failed to connect to destination server %@. Please try later.</source>
<target>Serwer przekazujący %@ nie mógł połączyć się z serwerem docelowym %@. Spróbuj ponownie później.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Forwarding server address is incompatible with network settings: %@." xml:space="preserve"> <trans-unit id="Forwarding server address is incompatible with network settings: %@." xml:space="preserve">
<source>Forwarding server address is incompatible with network settings: %@.</source> <source>Forwarding server address is incompatible with network settings: %@.</source>
<target>Adres serwera przekierowującego jest niekompatybilny z ustawieniami sieciowymi: %@.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Forwarding server version is incompatible with network settings: %@." xml:space="preserve"> <trans-unit id="Forwarding server version is incompatible with network settings: %@." xml:space="preserve">
<source>Forwarding server version is incompatible with network settings: %@.</source> <source>Forwarding server version is incompatible with network settings: %@.</source>
<target>Wersja serwera przekierowującego jest niekompatybilna z ustawieniami sieciowymi: %@.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Forwarding server: %@&#10;Destination server error: %@" xml:space="preserve"> <trans-unit id="Forwarding server: %@&#10;Destination server error: %@" xml:space="preserve">
@@ -3803,6 +3839,7 @@ Błąd: %2$@</target>
</trans-unit> </trans-unit>
<trans-unit id="It protects your IP address and connections." xml:space="preserve"> <trans-unit id="It protects your IP address and connections." xml:space="preserve">
<source>It protects your IP address and connections.</source> <source>It protects your IP address and connections.</source>
<target>Chroni Twój adres IP i połączenia.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="It seems like you are already connected via this link. If it is not the case, there was an error (%@)." xml:space="preserve"> <trans-unit id="It seems like you are already connected via this link. If it is not the case, there was an error (%@)." xml:space="preserve">
@@ -3869,6 +3906,7 @@ To jest twój link do grupy %@!</target>
</trans-unit> </trans-unit>
<trans-unit id="Keep conversation" xml:space="preserve"> <trans-unit id="Keep conversation" xml:space="preserve">
<source>Keep conversation</source> <source>Keep conversation</source>
<target>Zachowaj rozmowę</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Keep the app open to use it from desktop" xml:space="preserve"> <trans-unit id="Keep the app open to use it from desktop" xml:space="preserve">
@@ -4048,10 +4086,12 @@ To jest twój link do grupy %@!</target>
</trans-unit> </trans-unit>
<trans-unit id="Media &amp; file servers" xml:space="preserve"> <trans-unit id="Media &amp; file servers" xml:space="preserve">
<source>Media &amp; file servers</source> <source>Media &amp; file servers</source>
<target>Serwery mediów i plików</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Medium" xml:space="preserve"> <trans-unit id="Medium" xml:space="preserve">
<source>Medium</source> <source>Medium</source>
<target>Średni</target>
<note>blur media</note> <note>blur media</note>
</trans-unit> </trans-unit>
<trans-unit id="Member" xml:space="preserve"> <trans-unit id="Member" xml:space="preserve">
@@ -4141,6 +4181,7 @@ To jest twój link do grupy %@!</target>
</trans-unit> </trans-unit>
<trans-unit id="Message servers" xml:space="preserve"> <trans-unit id="Message servers" xml:space="preserve">
<source>Message servers</source> <source>Message servers</source>
<target>Serwery wiadomości</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Message source remains private." xml:space="preserve"> <trans-unit id="Message source remains private." xml:space="preserve">
@@ -4355,6 +4396,7 @@ To jest twój link do grupy %@!</target>
</trans-unit> </trans-unit>
<trans-unit id="New chat experience 🎉" xml:space="preserve"> <trans-unit id="New chat experience 🎉" xml:space="preserve">
<source>New chat experience 🎉</source> <source>New chat experience 🎉</source>
<target>Nowe możliwości czatu 🎉</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="New contact request" xml:space="preserve"> <trans-unit id="New contact request" xml:space="preserve">
@@ -4389,6 +4431,7 @@ To jest twój link do grupy %@!</target>
</trans-unit> </trans-unit>
<trans-unit id="New media options" xml:space="preserve"> <trans-unit id="New media options" xml:space="preserve">
<source>New media options</source> <source>New media options</source>
<target>Nowe opcje mediów</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="New member role" xml:space="preserve"> <trans-unit id="New member role" xml:space="preserve">
@@ -4483,6 +4526,7 @@ To jest twój link do grupy %@!</target>
</trans-unit> </trans-unit>
<trans-unit id="Nothing selected" xml:space="preserve"> <trans-unit id="Nothing selected" xml:space="preserve">
<source>Nothing selected</source> <source>Nothing selected</source>
<target>Nic nie jest zaznaczone</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Notifications" xml:space="preserve"> <trans-unit id="Notifications" xml:space="preserve">
@@ -4560,6 +4604,7 @@ Wymaga włączenia VPN.</target>
</trans-unit> </trans-unit>
<trans-unit id="Only delete conversation" xml:space="preserve"> <trans-unit id="Only delete conversation" xml:space="preserve">
<source>Only delete conversation</source> <source>Only delete conversation</source>
<target>Usuń tylko rozmowę</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Only group owners can change group preferences." xml:space="preserve"> <trans-unit id="Only group owners can change group preferences." xml:space="preserve">
@@ -4799,10 +4844,12 @@ Wymaga włączenia VPN.</target>
</trans-unit> </trans-unit>
<trans-unit id="Play from the chat list." xml:space="preserve"> <trans-unit id="Play from the chat list." xml:space="preserve">
<source>Play from the chat list.</source> <source>Play from the chat list.</source>
<target>Odtwórz z listy czatów.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Please ask your contact to enable calls." xml:space="preserve"> <trans-unit id="Please ask your contact to enable calls." xml:space="preserve">
<source>Please ask your contact to enable calls.</source> <source>Please ask your contact to enable calls.</source>
<target>Poproś kontakt o włącznie połączeń.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Please ask your contact to enable sending voice messages." xml:space="preserve"> <trans-unit id="Please ask your contact to enable sending voice messages." xml:space="preserve">
@@ -5108,6 +5155,7 @@ Włącz w ustawianiach *Sieć i serwery* .</target>
</trans-unit> </trans-unit>
<trans-unit id="Reachable chat toolbar" xml:space="preserve"> <trans-unit id="Reachable chat toolbar" xml:space="preserve">
<source>Reachable chat toolbar</source> <source>Reachable chat toolbar</source>
<target>Osiągalny pasek narzędzi czatu</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="React…" xml:space="preserve"> <trans-unit id="React…" xml:space="preserve">
@@ -5383,6 +5431,7 @@ Włącz w ustawianiach *Sieć i serwery* .</target>
</trans-unit> </trans-unit>
<trans-unit id="Reset all hints" xml:space="preserve"> <trans-unit id="Reset all hints" xml:space="preserve">
<source>Reset all hints</source> <source>Reset all hints</source>
<target>Zresetuj wszystkie wskazówki</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Reset all statistics" xml:space="preserve"> <trans-unit id="Reset all statistics" xml:space="preserve">
@@ -5517,6 +5566,7 @@ Włącz w ustawianiach *Sieć i serwery* .</target>
</trans-unit> </trans-unit>
<trans-unit id="Save and reconnect" xml:space="preserve"> <trans-unit id="Save and reconnect" xml:space="preserve">
<source>Save and reconnect</source> <source>Save and reconnect</source>
<target>Zapisz i połącz ponownie</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Save and update group profile" xml:space="preserve"> <trans-unit id="Save and update group profile" xml:space="preserve">
@@ -5681,6 +5731,7 @@ Włącz w ustawianiach *Sieć i serwery* .</target>
</trans-unit> </trans-unit>
<trans-unit id="Selected %lld" xml:space="preserve"> <trans-unit id="Selected %lld" xml:space="preserve">
<source>Selected %lld</source> <source>Selected %lld</source>
<target>Zaznaczono %lld</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve"> <trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
@@ -5750,6 +5801,7 @@ Włącz w ustawianiach *Sieć i serwery* .</target>
</trans-unit> </trans-unit>
<trans-unit id="Send message to enable calls." xml:space="preserve"> <trans-unit id="Send message to enable calls." xml:space="preserve">
<source>Send message to enable calls.</source> <source>Send message to enable calls.</source>
<target>Wyślij wiadomość aby włączyć połączenia.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Send messages directly when IP address is protected and your or destination server does not support private routing." xml:space="preserve"> <trans-unit id="Send messages directly when IP address is protected and your or destination server does not support private routing." xml:space="preserve">
@@ -6039,6 +6091,7 @@ Włącz w ustawianiach *Sieć i serwery* .</target>
</trans-unit> </trans-unit>
<trans-unit id="Share from other apps." xml:space="preserve"> <trans-unit id="Share from other apps." xml:space="preserve">
<source>Share from other apps.</source> <source>Share from other apps.</source>
<target>Udostępnij z innych aplikacji.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Share link" xml:space="preserve"> <trans-unit id="Share link" xml:space="preserve">
@@ -6053,6 +6106,7 @@ Włącz w ustawianiach *Sieć i serwery* .</target>
</trans-unit> </trans-unit>
<trans-unit id="Share to SimpleX" xml:space="preserve"> <trans-unit id="Share to SimpleX" xml:space="preserve">
<source>Share to SimpleX</source> <source>Share to SimpleX</source>
<target>Udostępnij do SimpleX</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Share with contacts" xml:space="preserve"> <trans-unit id="Share with contacts" xml:space="preserve">
@@ -6207,10 +6261,12 @@ Włącz w ustawianiach *Sieć i serwery* .</target>
</trans-unit> </trans-unit>
<trans-unit id="Soft" xml:space="preserve"> <trans-unit id="Soft" xml:space="preserve">
<source>Soft</source> <source>Soft</source>
<target>Łagodny</target>
<note>blur media</note> <note>blur media</note>
</trans-unit> </trans-unit>
<trans-unit id="Some file(s) were not exported:" xml:space="preserve"> <trans-unit id="Some file(s) were not exported:" xml:space="preserve">
<source>Some file(s) were not exported:</source> <source>Some file(s) were not exported:</source>
<target>Niektóre plik(i) nie zostały wyeksportowane:</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Some non-fatal errors occurred during import - you may see Chat console for more details." xml:space="preserve"> <trans-unit id="Some non-fatal errors occurred during import - you may see Chat console for more details." xml:space="preserve">
@@ -6220,6 +6276,7 @@ Włącz w ustawianiach *Sieć i serwery* .</target>
</trans-unit> </trans-unit>
<trans-unit id="Some non-fatal errors occurred during import:" xml:space="preserve"> <trans-unit id="Some non-fatal errors occurred during import:" xml:space="preserve">
<source>Some non-fatal errors occurred during import:</source> <source>Some non-fatal errors occurred during import:</source>
<target>Podczas importu wystąpiły niekrytyczne błędy:</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Somebody" xml:space="preserve"> <trans-unit id="Somebody" xml:space="preserve">
@@ -6319,6 +6376,7 @@ Włącz w ustawianiach *Sieć i serwery* .</target>
</trans-unit> </trans-unit>
<trans-unit id="Strong" xml:space="preserve"> <trans-unit id="Strong" xml:space="preserve">
<source>Strong</source> <source>Strong</source>
<target>Silne</target>
<note>blur media</note> <note>blur media</note>
</trans-unit> </trans-unit>
<trans-unit id="Submit" xml:space="preserve"> <trans-unit id="Submit" xml:space="preserve">
@@ -6358,6 +6416,7 @@ Włącz w ustawianiach *Sieć i serwery* .</target>
</trans-unit> </trans-unit>
<trans-unit id="TCP connection" xml:space="preserve"> <trans-unit id="TCP connection" xml:space="preserve">
<source>TCP connection</source> <source>TCP connection</source>
<target>Połączenie TCP</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="TCP connection timeout" xml:space="preserve"> <trans-unit id="TCP connection timeout" xml:space="preserve">
@@ -6529,10 +6588,12 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom
</trans-unit> </trans-unit>
<trans-unit id="The messages will be deleted for all members." xml:space="preserve"> <trans-unit id="The messages will be deleted for all members." xml:space="preserve">
<source>The messages will be deleted for all members.</source> <source>The messages will be deleted for all members.</source>
<target>Wiadomości zostaną usunięte dla wszystkich członków.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="The messages will be marked as moderated for all members." xml:space="preserve"> <trans-unit id="The messages will be marked as moderated for all members." xml:space="preserve">
<source>The messages will be marked as moderated for all members.</source> <source>The messages will be marked as moderated for all members.</source>
<target>Wiadomości zostaną oznaczone jako moderowane dla wszystkich członków.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="The next generation of private messaging" xml:space="preserve"> <trans-unit id="The next generation of private messaging" xml:space="preserve">
@@ -6719,6 +6780,7 @@ Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania.</ta
</trans-unit> </trans-unit>
<trans-unit id="Toggle chat list:" xml:space="preserve"> <trans-unit id="Toggle chat list:" xml:space="preserve">
<source>Toggle chat list:</source> <source>Toggle chat list:</source>
<target>Przełącz listę czatów:</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Toggle incognito when connecting." xml:space="preserve"> <trans-unit id="Toggle incognito when connecting." xml:space="preserve">
@@ -6728,6 +6790,7 @@ Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania.</ta
</trans-unit> </trans-unit>
<trans-unit id="Toolbar opacity" xml:space="preserve"> <trans-unit id="Toolbar opacity" xml:space="preserve">
<source>Toolbar opacity</source> <source>Toolbar opacity</source>
<target>Nieprzezroczystość paska narzędzi</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Total" xml:space="preserve"> <trans-unit id="Total" xml:space="preserve">
@@ -6914,6 +6977,7 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc
</trans-unit> </trans-unit>
<trans-unit id="Update settings?" xml:space="preserve"> <trans-unit id="Update settings?" xml:space="preserve">
<source>Update settings?</source> <source>Update settings?</source>
<target>Zaktualizować ustawienia?</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Updating settings will re-connect the client to all servers." xml:space="preserve"> <trans-unit id="Updating settings will re-connect the client to all servers." xml:space="preserve">
@@ -7023,6 +7087,7 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc
</trans-unit> </trans-unit>
<trans-unit id="Use the app with one hand." xml:space="preserve"> <trans-unit id="Use the app with one hand." xml:space="preserve">
<source>Use the app with one hand.</source> <source>Use the app with one hand.</source>
<target>Korzystaj z aplikacji jedną ręką.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="User profile" xml:space="preserve"> <trans-unit id="User profile" xml:space="preserve">
@@ -7384,6 +7449,7 @@ Powtórzyć prośbę dołączenia?</target>
</trans-unit> </trans-unit>
<trans-unit id="You can change it in Appearance settings." xml:space="preserve"> <trans-unit id="You can change it in Appearance settings." xml:space="preserve">
<source>You can change it in Appearance settings.</source> <source>You can change it in Appearance settings.</source>
<target>Możesz to zmienić w ustawieniach wyglądu.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="You can create it later" xml:space="preserve"> <trans-unit id="You can create it later" xml:space="preserve">
@@ -7423,6 +7489,7 @@ Powtórzyć prośbę dołączenia?</target>
</trans-unit> </trans-unit>
<trans-unit id="You can send messages to %@ from Archived contacts." xml:space="preserve"> <trans-unit id="You can send messages to %@ from Archived contacts." xml:space="preserve">
<source>You can send messages to %@ from Archived contacts.</source> <source>You can send messages to %@ from Archived contacts.</source>
<target>Możesz wysyłać wiadomości do %@ ze zarchiwizowanych kontaktów.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="You can set lock screen notification preview via settings." xml:space="preserve"> <trans-unit id="You can set lock screen notification preview via settings." xml:space="preserve">
@@ -7452,6 +7519,7 @@ Powtórzyć prośbę dołączenia?</target>
</trans-unit> </trans-unit>
<trans-unit id="You can still view conversation with %@ in the list of chats." xml:space="preserve"> <trans-unit id="You can still view conversation with %@ in the list of chats." xml:space="preserve">
<source>You can still view conversation with %@ in the list of chats.</source> <source>You can still view conversation with %@ in the list of chats.</source>
<target>Nadal możesz przeglądać rozmowę z %@ na liście czatów.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="You can turn on SimpleX Lock via Settings." xml:space="preserve"> <trans-unit id="You can turn on SimpleX Lock via Settings." xml:space="preserve">
@@ -7518,10 +7586,12 @@ Powtórzyć prośbę połączenia?</target>
</trans-unit> </trans-unit>
<trans-unit id="You may migrate the exported database." xml:space="preserve"> <trans-unit id="You may migrate the exported database." xml:space="preserve">
<source>You may migrate the exported database.</source> <source>You may migrate the exported database.</source>
<target>Możesz zmigrować wyeksportowaną bazy danych.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="You may save the exported archive." xml:space="preserve"> <trans-unit id="You may save the exported archive." xml:space="preserve">
<source>You may save the exported archive.</source> <source>You may save the exported archive.</source>
<target>Możesz zapisać wyeksportowane archiwum.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts." xml:space="preserve"> <trans-unit id="You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts." xml:space="preserve">
@@ -7531,6 +7601,7 @@ Powtórzyć prośbę połączenia?</target>
</trans-unit> </trans-unit>
<trans-unit id="You need to allow your contact to call to be able to call them." xml:space="preserve"> <trans-unit id="You need to allow your contact to call to be able to call them." xml:space="preserve">
<source>You need to allow your contact to call to be able to call them.</source> <source>You need to allow your contact to call to be able to call them.</source>
<target>Aby móc dzwonić, musisz zezwolić kontaktowi na połączenia.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="You need to allow your contact to send voice messages to be able to send them." xml:space="preserve"> <trans-unit id="You need to allow your contact to send voice messages to be able to send them." xml:space="preserve">
@@ -7842,6 +7913,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu.</target>
</trans-unit> </trans-unit>
<trans-unit id="call" xml:space="preserve"> <trans-unit id="call" xml:space="preserve">
<source>call</source> <source>call</source>
<target>zadzwoń</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="call error" xml:space="preserve"> <trans-unit id="call error" xml:space="preserve">
@@ -8216,6 +8288,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu.</target>
</trans-unit> </trans-unit>
<trans-unit id="invite" xml:space="preserve"> <trans-unit id="invite" xml:space="preserve">
<source>invite</source> <source>invite</source>
<target>zaproś</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="invited" xml:space="preserve"> <trans-unit id="invited" xml:space="preserve">
@@ -8275,6 +8348,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu.</target>
</trans-unit> </trans-unit>
<trans-unit id="message" xml:space="preserve"> <trans-unit id="message" xml:space="preserve">
<source>message</source> <source>message</source>
<target>wiadomość</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="message received" xml:space="preserve"> <trans-unit id="message received" xml:space="preserve">
@@ -8309,6 +8383,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu.</target>
</trans-unit> </trans-unit>
<trans-unit id="mute" xml:space="preserve"> <trans-unit id="mute" xml:space="preserve">
<source>mute</source> <source>mute</source>
<target>wycisz</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="never" xml:space="preserve"> <trans-unit id="never" xml:space="preserve">
@@ -8445,6 +8520,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu.</target>
</trans-unit> </trans-unit>
<trans-unit id="search" xml:space="preserve"> <trans-unit id="search" xml:space="preserve">
<source>search</source> <source>search</source>
<target>szukaj</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="sec" xml:space="preserve"> <trans-unit id="sec" xml:space="preserve">
@@ -8533,6 +8609,7 @@ ostatnia otrzymana wiadomość: %2$@</target>
</trans-unit> </trans-unit>
<trans-unit id="unmute" xml:space="preserve"> <trans-unit id="unmute" xml:space="preserve">
<source>unmute</source> <source>unmute</source>
<target>wyłącz wyciszenie</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="unprotected" xml:space="preserve"> <trans-unit id="unprotected" xml:space="preserve">
@@ -8582,6 +8659,7 @@ ostatnia otrzymana wiadomość: %2$@</target>
</trans-unit> </trans-unit>
<trans-unit id="video" xml:space="preserve"> <trans-unit id="video" xml:space="preserve">
<source>video</source> <source>video</source>
<target>wideo</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="video call (not e2e encrypted)" xml:space="preserve"> <trans-unit id="video call (not e2e encrypted)" xml:space="preserve">
@@ -8762,14 +8840,17 @@ ostatnia otrzymana wiadomość: %2$@</target>
<body> <body>
<trans-unit id="CFBundleDisplayName" xml:space="preserve"> <trans-unit id="CFBundleDisplayName" xml:space="preserve">
<source>SimpleX SE</source> <source>SimpleX SE</source>
<target>SimpleX SE</target>
<note>Bundle display name</note> <note>Bundle display name</note>
</trans-unit> </trans-unit>
<trans-unit id="CFBundleName" xml:space="preserve"> <trans-unit id="CFBundleName" xml:space="preserve">
<source>SimpleX SE</source> <source>SimpleX SE</source>
<target>SimpleX SE</target>
<note>Bundle name</note> <note>Bundle name</note>
</trans-unit> </trans-unit>
<trans-unit id="NSHumanReadableCopyright" xml:space="preserve"> <trans-unit id="NSHumanReadableCopyright" xml:space="preserve">
<source>Copyright © 2024 SimpleX Chat. All rights reserved.</source> <source>Copyright © 2024 SimpleX Chat. All rights reserved.</source>
<target>Copyright © 2024 SimpleX Chat. Wszelkie prawa zastrzeżone.</target>
<note>Copyright (human-readable)</note> <note>Copyright (human-readable)</note>
</trans-unit> </trans-unit>
</body> </body>
@@ -8781,150 +8862,187 @@ ostatnia otrzymana wiadomość: %2$@</target>
<body> <body>
<trans-unit id="%@" xml:space="preserve"> <trans-unit id="%@" xml:space="preserve">
<source>%@</source> <source>%@</source>
<target>%@</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="App is locked!" xml:space="preserve"> <trans-unit id="App is locked!" xml:space="preserve">
<source>App is locked!</source> <source>App is locked!</source>
<target>Aplikacja zablokowana!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Cancel" xml:space="preserve"> <trans-unit id="Cancel" xml:space="preserve">
<source>Cancel</source> <source>Cancel</source>
<target>Anuluj</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Cannot access keychain to save database password" xml:space="preserve"> <trans-unit id="Cannot access keychain to save database password" xml:space="preserve">
<source>Cannot access keychain to save database password</source> <source>Cannot access keychain to save database password</source>
<target>Nie można uzyskać dostępu do pęku kluczy aby zapisać hasło do bazy danych</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Cannot forward message" xml:space="preserve"> <trans-unit id="Cannot forward message" xml:space="preserve">
<source>Cannot forward message</source> <source>Cannot forward message</source>
<target>Nie można przekazać wiadomości</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Comment" xml:space="preserve"> <trans-unit id="Comment" xml:space="preserve">
<source>Comment</source> <source>Comment</source>
<target>Komentarz</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Currently maximum supported file size is %@." xml:space="preserve"> <trans-unit id="Currently maximum supported file size is %@." xml:space="preserve">
<source>Currently maximum supported file size is %@.</source> <source>Currently maximum supported file size is %@.</source>
<target>Obecnie maksymalny obsługiwany rozmiar pliku to %@.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Database downgrade required" xml:space="preserve"> <trans-unit id="Database downgrade required" xml:space="preserve">
<source>Database downgrade required</source> <source>Database downgrade required</source>
<target>Wymagane obniżenie wersji bazy danych</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Database encrypted!" xml:space="preserve"> <trans-unit id="Database encrypted!" xml:space="preserve">
<source>Database encrypted!</source> <source>Database encrypted!</source>
<target>Baza danych zaszyfrowana!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Database error" xml:space="preserve"> <trans-unit id="Database error" xml:space="preserve">
<source>Database error</source> <source>Database error</source>
<target>Błąd bazy danych</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Database passphrase is different from saved in the keychain." xml:space="preserve"> <trans-unit id="Database passphrase is different from saved in the keychain." xml:space="preserve">
<source>Database passphrase is different from saved in the keychain.</source> <source>Database passphrase is different from saved in the keychain.</source>
<target>Hasło bazy danych jest inne niż zapisane w pęku kluczy.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Database passphrase is required to open chat." xml:space="preserve"> <trans-unit id="Database passphrase is required to open chat." xml:space="preserve">
<source>Database passphrase is required to open chat.</source> <source>Database passphrase is required to open chat.</source>
<target>Hasło do bazy danych jest wymagane do otwarcia czatu.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Database upgrade required" xml:space="preserve"> <trans-unit id="Database upgrade required" xml:space="preserve">
<source>Database upgrade required</source> <source>Database upgrade required</source>
<target>Wymagana aktualizacja bazy danych</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Error preparing file" xml:space="preserve"> <trans-unit id="Error preparing file" xml:space="preserve">
<source>Error preparing file</source> <source>Error preparing file</source>
<target>Błąd przygotowania pliku</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Error preparing message" xml:space="preserve"> <trans-unit id="Error preparing message" xml:space="preserve">
<source>Error preparing message</source> <source>Error preparing message</source>
<target>Błąd przygotowania wiadomości</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Error: %@" xml:space="preserve"> <trans-unit id="Error: %@" xml:space="preserve">
<source>Error: %@</source> <source>Error: %@</source>
<target>Błąd: %@</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="File error" xml:space="preserve"> <trans-unit id="File error" xml:space="preserve">
<source>File error</source> <source>File error</source>
<target>Błąd pliku</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Incompatible database version" xml:space="preserve"> <trans-unit id="Incompatible database version" xml:space="preserve">
<source>Incompatible database version</source> <source>Incompatible database version</source>
<target>Niekompatybilna wersja bazy danych</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Invalid migration confirmation" xml:space="preserve"> <trans-unit id="Invalid migration confirmation" xml:space="preserve">
<source>Invalid migration confirmation</source> <source>Invalid migration confirmation</source>
<target>Nieprawidłowe potwierdzenie migracji</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Keychain error" xml:space="preserve"> <trans-unit id="Keychain error" xml:space="preserve">
<source>Keychain error</source> <source>Keychain error</source>
<target>Błąd pęku kluczy</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Large file!" xml:space="preserve"> <trans-unit id="Large file!" xml:space="preserve">
<source>Large file!</source> <source>Large file!</source>
<target>Duży plik!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="No active profile" xml:space="preserve"> <trans-unit id="No active profile" xml:space="preserve">
<source>No active profile</source> <source>No active profile</source>
<target>Brak aktywnego profilu</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Ok" xml:space="preserve"> <trans-unit id="Ok" xml:space="preserve">
<source>Ok</source> <source>Ok</source>
<target>Ok</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Open the app to downgrade the database." xml:space="preserve"> <trans-unit id="Open the app to downgrade the database." xml:space="preserve">
<source>Open the app to downgrade the database.</source> <source>Open the app to downgrade the database.</source>
<target>Otwórz aplikację aby obniżyć wersję bazy danych.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Open the app to upgrade the database." xml:space="preserve"> <trans-unit id="Open the app to upgrade the database." xml:space="preserve">
<source>Open the app to upgrade the database.</source> <source>Open the app to upgrade the database.</source>
<target>Otwórz aplikację aby zaktualizować bazę danych.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Passphrase" xml:space="preserve"> <trans-unit id="Passphrase" xml:space="preserve">
<source>Passphrase</source> <source>Passphrase</source>
<target>Hasło</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Please create a profile in the SimpleX app" xml:space="preserve"> <trans-unit id="Please create a profile in the SimpleX app" xml:space="preserve">
<source>Please create a profile in the SimpleX app</source> <source>Please create a profile in the SimpleX app</source>
<target>Proszę utworzyć profil w aplikacji SimpleX</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve"> <trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
<source>Selected chat preferences prohibit this message.</source> <source>Selected chat preferences prohibit this message.</source>
<target>Wybrane preferencje czatu zabraniają tej wiadomości.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Sending a message takes longer than expected." xml:space="preserve"> <trans-unit id="Sending a message takes longer than expected." xml:space="preserve">
<source>Sending a message takes longer than expected.</source> <source>Sending a message takes longer than expected.</source>
<target>Wysłanie wiadomości trwa dłużej niż oczekiwano.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Sending message…" xml:space="preserve"> <trans-unit id="Sending message…" xml:space="preserve">
<source>Sending message…</source> <source>Sending message…</source>
<target>Wysyłanie wiadomości…</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Share" xml:space="preserve"> <trans-unit id="Share" xml:space="preserve">
<source>Share</source> <source>Share</source>
<target>Udostępnij</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Slow network?" xml:space="preserve"> <trans-unit id="Slow network?" xml:space="preserve">
<source>Slow network?</source> <source>Slow network?</source>
<target>Wolna sieć?</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Unknown database error: %@" xml:space="preserve"> <trans-unit id="Unknown database error: %@" xml:space="preserve">
<source>Unknown database error: %@</source> <source>Unknown database error: %@</source>
<target>Nieznany błąd bazy danych: %@</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Unsupported format" xml:space="preserve"> <trans-unit id="Unsupported format" xml:space="preserve">
<source>Unsupported format</source> <source>Unsupported format</source>
<target>Niewspierany format</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Wait" xml:space="preserve"> <trans-unit id="Wait" xml:space="preserve">
<source>Wait</source> <source>Wait</source>
<target>Czekaj</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Wrong database passphrase" xml:space="preserve"> <trans-unit id="Wrong database passphrase" xml:space="preserve">
<source>Wrong database passphrase</source> <source>Wrong database passphrase</source>
<target>Nieprawidłowe hasło bazy danych</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="You can allow sharing in Privacy &amp; Security / SimpleX Lock settings." xml:space="preserve"> <trans-unit id="You can allow sharing in Privacy &amp; Security / SimpleX Lock settings." xml:space="preserve">
<source>You can allow sharing in Privacy &amp; Security / SimpleX Lock settings.</source> <source>You can allow sharing in Privacy &amp; Security / SimpleX Lock settings.</source>
<target>Możesz zezwolić na udostępnianie w ustawieniach Prywatność i bezpieczeństwo / Blokada SimpleX.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
</body> </body>
@@ -5234,6 +5234,274 @@ Isso pode acontecer por causa de algum bug ou quando a conexão está comprometi
<target state="translated">%1$@ em %2$@:</target> <target state="translated">%1$@ em %2$@:</target>
<note>copied message info, &lt;sender&gt; at &lt;time&gt;</note> <note>copied message info, &lt;sender&gt; at &lt;time&gt;</note>
</trans-unit> </trans-unit>
<trans-unit id="Allow your contacts to irreversibly delete sent messages. (24 hours)" xml:space="preserve" approved="no">
<source>Allow your contacts to irreversibly delete sent messages. (24 hours)</source>
<target state="translated">Permitir que seus contatos deletem mensagens enviadas de maneira irreversível. (24 horas)</target>
</trans-unit>
<trans-unit id="%@ downloaded" xml:space="preserve" approved="no">
<source>%@ downloaded</source>
<target state="translated">baixado</target>
</trans-unit>
<trans-unit id="%@ uploaded" xml:space="preserve" approved="no">
<source>%@ uploaded</source>
<target state="translated">transferido</target>
</trans-unit>
<trans-unit id="A new random profile will be shared." xml:space="preserve" approved="no">
<source>A new random profile will be shared.</source>
<target state="translated">Um novo perfil aleatório será compartilhado.</target>
</trans-unit>
<trans-unit id="Camera not available" xml:space="preserve" approved="no">
<source>Camera not available</source>
<target state="translated">Câmera indisponível</target>
</trans-unit>
<trans-unit id="Admins can block a member for all." xml:space="preserve" approved="no">
<source>Admins can block a member for all.</source>
<target state="translated">Administradores podem bloquear um membro para todos.</target>
</trans-unit>
<trans-unit id="Allow to irreversibly delete sent messages. (24 hours)" xml:space="preserve" approved="no">
<source>Allow to irreversibly delete sent messages. (24 hours)</source>
<target state="translated">Permitir que mensagens enviadas sejam deletadas de maneira irreversível. (24 horas)</target>
</trans-unit>
<trans-unit id="Apply" xml:space="preserve" approved="no">
<source>Apply</source>
<target state="translated">Aplicar</target>
</trans-unit>
<trans-unit id="Accent" xml:space="preserve" approved="no">
<source>Accent</source>
<target state="translated">Esquema</target>
</trans-unit>
<trans-unit id="Accept connection request?" xml:space="preserve" approved="no">
<source>Accept connection request?</source>
<target state="translated">Aceitar solicitação de conexão?</target>
</trans-unit>
<trans-unit id="Active connections" xml:space="preserve" approved="no">
<source>Active connections</source>
<target state="translated">Conexões ativas</target>
</trans-unit>
<trans-unit id="Add contact" xml:space="preserve" approved="no">
<source>Add contact</source>
<target state="translated">Adicionar contato</target>
</trans-unit>
<trans-unit id="Additional accent" xml:space="preserve" approved="no">
<source>Additional accent</source>
<target state="translated">Esquema adicional</target>
</trans-unit>
<trans-unit id="All new messages from %@ will be hidden!" xml:space="preserve" approved="no">
<source>All new messages from %@ will be hidden!</source>
<target state="translated">Todas as novas mensagens de %@ serão ocultas!</target>
</trans-unit>
<trans-unit id="All profiles" xml:space="preserve" approved="no">
<source>All profiles</source>
<target state="translated">Todos perfis</target>
</trans-unit>
<trans-unit id="Allow calls?" xml:space="preserve" approved="no">
<source>Allow calls?</source>
<target state="translated">Permitir chamadas?</target>
</trans-unit>
<trans-unit id="Archive contacts to chat later." xml:space="preserve" approved="no">
<source>Archive contacts to chat later.</source>
<target state="translated">Arquivar contatos para conversar depois.</target>
</trans-unit>
<trans-unit id="Blur media" xml:space="preserve" approved="no">
<source>Blur media</source>
<target state="translated">Censurar mídia</target>
</trans-unit>
<trans-unit id="Calls prohibited!" xml:space="preserve" approved="no">
<source>Calls prohibited!</source>
<target state="translated">Chamadas proibidas!</target>
</trans-unit>
<trans-unit id="Can't call contact" xml:space="preserve" approved="no">
<source>Can't call contact</source>
<target state="translated">Não foi possível ligar para o contato</target>
</trans-unit>
<trans-unit id="%lld messages marked deleted" xml:space="preserve" approved="no">
<source>%lld messages marked deleted</source>
<target state="translated">mensagens deletadas</target>
</trans-unit>
<trans-unit id="0 sec" xml:space="preserve" approved="no">
<source>0 sec</source>
<target state="translated">0 seg</target>
</trans-unit>
<trans-unit id="%lld messages blocked" xml:space="preserve" approved="no">
<source>%lld messages blocked</source>
<target state="translated">mensagens bloqueadas</target>
</trans-unit>
<trans-unit id="%lld messages blocked by admin" xml:space="preserve" approved="no">
<source>%lld messages blocked by admin</source>
<target state="translated">mensagens bloqueadas pelo administrador</target>
</trans-unit>
<trans-unit id="**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." xml:space="preserve" approved="no">
<source>**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection.</source>
<target state="translated">**Nota**: usar o mesmo banco de dados em dois dispositivos irá quebrar a desencriptação das mensagens de suas conexões como uma medida de segurança.</target>
</trans-unit>
<trans-unit id="- more stable message delivery.&#10;- a bit better groups.&#10;- and more!" xml:space="preserve" approved="no">
<source>- more stable message delivery.
- a bit better groups.
- and more!</source>
<target state="translated">- entrega de mensagens mais estável.
- grupos melhorados.
- e muito mais!</target>
</trans-unit>
<trans-unit id="All messages will be deleted - this cannot be undone!" xml:space="preserve" approved="no">
<source>All messages will be deleted - this cannot be undone!</source>
<target state="translated">Todas as mensagens serão deletadas - isto não pode ser desfeito!</target>
</trans-unit>
<trans-unit id="Allow to send files and media." xml:space="preserve" approved="no">
<source>Allow to send files and media.</source>
<target state="translated">Permitir o envio de arquivos e mídia.</target>
</trans-unit>
<trans-unit id="Allow to send SimpleX links." xml:space="preserve" approved="no">
<source>Allow to send SimpleX links.</source>
<target state="translated">Permitir envio de links SimpleX.</target>
</trans-unit>
<trans-unit id="Block for all" xml:space="preserve" approved="no">
<source>Block for all</source>
<target state="translated">Bloquear para todos</target>
</trans-unit>
<trans-unit id="Block member" xml:space="preserve" approved="no">
<source>Block member</source>
<target state="translated">Bloquear membro</target>
</trans-unit>
<trans-unit id="Blocked by admin" xml:space="preserve" approved="no">
<source>Blocked by admin</source>
<target state="translated">Bloqueado por um administrador</target>
</trans-unit>
<trans-unit id="Block group members" xml:space="preserve" approved="no">
<source>Block group members</source>
<target state="translated">Bloquear membros de grupo</target>
</trans-unit>
<trans-unit id="Block member for all?" xml:space="preserve" approved="no">
<source>Block member for all?</source>
<target state="translated">Bloquear membro para todos?</target>
</trans-unit>
<trans-unit id="Block member?" xml:space="preserve" approved="no">
<source>Block member?</source>
<target state="translated">Bloquear membro?</target>
</trans-unit>
<trans-unit id="Both you and your contact can irreversibly delete sent messages. (24 hours)" xml:space="preserve" approved="no">
<source>Both you and your contact can irreversibly delete sent messages. (24 hours)</source>
<target state="translated">Você e seu contato podem apagar mensagens enviadas de maneira irreversível. (24 horas)</target>
</trans-unit>
<trans-unit id="Can't call member" xml:space="preserve" approved="no">
<source>Can't call member</source>
<target state="translated">Não foi possível ligar para este membro</target>
</trans-unit>
<trans-unit id="Can't message member" xml:space="preserve" approved="no">
<source>Can't message member</source>
<target state="translated">Não foi possível enviar mensagem para este membro</target>
</trans-unit>
<trans-unit id="Cancel migration" xml:space="preserve" approved="no">
<source>Cancel migration</source>
<target state="translated">Cancelar migração</target>
</trans-unit>
<trans-unit id="Abort" xml:space="preserve" approved="no">
<source>Abort</source>
<target state="translated">Abortar</target>
</trans-unit>
<trans-unit id="Abort changing address" xml:space="preserve" approved="no">
<source>Abort changing address</source>
<target state="translated">Abortar troca de endereço</target>
</trans-unit>
<trans-unit id="Abort changing address?" xml:space="preserve" approved="no">
<source>Abort changing address?</source>
<target state="translated">Abortar troca de endereço?</target>
</trans-unit>
<trans-unit id="- optionally notify deleted contacts.&#10;- profile names with spaces.&#10;- and more!" xml:space="preserve" approved="no">
<source>- optionally notify deleted contacts.
- profile names with spaces.
- and more!</source>
<target state="translated">- notificar contatos apagados de maneira opcional.
- nome de perfil com espaços.
- e muito mais!</target>
</trans-unit>
<trans-unit id="Allow sharing" xml:space="preserve" approved="no">
<source>Allow sharing</source>
<target state="translated">Permitir compartilhamento</target>
</trans-unit>
<trans-unit id="Block" xml:space="preserve" approved="no">
<source>Block</source>
<target state="translated">Bloquear</target>
</trans-unit>
<trans-unit id="Additional accent 2" xml:space="preserve" approved="no">
<source>Additional accent 2</source>
<target state="translated">Esquema adicional 2</target>
</trans-unit>
<trans-unit id="Address change will be aborted. Old receiving address will be used." xml:space="preserve" approved="no">
<source>Address change will be aborted. Old receiving address will be used.</source>
<target state="translated">Alteração de endereço será abortada. O endereço antigo será utilizado.</target>
</trans-unit>
<trans-unit id="Advanced settings" xml:space="preserve" approved="no">
<source>Advanced settings</source>
<target state="translated">Configurações avançadas</target>
</trans-unit>
<trans-unit id="All data is private to your device." xml:space="preserve" approved="no">
<source>All data is private to your device.</source>
<target state="translated">Toda informação é privada em seu dispositivo.</target>
</trans-unit>
<trans-unit id="All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." xml:space="preserve" approved="no">
<source>All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays.</source>
<target state="translated">Todos os seus contatos, conversas e arquivos serão encriptados e enviados em pedaços para nós XFTP.</target>
</trans-unit>
<trans-unit id="Allow irreversible message deletion only if your contact allows it to you. (24 hours)" xml:space="preserve" approved="no">
<source>Allow irreversible message deletion only if your contact allows it to you. (24 hours)</source>
<target state="translated">Permitir deletar mensagens de maneira irreversível apenas se seu contato permitir para você. (24 horas)</target>
</trans-unit>
<trans-unit id="Already connecting!" xml:space="preserve" approved="no">
<source>Already connecting!</source>
<target state="translated">Já está conectando!</target>
</trans-unit>
<trans-unit id="Already joining the group!" xml:space="preserve" approved="no">
<source>Already joining the group!</source>
<target state="translated">Já está entrando no grupo!</target>
</trans-unit>
<trans-unit id="Always use private routing." xml:space="preserve" approved="no">
<source>Always use private routing.</source>
<target state="translated">Sempre use rotas privadas.</target>
</trans-unit>
<trans-unit id="Apply to" xml:space="preserve" approved="no">
<source>Apply to</source>
<target state="translated">Aplicar em</target>
</trans-unit>
<trans-unit id="Archiving database" xml:space="preserve" approved="no">
<source>Archiving database</source>
<target state="translated">Arquivando banco de dados</target>
</trans-unit>
<trans-unit id="Black" xml:space="preserve" approved="no">
<source>Black</source>
<target state="translated">Preto</target>
</trans-unit>
<trans-unit id="Cannot forward message" xml:space="preserve" approved="no">
<source>Cannot forward message</source>
<target state="translated">Não é possível encaminhar mensagem</target>
</trans-unit>
<trans-unit id="(new)" xml:space="preserve" approved="no">
<source>(new)</source>
<target state="translated">(novo)</target>
</trans-unit>
<trans-unit id="(this device v%@)" xml:space="preserve" approved="no">
<source>(this device v%@)</source>
<target state="translated">este dispositivo</target>
</trans-unit>
<trans-unit id="**Add contact**: to create a new invitation link, or connect via a link you received." xml:space="preserve" approved="no">
<source>**Add contact**: to create a new invitation link, or connect via a link you received.</source>
<target state="translated">**Adicionar contato**: criar um novo link de convite ou conectar via um link que você recebeu.</target>
</trans-unit>
<trans-unit id="**Create group**: to create a new group." xml:space="preserve" approved="no">
<source>**Create group**: to create a new group.</source>
<target state="translated">**Criar grupo**: criar um novo grupo.</target>
</trans-unit>
<trans-unit id="**Warning**: the archive will be removed." xml:space="preserve" approved="no">
<source>**Warning**: the archive will be removed.</source>
<target state="translated">**Aviso**: o arquivo será removido.</target>
</trans-unit>
<trans-unit id="A few more things" xml:space="preserve" approved="no">
<source>A few more things</source>
<target state="translated">E mais algumas coisas</target>
</trans-unit>
<trans-unit id="Archived contacts" xml:space="preserve" approved="no">
<source>Archived contacts</source>
<target state="translated">Contatos arquivados</target>
</trans-unit>
</body> </body>
</file> </file>
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="pt-BR" datatype="plaintext"> <file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="pt-BR" datatype="plaintext">
@@ -1418,7 +1418,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Color chats with the new themes." xml:space="preserve"> <trans-unit id="Color chats with the new themes." xml:space="preserve">
<source>Color chats with the new themes.</source> <source>Color chats with the new themes.</source>
<target>Добавьте цвета к чатам в настройках тем.</target> <target>Добавьте цвета к чатам в настройках.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Color mode" xml:space="preserve"> <trans-unit id="Color mode" xml:space="preserve">
@@ -2176,7 +2176,7 @@ This is your own one-time link!</source>
</trans-unit> </trans-unit>
<trans-unit id="Delete up to 20 messages at once." xml:space="preserve"> <trans-unit id="Delete up to 20 messages at once." xml:space="preserve">
<source>Delete up to 20 messages at once.</source> <source>Delete up to 20 messages at once.</source>
<target>Удаляйте до 20 сообщений одновременно.</target> <target>Удаляйте до 20 сообщений за раз.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Delete user profile?" xml:space="preserve"> <trans-unit id="Delete user profile?" xml:space="preserve">
@@ -4988,7 +4988,7 @@ Error: %@</source>
</trans-unit> </trans-unit>
<trans-unit id="Private message routing 🚀" xml:space="preserve"> <trans-unit id="Private message routing 🚀" xml:space="preserve">
<source>Private message routing 🚀</source> <source>Private message routing 🚀</source>
<target>Конфиденциальная доставка сообщений 🚀</target> <target>Конфиденциальная доставка 🚀</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Private notes" xml:space="preserve"> <trans-unit id="Private notes" xml:space="preserve">
File diff suppressed because it is too large Load Diff
@@ -314,7 +314,7 @@
</trans-unit> </trans-unit>
<trans-unit id="About SimpleX Chat" xml:space="preserve" approved="no"> <trans-unit id="About SimpleX Chat" xml:space="preserve" approved="no">
<source>About SimpleX Chat</source> <source>About SimpleX Chat</source>
<target state="translated">關於 SimpleX 對話</target> <target state="translated">關於 SimpleX Chat</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Accent color" xml:space="preserve" approved="no"> <trans-unit id="Accent color" xml:space="preserve" approved="no">
@@ -445,7 +445,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Allow your contacts to send disappearing messages." xml:space="preserve" approved="no"> <trans-unit id="Allow your contacts to send disappearing messages." xml:space="preserve" approved="no">
<source>Allow your contacts to send disappearing messages.</source> <source>Allow your contacts to send disappearing messages.</source>
<target state="translated">允許的聯絡人傳送自動銷毀的訊息。</target> <target state="translated">允許的聯絡人傳送限時訊息。</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Allow your contacts to send voice messages." xml:space="preserve" approved="no"> <trans-unit id="Allow your contacts to send voice messages." xml:space="preserve" approved="no">
@@ -5898,6 +5898,230 @@ It can happen because of some bug or when the connection is compromised.</source
<target state="translated">%@ 和 %@ 已連接</target> <target state="translated">%@ 和 %@ 已連接</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="%@ downloaded" xml:space="preserve" approved="no">
<source>%@ downloaded</source>
<target state="translated">%@ 下載</target>
</trans-unit>
<trans-unit id="%@ uploaded" xml:space="preserve" approved="no">
<source>%@ uploaded</source>
<target state="translated">%@ 上傳</target>
</trans-unit>
<trans-unit id="Abort" xml:space="preserve" approved="no">
<source>Abort</source>
<target state="translated">中止</target>
</trans-unit>
<trans-unit id="**Create group**: to create a new group." xml:space="preserve" approved="no">
<source>**Create group**: to create a new group.</source>
<target state="translated">**創建群組**: 創建一個新的群組。</target>
</trans-unit>
<trans-unit id="Abort changing address" xml:space="preserve" approved="no">
<source>Abort changing address</source>
<target state="translated">中止更改地址</target>
</trans-unit>
<trans-unit id="Accept connection request?" xml:space="preserve" approved="no">
<source>Accept connection request?</source>
<target state="translated">接受連線請求?</target>
</trans-unit>
<trans-unit id="Camera not available" xml:space="preserve" approved="no">
<source>Camera not available</source>
<target state="translated">相機不可用</target>
</trans-unit>
<trans-unit id="All messages will be deleted - this cannot be undone!" xml:space="preserve" approved="no">
<source>All messages will be deleted - this cannot be undone!</source>
<target state="translated">所有訊息都將被刪除 - 這不能還原!</target>
</trans-unit>
<trans-unit id="Allow irreversible message deletion only if your contact allows it to you. (24 hours)" xml:space="preserve" approved="no">
<source>Allow irreversible message deletion only if your contact allows it to you. (24 hours)</source>
<target state="translated">只有你的聯絡人允許的情況下,才允許不可逆地將訊息刪除。(24小時)</target>
</trans-unit>
<trans-unit id="Allow to irreversibly delete sent messages. (24 hours)" xml:space="preserve" approved="no">
<source>Allow to irreversibly delete sent messages. (24 hours)</source>
<target state="translated">允許將不可撤銷的訊息刪除。(24小時)</target>
</trans-unit>
<trans-unit id="Allow your contacts to irreversibly delete sent messages. (24 hours)" xml:space="preserve" approved="no">
<source>Allow your contacts to irreversibly delete sent messages. (24 hours)</source>
<target state="translated">允許您的聯絡人不可復原地刪除已傳送的訊息。(24小時)</target>
</trans-unit>
<trans-unit id="Bad desktop address" xml:space="preserve" approved="no">
<source>Bad desktop address</source>
<target state="translated">無效的桌面地址</target>
</trans-unit>
<trans-unit id="Error decrypting file" xml:space="preserve" approved="no">
<source>Error decrypting file</source>
<target state="translated">解密檔案時出錯</target>
</trans-unit>
<trans-unit id="Add contact" xml:space="preserve" approved="no">
<source>Add contact</source>
<target state="translated">新增聯絡人</target>
</trans-unit>
<trans-unit id="Advanced settings" xml:space="preserve" approved="no">
<source>Advanced settings</source>
<target state="translated">進階設定</target>
</trans-unit>
<trans-unit id="Allow calls?" xml:space="preserve" approved="no">
<source>Allow calls?</source>
<target state="translated">允許通話?</target>
</trans-unit>
<trans-unit id="Allow to send files and media." xml:space="preserve" approved="no">
<source>Allow to send files and media.</source>
<target state="translated">允許傳送檔案和媒體。</target>
</trans-unit>
<trans-unit id="Already joining the group!" xml:space="preserve" approved="no">
<source>Already joining the group!</source>
<target state="translated">已加入群組!</target>
</trans-unit>
<trans-unit id="App data migration" xml:space="preserve" approved="no">
<source>App data migration</source>
<target state="translated">應用資料轉移</target>
</trans-unit>
<trans-unit id="Apply" xml:space="preserve" approved="no">
<source>Apply</source>
<target state="translated">應用</target>
</trans-unit>
<trans-unit id="Apply to" xml:space="preserve" approved="no">
<source>Apply to</source>
<target state="translated">應用到</target>
</trans-unit>
<trans-unit id="Archive and upload" xml:space="preserve" approved="no">
<source>Archive and upload</source>
<target state="translated">儲存並上傳</target>
</trans-unit>
<trans-unit id="Block" xml:space="preserve" approved="no">
<source>Block</source>
<target state="translated">封鎖</target>
</trans-unit>
<trans-unit id="Block group members" xml:space="preserve" approved="no">
<source>Block group members</source>
<target state="translated">封鎖群組成員</target>
</trans-unit>
<trans-unit id="Block member" xml:space="preserve" approved="no">
<source>Block member</source>
<target state="translated">封鎖成員</target>
</trans-unit>
<trans-unit id="Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" xml:space="preserve" approved="no">
<source>Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!</source>
<target state="translated">保加利亞語、芬蘭語、泰語和烏克蘭語——感謝使用者們和[Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)</target>
</trans-unit>
<trans-unit id="Can't call member" xml:space="preserve" approved="no">
<source>Can't call member</source>
<target state="translated">無法與成員通話</target>
</trans-unit>
<trans-unit id="Can't message member" xml:space="preserve" approved="no">
<source>Can't message member</source>
<target state="translated">無法傳送訊息給成員</target>
</trans-unit>
<trans-unit id="Cancel migration" xml:space="preserve" approved="no">
<source>Cancel migration</source>
<target state="translated">取消遷移</target>
</trans-unit>
<trans-unit id="Chat database exported" xml:space="preserve" approved="no">
<source>Chat database exported</source>
<target state="translated">導出聊天數據庫</target>
</trans-unit>
<trans-unit id="0 sec" xml:space="preserve" approved="no">
<source>0 sec</source>
<target state="translated">0 秒</target>
</trans-unit>
<trans-unit id="All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." xml:space="preserve" approved="no">
<source>All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays.</source>
<target state="translated">你的所有聯絡人、對話和文件將被安全加密並切塊上傳到設置的 XFTP 中繼。</target>
</trans-unit>
<trans-unit id="Address change will be aborted. Old receiving address will be used." xml:space="preserve" approved="no">
<source>Address change will be aborted. Old receiving address will be used.</source>
<target state="translated">將取消地址更改。將使用舊聯絡地址。</target>
</trans-unit>
<trans-unit id="Archiving database" xml:space="preserve" approved="no">
<source>Archiving database</source>
<target state="translated">正在儲存資料庫</target>
</trans-unit>
<trans-unit id="Cellular" xml:space="preserve" approved="no">
<source>Cellular</source>
<target state="translated">行動網路</target>
</trans-unit>
<trans-unit id="%@, %@ and %lld members" xml:space="preserve" approved="no">
<source>%@, %@ and %lld members</source>
<target state="translated">%@, %@ 和 %lld 成員</target>
</trans-unit>
<trans-unit id="%lld messages marked deleted" xml:space="preserve" approved="no">
<source>%lld messages marked deleted</source>
<target state="translated">%lld 條訊息已刪除</target>
</trans-unit>
<trans-unit id="Already connecting!" xml:space="preserve" approved="no">
<source>Already connecting!</source>
<target state="translated">已連接!</target>
</trans-unit>
<trans-unit id="Block member?" xml:space="preserve" approved="no">
<source>Block member?</source>
<target state="translated">封鎖成員?</target>
</trans-unit>
<trans-unit id="(new)" xml:space="preserve" approved="no">
<source>(new)</source>
<target state="translated">(新)</target>
</trans-unit>
<trans-unit id="%@, %@ and %lld other members connected" xml:space="preserve" approved="no">
<source>%@, %@ and %lld other members connected</source>
<target state="translated">%@, %@ 和 %lld 個成員已連接</target>
</trans-unit>
<trans-unit id="A few more things" xml:space="preserve" approved="no">
<source>A few more things</source>
<target state="translated">其他</target>
</trans-unit>
<trans-unit id="Show last messages" xml:space="preserve" approved="no">
<source>Show last messages</source>
<target state="translated">顯示最新的訊息</target>
</trans-unit>
<trans-unit id="App encrypts new local files (except videos)." xml:space="preserve" approved="no">
<source>App encrypts new local files (except videos).</source>
<target state="translated">應用程式將為新的本機文件(影片除外)加密。</target>
</trans-unit>
<trans-unit id="Better groups" xml:space="preserve" approved="no">
<source>Better groups</source>
<target state="translated">更加的群組</target>
</trans-unit>
<trans-unit id="%lld new interface languages" xml:space="preserve" approved="no">
<source>%lld new interface languages</source>
<target state="translated">%lld 種新的介面語言</target>
</trans-unit>
<trans-unit id="Blocked by admin" xml:space="preserve" approved="no">
<source>Blocked by admin</source>
<target state="translated">由管理員封鎖</target>
</trans-unit>
<trans-unit id="Both you and your contact can irreversibly delete sent messages. (24 hours)" xml:space="preserve" approved="no">
<source>Both you and your contact can irreversibly delete sent messages. (24 hours)</source>
<target state="translated">您與您的聯絡人都可以不可復原地删除已傳送的訊息。(24小時)</target>
</trans-unit>
<trans-unit id="Encrypt local files" xml:space="preserve" approved="no">
<source>Encrypt local files</source>
<target state="translated">加密本機檔案</target>
</trans-unit>
<trans-unit id="- more stable message delivery.&#10;- a bit better groups.&#10;- and more!" xml:space="preserve" approved="no">
<source>- more stable message delivery.
- a bit better groups.
- and more!</source>
<target state="translated">- 更穩定的傳送!
- 更好的社群!
- 以及更多!</target>
</trans-unit>
<trans-unit id="- optionally notify deleted contacts.&#10;- profile names with spaces.&#10;- and more!" xml:space="preserve" approved="no">
<source>- optionally notify deleted contacts.
- profile names with spaces.
- and more!</source>
<target state="translated">- 可選擇通知已刪除的聯絡人
- 帶空格的共人資料名稱。
-以及更多!</target>
</trans-unit>
<trans-unit id="Abort changing address?" xml:space="preserve" approved="no">
<source>Abort changing address?</source>
<target state="translated">中止更改地址?</target>
</trans-unit>
<trans-unit id="Allow to send SimpleX links." xml:space="preserve" approved="no">
<source>Allow to send SimpleX links.</source>
<target state="translated">允許傳送 SimpleX 連結。</target>
</trans-unit>
<trans-unit id="Background" xml:space="preserve" approved="no">
<source>Background</source>
<target state="translated">後台</target>
</trans-unit>
</body> </body>
</file> </file>
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="zh-Hant" datatype="plaintext"> <file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="zh-Hant" datatype="plaintext">
@@ -339,7 +339,9 @@ class NotificationService: UNNotificationServiceExtension {
CXProvider.reportNewIncomingVoIPPushPayload([ CXProvider.reportNewIncomingVoIPPushPayload([
"displayName": invitation.contact.displayName, "displayName": invitation.contact.displayName,
"contactId": invitation.contact.id, "contactId": invitation.contact.id,
"media": invitation.callType.media.rawValue "callUUID": invitation.callUUID ?? "",
"media": invitation.callType.media.rawValue,
"callTs": invitation.callTs.timeIntervalSince1970
]) { error in ]) { error in
logger.debug("reportNewIncomingVoIPPushPayload result: \(error)") logger.debug("reportNewIncomingVoIPPushPayload result: \(error)")
deliver(error == nil ? nil : createCallInvitationNtf(invitation)) deliver(error == nil ? nil : createCallInvitationNtf(invitation))
@@ -1,7 +1,9 @@
/* /* Bundle display name */
InfoPlist.strings "CFBundleDisplayName" = "SimpleX SE";
SimpleX
/* Bundle name */
"CFBundleName" = "SimpleX SE";
/* Copyright (human-readable) */
"NSHumanReadableCopyright" = "Copyright © 2024 SimpleX Chat. Wszelkie prawa zastrzeżone.";
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -1,7 +1,111 @@
/* /* No comment provided by engineer. */
Localizable.strings "%@" = "%@";
SimpleX
/* No comment provided by engineer. */
"App is locked!" = "Aplikacja zablokowana!";
/* No comment provided by engineer. */
"Cancel" = "Anuluj";
/* No comment provided by engineer. */
"Cannot access keychain to save database password" = "Nie można uzyskać dostępu do pęku kluczy aby zapisać hasło do bazy danych";
/* No comment provided by engineer. */
"Cannot forward message" = "Nie można przekazać wiadomości";
/* No comment provided by engineer. */
"Comment" = "Komentarz";
/* No comment provided by engineer. */
"Currently maximum supported file size is %@." = "Obecnie maksymalny obsługiwany rozmiar pliku to %@.";
/* No comment provided by engineer. */
"Database downgrade required" = "Wymagane obniżenie wersji bazy danych";
/* No comment provided by engineer. */
"Database encrypted!" = "Baza danych zaszyfrowana!";
/* No comment provided by engineer. */
"Database error" = "Błąd bazy danych";
/* No comment provided by engineer. */
"Database passphrase is different from saved in the keychain." = "Hasło bazy danych jest inne niż zapisane w pęku kluczy.";
/* No comment provided by engineer. */
"Database passphrase is required to open chat." = "Hasło do bazy danych jest wymagane do otwarcia czatu.";
/* No comment provided by engineer. */
"Database upgrade required" = "Wymagana aktualizacja bazy danych";
/* No comment provided by engineer. */
"Error preparing file" = "Błąd przygotowania pliku";
/* No comment provided by engineer. */
"Error preparing message" = "Błąd przygotowania wiadomości";
/* No comment provided by engineer. */
"Error: %@" = "Błąd: %@";
/* No comment provided by engineer. */
"File error" = "Błąd pliku";
/* No comment provided by engineer. */
"Incompatible database version" = "Niekompatybilna wersja bazy danych";
/* No comment provided by engineer. */
"Invalid migration confirmation" = "Nieprawidłowe potwierdzenie migracji";
/* No comment provided by engineer. */
"Keychain error" = "Błąd pęku kluczy";
/* No comment provided by engineer. */
"Large file!" = "Duży plik!";
/* No comment provided by engineer. */
"No active profile" = "Brak aktywnego profilu";
/* No comment provided by engineer. */
"Ok" = "Ok";
/* No comment provided by engineer. */
"Open the app to downgrade the database." = "Otwórz aplikację aby obniżyć wersję bazy danych.";
/* No comment provided by engineer. */
"Open the app to upgrade the database." = "Otwórz aplikację aby zaktualizować bazę danych.";
/* No comment provided by engineer. */
"Passphrase" = "Hasło";
/* No comment provided by engineer. */
"Please create a profile in the SimpleX app" = "Proszę utworzyć profil w aplikacji SimpleX";
/* No comment provided by engineer. */
"Selected chat preferences prohibit this message." = "Wybrane preferencje czatu zabraniają tej wiadomości.";
/* No comment provided by engineer. */
"Sending a message takes longer than expected." = "Wysłanie wiadomości trwa dłużej niż oczekiwano.";
/* No comment provided by engineer. */
"Sending message…" = "Wysyłanie wiadomości…";
/* No comment provided by engineer. */
"Share" = "Udostępnij";
/* No comment provided by engineer. */
"Slow network?" = "Wolna sieć?";
/* No comment provided by engineer. */
"Unknown database error: %@" = "Nieznany błąd bazy danych: %@";
/* No comment provided by engineer. */
"Unsupported format" = "Niewspierany format";
/* No comment provided by engineer. */
"Wait" = "Czekaj";
/* No comment provided by engineer. */
"Wrong database passphrase" = "Nieprawidłowe hasło bazy danych";
/* No comment provided by engineer. */
"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "Możesz zezwolić na udostępnianie w ustawieniach Prywatność i bezpieczeństwo / Blokada SimpleX.";
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -1,7 +1,9 @@
/* /* Bundle display name */
InfoPlist.strings "CFBundleDisplayName" = "SimpleX SE";
SimpleX
/* Bundle name */
"CFBundleName" = "SimpleX SE";
/* Copyright (human-readable) */
"NSHumanReadableCopyright" = "Copyright © 2024 SimpleX Chat. Всі права захищені.";
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -1,7 +1,111 @@
/* /* No comment provided by engineer. */
Localizable.strings "%@" = "%@";
SimpleX
/* No comment provided by engineer. */
"App is locked!" = "Додаток заблоковано!";
/* No comment provided by engineer. */
"Cancel" = "Скасувати";
/* No comment provided by engineer. */
"Cannot access keychain to save database password" = "Не вдається отримати доступ до зв'язки ключів для збереження пароля до бази даних";
/* No comment provided by engineer. */
"Cannot forward message" = "Неможливо переслати повідомлення";
/* No comment provided by engineer. */
"Comment" = "Коментар";
/* No comment provided by engineer. */
"Currently maximum supported file size is %@." = "Наразі максимальний підтримуваний розмір файлу - %@.";
/* No comment provided by engineer. */
"Database downgrade required" = "Потрібне оновлення бази даних";
/* No comment provided by engineer. */
"Database encrypted!" = "База даних зашифрована!";
/* No comment provided by engineer. */
"Database error" = "Помилка в базі даних";
/* No comment provided by engineer. */
"Database passphrase is different from saved in the keychain." = "Парольна фраза бази даних відрізняється від збереженої у в’язці ключів.";
/* No comment provided by engineer. */
"Database passphrase is required to open chat." = "Для відкриття чату потрібно ввести пароль до бази даних.";
/* No comment provided by engineer. */
"Database upgrade required" = "Потрібне оновлення бази даних";
/* No comment provided by engineer. */
"Error preparing file" = "Помилка підготовки файлу";
/* No comment provided by engineer. */
"Error preparing message" = "Повідомлення про підготовку до помилки";
/* No comment provided by engineer. */
"Error: %@" = "Помилка: %@";
/* No comment provided by engineer. */
"File error" = "Помилка файлу";
/* No comment provided by engineer. */
"Incompatible database version" = "Несумісна версія бази даних";
/* No comment provided by engineer. */
"Invalid migration confirmation" = "Недійсне підтвердження міграції";
/* No comment provided by engineer. */
"Keychain error" = "Помилка зв'язки ключів";
/* No comment provided by engineer. */
"Large file!" = "Великий файл!";
/* No comment provided by engineer. */
"No active profile" = "Немає активного профілю";
/* No comment provided by engineer. */
"Ok" = "Гаразд";
/* No comment provided by engineer. */
"Open the app to downgrade the database." = "Відкрийте програму, щоб знизити версію бази даних.";
/* No comment provided by engineer. */
"Open the app to upgrade the database." = "Відкрийте програму, щоб оновити базу даних.";
/* No comment provided by engineer. */
"Passphrase" = "Парольна фраза";
/* No comment provided by engineer. */
"Please create a profile in the SimpleX app" = "Будь ласка, створіть профіль у додатку SimpleX";
/* No comment provided by engineer. */
"Selected chat preferences prohibit this message." = "Вибрані налаштування чату забороняють це повідомлення.";
/* No comment provided by engineer. */
"Sending a message takes longer than expected." = "Надсилання повідомлення займає більше часу, ніж очікувалося.";
/* No comment provided by engineer. */
"Sending message…" = "Надсилаю повідомлення…";
/* No comment provided by engineer. */
"Share" = "Поділіться";
/* No comment provided by engineer. */
"Slow network?" = "Повільна мережа?";
/* No comment provided by engineer. */
"Unknown database error: %@" = "Невідома помилка бази даних: %@";
/* No comment provided by engineer. */
"Unsupported format" = "Непідтримуваний формат";
/* No comment provided by engineer. */
"Wait" = "Зачекай";
/* No comment provided by engineer. */
"Wrong database passphrase" = "Неправильна ключова фраза до бази даних";
/* No comment provided by engineer. */
"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "Ви можете дозволити спільний доступ у налаштуваннях Конфіденційність і безпека / SimpleX Lock.";
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
+40 -40
View File
@@ -214,15 +214,15 @@
D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; };
D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; };
E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; };
E5BD844D2C8220D0008C24D1 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84482C8220D0008C24D1 /* libffi.a */; };
E5BD844E2C8220D0008C24D1 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84492C8220D0008C24D1 /* libgmpxx.a */; };
E5BD844F2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD844A2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a */; };
E5BD84502C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD844B2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a */; };
E5BD84512C8220D0008C24D1 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD844C2C8220D0008C24D1 /* libgmp.a */; };
E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; };
E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; };
E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; };
E5DCF9982C5906FF007928CC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9962C5906FF007928CC /* InfoPlist.strings */; }; E5DCF9982C5906FF007928CC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9962C5906FF007928CC /* InfoPlist.strings */; };
E5E218532C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E2184E2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a */; };
E5E218542C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E2184F2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a */; };
E5E218552C6D4C0F0013B4C6 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E218502C6D4C0F0013B4C6 /* libgmp.a */; };
E5E218562C6D4C0F0013B4C6 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E218512C6D4C0F0013B4C6 /* libffi.a */; };
E5E218572C6D4C0F0013B4C6 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E218522C6D4C0F0013B4C6 /* libgmpxx.a */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@@ -550,6 +550,11 @@
D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; };
D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = "<group>"; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = "<group>"; };
E5BD84482C8220D0008C24D1 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
E5BD84492C8220D0008C24D1 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
E5BD844A2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a"; sourceTree = "<group>"; };
E5BD844B2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a"; sourceTree = "<group>"; };
E5BD844C2C8220D0008C24D1 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = "<group>"; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = "<group>"; };
E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; }; E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
@@ -602,11 +607,6 @@
E5DCF9A62C590731007928CC /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/InfoPlist.strings; sourceTree = "<group>"; }; E5DCF9A62C590731007928CC /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/InfoPlist.strings; sourceTree = "<group>"; };
E5DCF9A72C590732007928CC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = "<group>"; }; E5DCF9A72C590732007928CC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = "<group>"; };
E5DCF9A82C590732007928CC /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = "<group>"; }; E5DCF9A82C590732007928CC /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = "<group>"; };
E5E2184E2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a"; sourceTree = "<group>"; };
E5E2184F2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a"; sourceTree = "<group>"; };
E5E218502C6D4C0F0013B4C6 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
E5E218512C6D4C0F0013B4C6 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
E5E218522C6D4C0F0013B4C6 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@@ -646,13 +646,13 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
E5E218542C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a in Frameworks */, E5BD844E2C8220D0008C24D1 /* libgmpxx.a in Frameworks */,
E5BD84512C8220D0008C24D1 /* libgmp.a in Frameworks */,
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
E5E218572C6D4C0F0013B4C6 /* libgmpxx.a in Frameworks */, E5BD84502C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a in Frameworks */,
E5E218562C6D4C0F0013B4C6 /* libffi.a in Frameworks */, E5BD844D2C8220D0008C24D1 /* libffi.a in Frameworks */,
E5E218552C6D4C0F0013B4C6 /* libgmp.a in Frameworks */, E5BD844F2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a in Frameworks */,
E5E218532C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -729,11 +729,11 @@
5C764E5C279C70B7000C6508 /* Libraries */ = { 5C764E5C279C70B7000C6508 /* Libraries */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E5E218512C6D4C0F0013B4C6 /* libffi.a */, E5BD84482C8220D0008C24D1 /* libffi.a */,
E5E218502C6D4C0F0013B4C6 /* libgmp.a */, E5BD844C2C8220D0008C24D1 /* libgmp.a */,
E5E218522C6D4C0F0013B4C6 /* libgmpxx.a */, E5BD84492C8220D0008C24D1 /* libgmpxx.a */,
E5E2184E2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q-ghc9.6.3.a */, E5BD844A2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a */,
E5E2184F2C6D4C0F0013B4C6 /* libHSsimplex-chat-6.0.0.8-9fvDFLivFrv8AINTqPH03q.a */, E5BD844B2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a */,
); );
path = Libraries; path = Libraries;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1879,7 +1879,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 233; CURRENT_PROJECT_VERSION = 236;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -1904,7 +1904,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
LLVM_LTO = YES_THIN; LLVM_LTO = YES_THIN;
MARKETING_VERSION = 6.0.1; MARKETING_VERSION = 6.0.4;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX; PRODUCT_NAME = SimpleX;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -1928,7 +1928,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 233; CURRENT_PROJECT_VERSION = 236;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -1953,7 +1953,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 6.0.1; MARKETING_VERSION = 6.0.4;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX; PRODUCT_NAME = SimpleX;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -1969,11 +1969,11 @@
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 233; CURRENT_PROJECT_VERSION = 236;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 6.0.1; MARKETING_VERSION = 6.0.4;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -1989,11 +1989,11 @@
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 233; CURRENT_PROJECT_VERSION = 236;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 6.0.1; MARKETING_VERSION = 6.0.4;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -2014,7 +2014,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 233; CURRENT_PROJECT_VERSION = 236;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
GCC_OPTIMIZATION_LEVEL = s; GCC_OPTIMIZATION_LEVEL = s;
@@ -2029,7 +2029,7 @@
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 6.0.1; MARKETING_VERSION = 6.0.4;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2051,7 +2051,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 233; CURRENT_PROJECT_VERSION = 236;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
ENABLE_CODE_COVERAGE = NO; ENABLE_CODE_COVERAGE = NO;
@@ -2066,7 +2066,7 @@
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 6.0.1; MARKETING_VERSION = 6.0.4;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2088,7 +2088,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 233; CURRENT_PROJECT_VERSION = 236;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1;
@@ -2114,7 +2114,7 @@
"$(PROJECT_DIR)/Libraries/sim", "$(PROJECT_DIR)/Libraries/sim",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 6.0.1; MARKETING_VERSION = 6.0.4;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -2139,7 +2139,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 233; CURRENT_PROJECT_VERSION = 236;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1;
@@ -2165,7 +2165,7 @@
"$(PROJECT_DIR)/Libraries/sim", "$(PROJECT_DIR)/Libraries/sim",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 6.0.1; MARKETING_VERSION = 6.0.4;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -2190,7 +2190,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 233; CURRENT_PROJECT_VERSION = 236;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -2205,7 +2205,7 @@
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 6.0.1; MARKETING_VERSION = 6.0.4;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -2224,7 +2224,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 233; CURRENT_PROJECT_VERSION = 236;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -2239,7 +2239,7 @@
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 6.0.1; MARKETING_VERSION = 6.0.4;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
+48 -39
View File
@@ -537,7 +537,7 @@ public enum ChatResponse: Decodable, Error {
case networkConfig(networkConfig: NetCfg) case networkConfig(networkConfig: NetCfg)
case contactInfo(user: UserRef, contact: Contact, connectionStats_: ConnectionStats?, customUserProfile: Profile?) case contactInfo(user: UserRef, contact: Contact, connectionStats_: ConnectionStats?, customUserProfile: Profile?)
case groupMemberInfo(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats_: ConnectionStats?) case groupMemberInfo(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats_: ConnectionStats?)
case queueInfo(user: UserRef, rcvMsgInfo: RcvMsgInfo?, queueInfo: QueueInfo) case queueInfo(user: UserRef, rcvMsgInfo: RcvMsgInfo?, queueInfo: ServerQueueInfo)
case contactSwitchStarted(user: UserRef, contact: Contact, connectionStats: ConnectionStats) case contactSwitchStarted(user: UserRef, contact: Contact, connectionStats: ConnectionStats)
case groupMemberSwitchStarted(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats) case groupMemberSwitchStarted(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats)
case contactSwitchAborted(user: UserRef, contact: Contact, connectionStats: ConnectionStats) case contactSwitchAborted(user: UserRef, contact: Contact, connectionStats: ConnectionStats)
@@ -1094,12 +1094,12 @@ public enum GroupLinkPlan: Decodable, Hashable {
case known(groupInfo: GroupInfo) case known(groupInfo: GroupInfo)
} }
struct NewUser: Encodable, Hashable { struct NewUser: Encodable {
var profile: Profile? var profile: Profile?
var pastTimestamp: Bool var pastTimestamp: Bool
} }
public enum ChatPagination: Hashable { public enum ChatPagination {
case last(count: Int) case last(count: Int)
case after(chatItemId: Int64, count: Int) case after(chatItemId: Int64, count: Int)
case before(chatItemId: Int64, count: Int) case before(chatItemId: Int64, count: Int)
@@ -1315,7 +1315,7 @@ public struct ServerAddress: Decodable {
) )
} }
public struct NetCfg: Codable, Equatable, Hashable { public struct NetCfg: Codable, Equatable {
public var socksProxy: String? = nil public var socksProxy: String? = nil
var socksMode: SocksMode = .always var socksMode: SocksMode = .always
public var hostMode: HostMode = .publicHost public var hostMode: HostMode = .publicHost
@@ -1369,18 +1369,18 @@ public struct NetCfg: Codable, Equatable, Hashable {
public var enableKeepAlive: Bool { tcpKeepAlive != nil } public var enableKeepAlive: Bool { tcpKeepAlive != nil }
} }
public enum HostMode: String, Codable, Hashable { public enum HostMode: String, Codable {
case onionViaSocks case onionViaSocks
case onionHost = "onion" case onionHost = "onion"
case publicHost = "public" case publicHost = "public"
} }
public enum SocksMode: String, Codable, Hashable { public enum SocksMode: String, Codable {
case always = "always" case always = "always"
case onion = "onion" case onion = "onion"
} }
public enum SMPProxyMode: String, Codable, Hashable, SelectableItem { public enum SMPProxyMode: String, Codable, SelectableItem {
case always = "always" case always = "always"
case unknown = "unknown" case unknown = "unknown"
case unprotected = "unprotected" case unprotected = "unprotected"
@@ -1400,7 +1400,7 @@ public enum SMPProxyMode: String, Codable, Hashable, SelectableItem {
public static let values: [SMPProxyMode] = [.always, .unknown, .unprotected, .never] public static let values: [SMPProxyMode] = [.always, .unknown, .unprotected, .never]
} }
public enum SMPProxyFallback: String, Codable, Hashable, SelectableItem { public enum SMPProxyFallback: String, Codable, SelectableItem {
case allow = "allow" case allow = "allow"
case allowProtected = "allowProtected" case allowProtected = "allowProtected"
case prohibit = "prohibit" case prohibit = "prohibit"
@@ -1418,7 +1418,7 @@ public enum SMPProxyFallback: String, Codable, Hashable, SelectableItem {
public static let values: [SMPProxyFallback] = [.allow, .allowProtected, .prohibit] public static let values: [SMPProxyFallback] = [.allow, .allowProtected, .prohibit]
} }
public enum OnionHosts: String, Identifiable, Hashable { public enum OnionHosts: String, Identifiable {
case no case no
case prefer case prefer
case require case require
@@ -1452,7 +1452,7 @@ public enum OnionHosts: String, Identifiable, Hashable {
public static let values: [OnionHosts] = [.no, .prefer, .require] public static let values: [OnionHosts] = [.no, .prefer, .require]
} }
public enum TransportSessionMode: String, Codable, Identifiable, Hashable { public enum TransportSessionMode: String, Codable, Identifiable {
case user case user
case entity case entity
@@ -1468,7 +1468,7 @@ public enum TransportSessionMode: String, Codable, Identifiable, Hashable {
public static let values: [TransportSessionMode] = [.user, .entity] public static let values: [TransportSessionMode] = [.user, .entity]
} }
public struct KeepAliveOpts: Codable, Equatable, Hashable { public struct KeepAliveOpts: Codable, Equatable {
public var keepIdle: Int // seconds public var keepIdle: Int // seconds
public var keepIntvl: Int // seconds public var keepIntvl: Int // seconds
public var keepCnt: Int // times public var keepCnt: Int // times
@@ -1476,7 +1476,7 @@ public struct KeepAliveOpts: Codable, Equatable, Hashable {
public static let defaults: KeepAliveOpts = KeepAliveOpts(keepIdle: 30, keepIntvl: 15, keepCnt: 4) public static let defaults: KeepAliveOpts = KeepAliveOpts(keepIdle: 30, keepIntvl: 15, keepCnt: 4)
} }
public enum NetworkStatus: Decodable, Equatable, Hashable { public enum NetworkStatus: Decodable, Equatable {
case unknown case unknown
case connected case connected
case disconnected case disconnected
@@ -1514,7 +1514,7 @@ public enum NetworkStatus: Decodable, Equatable, Hashable {
} }
} }
public struct ConnNetworkStatus: Decodable, Hashable { public struct ConnNetworkStatus: Decodable {
public var agentConnId: String public var agentConnId: String
public var networkStatus: NetworkStatus public var networkStatus: NetworkStatus
} }
@@ -1539,7 +1539,7 @@ public enum MsgFilter: String, Codable, Hashable {
case mentions case mentions
} }
public struct UserMsgReceiptSettings: Codable, Hashable { public struct UserMsgReceiptSettings: Codable {
public var enable: Bool public var enable: Bool
public var clearOverrides: Bool public var clearOverrides: Bool
@@ -1588,7 +1588,7 @@ public enum SndSwitchStatus: String, Codable, Hashable {
case sendingQTEST = "sending_qtest" case sendingQTEST = "sending_qtest"
} }
public enum QueueDirection: String, Decodable, Hashable { public enum QueueDirection: String, Decodable {
case rcv case rcv
case snd case snd
} }
@@ -1643,12 +1643,12 @@ public struct AutoAccept: Codable, Hashable {
} }
} }
public protocol SelectableItem: Hashable, Identifiable { public protocol SelectableItem: Identifiable, Equatable {
var label: LocalizedStringKey { get } var label: LocalizedStringKey { get }
static var values: [Self] { get } static var values: [Self] { get }
} }
public struct DeviceToken: Decodable, Hashable { public struct DeviceToken: Decodable {
var pushProvider: PushProvider var pushProvider: PushProvider
var token: String var token: String
@@ -1662,12 +1662,12 @@ public struct DeviceToken: Decodable, Hashable {
} }
} }
public enum PushEnvironment: String, Hashable { public enum PushEnvironment: String {
case development case development
case production case production
} }
public enum PushProvider: String, Decodable, Hashable { public enum PushProvider: String, Decodable {
case apns_dev case apns_dev
case apns_prod case apns_prod
@@ -1681,7 +1681,7 @@ public enum PushProvider: String, Decodable, Hashable {
// This notification mode is for app core, UI uses AppNotificationsMode.off to mean completely disable, // This notification mode is for app core, UI uses AppNotificationsMode.off to mean completely disable,
// and .local for periodic background checks // and .local for periodic background checks
public enum NotificationsMode: String, Decodable, SelectableItem, Hashable { public enum NotificationsMode: String, Decodable, SelectableItem {
case off = "OFF" case off = "OFF"
case periodic = "PERIODIC" case periodic = "PERIODIC"
case instant = "INSTANT" case instant = "INSTANT"
@@ -1699,7 +1699,7 @@ public enum NotificationsMode: String, Decodable, SelectableItem, Hashable {
public static var values: [NotificationsMode] = [.instant, .periodic, .off] public static var values: [NotificationsMode] = [.instant, .periodic, .off]
} }
public enum NotificationPreviewMode: String, SelectableItem, Codable, Hashable { public enum NotificationPreviewMode: String, SelectableItem, Codable {
case hidden case hidden
case contact case contact
case message case message
@@ -1717,7 +1717,7 @@ public enum NotificationPreviewMode: String, SelectableItem, Codable, Hashable {
public static var values: [NotificationPreviewMode] = [.message, .contact, .hidden] public static var values: [NotificationPreviewMode] = [.message, .contact, .hidden]
} }
public struct RemoteCtrlInfo: Decodable, Hashable { public struct RemoteCtrlInfo: Decodable {
public var remoteCtrlId: Int64 public var remoteCtrlId: Int64
public var ctrlDeviceName: String public var ctrlDeviceName: String
public var sessionState: RemoteCtrlSessionState? public var sessionState: RemoteCtrlSessionState?
@@ -1727,7 +1727,7 @@ public struct RemoteCtrlInfo: Decodable, Hashable {
} }
} }
public enum RemoteCtrlSessionState: Decodable, Hashable { public enum RemoteCtrlSessionState: Decodable {
case starting case starting
case searching case searching
case connecting case connecting
@@ -1742,17 +1742,17 @@ public enum RemoteCtrlStopReason: Decodable {
case disconnected case disconnected
} }
public struct CtrlAppInfo: Decodable, Hashable { public struct CtrlAppInfo: Decodable {
public var appVersionRange: AppVersionRange public var appVersionRange: AppVersionRange
public var deviceName: String public var deviceName: String
} }
public struct AppVersionRange: Decodable, Hashable { public struct AppVersionRange: Decodable {
public var minVersion: String public var minVersion: String
public var maxVersion: String public var maxVersion: String
} }
public struct CoreVersionInfo: Decodable, Hashable { public struct CoreVersionInfo: Decodable {
public var version: String public var version: String
public var simplexmqVersion: String public var simplexmqVersion: String
public var simplexmqCommit: String public var simplexmqCommit: String
@@ -2090,14 +2090,14 @@ public enum RemoteCtrlError: Decodable, Hashable {
case protocolError case protocolError
} }
public struct MigrationFileLinkData: Codable, Hashable { public struct MigrationFileLinkData: Codable {
let networkConfig: NetworkConfig? let networkConfig: NetworkConfig?
public init(networkConfig: NetworkConfig) { public init(networkConfig: NetworkConfig) {
self.networkConfig = networkConfig self.networkConfig = networkConfig
} }
public struct NetworkConfig: Codable, Hashable { public struct NetworkConfig: Codable {
let socksProxy: String? let socksProxy: String?
let hostMode: HostMode? let hostMode: HostMode?
let requiredHostMode: Bool? let requiredHostMode: Bool?
@@ -2129,7 +2129,7 @@ public struct MigrationFileLinkData: Codable, Hashable {
} }
} }
public struct AppSettings: Codable, Equatable, Hashable { public struct AppSettings: Codable, Equatable {
public var networkConfig: NetCfg? = nil public var networkConfig: NetCfg? = nil
public var privacyEncryptLocalFiles: Bool? = nil public var privacyEncryptLocalFiles: Bool? = nil
public var privacyAskToApproveRelays: Bool? = nil public var privacyAskToApproveRelays: Bool? = nil
@@ -2224,7 +2224,7 @@ public struct AppSettings: Codable, Equatable, Hashable {
} }
} }
public enum AppSettingsNotificationMode: String, Codable, Hashable { public enum AppSettingsNotificationMode: String, Codable {
case off case off
case periodic case periodic
case instant case instant
@@ -2252,13 +2252,13 @@ public enum AppSettingsNotificationMode: String, Codable, Hashable {
// case message // case message
//} //}
public enum AppSettingsLockScreenCalls: String, Codable, Hashable { public enum AppSettingsLockScreenCalls: String, Codable {
case disable case disable
case show case show
case accept case accept
} }
public struct UserNetworkInfo: Codable, Equatable, Hashable { public struct UserNetworkInfo: Codable, Equatable {
public let networkType: UserNetworkType public let networkType: UserNetworkType
public let online: Bool public let online: Bool
@@ -2268,7 +2268,7 @@ public struct UserNetworkInfo: Codable, Equatable, Hashable {
} }
} }
public enum UserNetworkType: String, Codable, Hashable { public enum UserNetworkType: String, Codable {
case none case none
case cellular case cellular
case wifi case wifi
@@ -2286,7 +2286,7 @@ public enum UserNetworkType: String, Codable, Hashable {
} }
} }
public struct RcvMsgInfo: Codable, Hashable { public struct RcvMsgInfo: Codable {
var msgId: Int64 var msgId: Int64
var msgDeliveryId: Int64 var msgDeliveryId: Int64
var msgDeliveryStatus: String var msgDeliveryStatus: String
@@ -2294,7 +2294,16 @@ public struct RcvMsgInfo: Codable, Hashable {
var agentMsgMeta: String var agentMsgMeta: String
} }
public struct QueueInfo: Codable, Hashable { public struct ServerQueueInfo: Codable {
var server: String
var rcvId: String
var sndId: String
var ntfId: String?
var status: String
var info: QueueInfo
}
public struct QueueInfo: Codable {
var qiSnd: Bool var qiSnd: Bool
var qiNtf: Bool var qiNtf: Bool
var qiSub: QSub? var qiSub: QSub?
@@ -2302,25 +2311,25 @@ public struct QueueInfo: Codable, Hashable {
var qiMsg: MsgInfo? var qiMsg: MsgInfo?
} }
public struct QSub: Codable, Hashable { public struct QSub: Codable {
var qSubThread: QSubThread var qSubThread: QSubThread
var qDelivered: String? var qDelivered: String?
} }
public enum QSubThread: String, Codable, Hashable { public enum QSubThread: String, Codable {
case noSub case noSub
case subPending case subPending
case subThread case subThread
case prohibitSub case prohibitSub
} }
public struct MsgInfo: Codable, Hashable { public struct MsgInfo: Codable {
var msgId: String var msgId: String
var msgTs: Date var msgTs: Date
var msgType: MsgType var msgType: MsgType
} }
public enum MsgType: String, Codable, Hashable { public enum MsgType: String, Codable {
case message case message
case quota case quota
} }
+2 -3
View File
@@ -42,6 +42,7 @@ public struct RcvCallInvitation: Decodable {
public var contact: Contact public var contact: Contact
public var callType: CallType public var callType: CallType
public var sharedKey: String? public var sharedKey: String?
public var callUUID: String?
public var callTs: Date public var callTs: Date
public var callTypeText: LocalizedStringKey { public var callTypeText: LocalizedStringKey {
get { get {
@@ -52,10 +53,8 @@ public struct RcvCallInvitation: Decodable {
} }
} }
public var callkitUUID: UUID? = UUID()
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case user, contact, callType, sharedKey, callTs case user, contact, callType, sharedKey, callUUID, callTs
} }
public static let sampleData = RcvCallInvitation( public static let sampleData = RcvCallInvitation(
+6
View File
@@ -1500,6 +1500,12 @@ public struct ChatData: Decodable, Identifiable, Hashable, ChatLike {
public var id: ChatId { get { chatInfo.id } } public var id: ChatId { get { chatInfo.id } }
public init(chatInfo: ChatInfo, chatItems: [ChatItem], chatStats: ChatStats = ChatStats()) {
self.chatInfo = chatInfo
self.chatItems = chatItems
self.chatStats = chatStats
}
public static func invalidJSON(_ json: String) -> ChatData { public static func invalidJSON(_ json: String) -> ChatData {
ChatData( ChatData(
chatInfo: .invalidJSON(json: json), chatInfo: .invalidJSON(json: json),
+5 -5
View File
@@ -506,7 +506,7 @@
"Allow to send files and media." = "Se permite enviar archivos y multimedia."; "Allow to send files and media." = "Se permite enviar archivos y multimedia.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Allow to send SimpleX links." = "Permitir enviar enlaces SimpleX."; "Allow to send SimpleX links." = "Se permite enviar enlaces SimpleX.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Allow to send voice messages." = "Permites enviar mensajes de voz."; "Allow to send voice messages." = "Permites enviar mensajes de voz.";
@@ -1606,7 +1606,7 @@
"Do it later" = "Hacer más tarde"; "Do it later" = "Hacer más tarde";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Do not send history to new members." = "No enviar historial a miembros nuevos."; "Do not send history to new members." = "No se envía el historial a los miembros nuevos.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Do NOT send messages directly, even if your or destination server does not support private routing." = "NO enviar mensajes directamente incluso si tu servidor o el de destino no soportan enrutamiento privado."; "Do NOT send messages directly, even if your or destination server does not support private routing." = "NO enviar mensajes directamente incluso si tu servidor o el de destino no soportan enrutamiento privado.";
@@ -3415,7 +3415,7 @@
"Prohibit sending files and media." = "No permitir el envío de archivos y multimedia."; "Prohibit sending files and media." = "No permitir el envío de archivos y multimedia.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Prohibit sending SimpleX links." = "No permitir el envío de enlaces SimpleX."; "Prohibit sending SimpleX links." = "No se permite enviar enlaces SimpleX.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Prohibit sending voice messages." = "No se permiten mensajes de voz."; "Prohibit sending voice messages." = "No se permiten mensajes de voz.";
@@ -3917,7 +3917,7 @@
"Send them from gallery or custom keyboards." = "Envíalos desde la galería o desde teclados personalizados."; "Send them from gallery or custom keyboards." = "Envíalos desde la galería o desde teclados personalizados.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Send up to 100 last messages to new members." = "Enviar hasta 100 últimos mensajes a los miembros nuevos."; "Send up to 100 last messages to new members." = "Se envían hasta 100 mensajes más recientes a los miembros nuevos.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Sender cancelled file transfer." = "El remitente ha cancelado la transferencia de archivos."; "Sender cancelled file transfer." = "El remitente ha cancelado la transferencia de archivos.";
@@ -4979,7 +4979,7 @@
"You can accept calls from lock screen, without device and app authentication." = "Puede aceptar llamadas desde la pantalla de bloqueo, sin autenticación de dispositivos y aplicaciones."; "You can accept calls from lock screen, without device and app authentication." = "Puede aceptar llamadas desde la pantalla de bloqueo, sin autenticación de dispositivos y aplicaciones.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You can change it in Appearance settings." = "Puede cambiarlo desde el menú Apariencia."; "You can change it in Appearance settings." = "Puedes cambiar la posición de la barra desde el menú Apariencia.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You can create it later" = "Puedes crearla más tarde"; "You can create it later" = "Puedes crearla más tarde";
+10 -10
View File
@@ -659,10 +659,10 @@
"Bad desktop address" = "Hibás számítógép cím"; "Bad desktop address" = "Hibás számítógép cím";
/* integrity error chat item */ /* integrity error chat item */
"bad message hash" = "téves üzenet hash"; "bad message hash" = "hibás az üzenet ellenőrzőösszege";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Bad message hash" = "Téves üzenet hash"; "Bad message hash" = "Hibás az üzenet ellenőrzőösszege";
/* integrity error chat item */ /* integrity error chat item */
"bad message ID" = "téves üzenet ID"; "bad message ID" = "téves üzenet ID";
@@ -1432,7 +1432,7 @@
"Delete link?" = "Hivatkozás törlése?"; "Delete link?" = "Hivatkozás törlése?";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Delete member message?" = "Csoporttag üzenet törlése?"; "Delete member message?" = "Csoporttag üzenetének törlése?";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Delete message?" = "Üzenet törlése?"; "Delete message?" = "Üzenet törlése?";
@@ -1495,7 +1495,7 @@
"Delivery receipts are disabled!" = "A kézbesítési jelentések ki vannak kapcsolva!"; "Delivery receipts are disabled!" = "A kézbesítési jelentések ki vannak kapcsolva!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Delivery receipts!" = "Kézbesítési igazolások!"; "Delivery receipts!" = "Üzenet kézbesítési jelentések!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Description" = "Leírás"; "Description" = "Leírás";
@@ -1828,10 +1828,10 @@
"Enter this device name…" = "Eszköznév megadása…"; "Enter this device name…" = "Eszköznév megadása…";
/* placeholder */ /* placeholder */
"Enter welcome message…" = "Üdvözlő üzenetet megadása…"; "Enter welcome message…" = "Üdvözlő üzenet megadása…";
/* placeholder */ /* placeholder */
"Enter welcome message… (optional)" = "Üdvözlő üzenetet megadása… (opcionális)"; "Enter welcome message… (optional)" = "Üdvözlő üzenet megadása… (opcionális)";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Enter your name…" = "Adjon meg egy nevet…"; "Enter your name…" = "Adjon meg egy nevet…";
@@ -2783,7 +2783,7 @@
"Message delivery error" = "Üzenetkézbesítési hiba"; "Message delivery error" = "Üzenetkézbesítési hiba";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Message delivery receipts!" = "Üzenetkézbesítési bizonylatok!"; "Message delivery receipts!" = "Üzenet kézbesítési jelentések!";
/* item status text */ /* item status text */
"Message delivery warning" = "Üzenet kézbesítési figyelmeztetés"; "Message delivery warning" = "Üzenet kézbesítési figyelmeztetés";
@@ -2813,7 +2813,7 @@
"message received" = "üzenet érkezett"; "message received" = "üzenet érkezett";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Message reception" = "Üzenetjelentés"; "Message reception" = "Üzenet kézbesítési jelentés";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Message servers" = "Üzenetkiszolgálók"; "Message servers" = "Üzenetkiszolgálók";
@@ -3605,7 +3605,7 @@
"removed" = "eltávolítva"; "removed" = "eltávolítva";
/* rcv group event chat item */ /* rcv group event chat item */
"removed %@" = "%@ eltávolítva"; "removed %@" = "eltávolította őt: %@";
/* profile update event chat item */ /* profile update event chat item */
"removed contact address" = "törölt kapcsolattartási cím"; "removed contact address" = "törölt kapcsolattartási cím";
@@ -4373,7 +4373,7 @@
"The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "A titkosítás működik, és új titkosítási egyezményre nincs szükség. Ez kapcsolati hibákat eredményezhet!"; "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "A titkosítás működik, és új titkosítási egyezményre nincs szükség. Ez kapcsolati hibákat eredményezhet!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"The hash of the previous message is different." = "Az előző üzenet hash-e más."; "The hash of the previous message is different." = "Az előző üzenet ellenőrzőösszege különbözik.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"The ID of the next message is incorrect (less or equal to the previous).\nIt can happen because of some bug or when the connection is compromised." = "A következő üzenet azonosítója hibás (kisebb vagy egyenlő az előzővel).\nEz valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő."; "The ID of the next message is incorrect (less or equal to the previous).\nIt can happen because of some bug or when the connection is compromised." = "A következő üzenet azonosítója hibás (kisebb vagy egyenlő az előzővel).\nEz valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő.";
+1 -1
View File
@@ -1089,7 +1089,7 @@
"Connection" = "Connessione"; "Connection" = "Connessione";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Connection and servers status." = "Stato di connessione e server."; "Connection and servers status." = "Stato della connessione e dei server.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Connection error" = "Errore di connessione"; "Connection error" = "Errore di connessione";
+1 -1
View File
@@ -2630,7 +2630,7 @@
"Keep" = "Bewaar"; "Keep" = "Bewaar";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Keep conversation" = "Blijf in gesprek"; "Keep conversation" = "Behoud het gesprek";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Keep the app open to use it from desktop" = "Houd de app geopend om deze vanaf de desktop te gebruiken"; "Keep the app open to use it from desktop" = "Houd de app geopend om deze vanaf de desktop te gebruiken";
+234
View File
@@ -472,6 +472,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Allow calls only if your contact allows them." = "Zezwalaj na połączenia tylko wtedy, gdy Twój kontakt na to pozwala."; "Allow calls only if your contact allows them." = "Zezwalaj na połączenia tylko wtedy, gdy Twój kontakt na to pozwala.";
/* No comment provided by engineer. */
"Allow calls?" = "Zezwolić na połączenia?";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Allow disappearing messages only if your contact allows it to you." = "Zezwól na znikające wiadomości tylko wtedy, gdy Twój kontakt Ci na to pozwoli."; "Allow disappearing messages only if your contact allows it to you." = "Zezwól na znikające wiadomości tylko wtedy, gdy Twój kontakt Ci na to pozwoli.";
@@ -493,6 +496,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Allow sending disappearing messages." = "Zezwól na wysyłanie znikających wiadomości."; "Allow sending disappearing messages." = "Zezwól na wysyłanie znikających wiadomości.";
/* No comment provided by engineer. */
"Allow sharing" = "Zezwól na udostępnianie";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Allow to irreversibly delete sent messages. (24 hours)" = "Zezwól na nieodwracalne usunięcie wysłanych wiadomości. (24 godziny)"; "Allow to irreversibly delete sent messages. (24 hours)" = "Zezwól na nieodwracalne usunięcie wysłanych wiadomości. (24 godziny)";
@@ -589,6 +595,12 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Archive and upload" = "Archiwizuj i prześlij"; "Archive and upload" = "Archiwizuj i prześlij";
/* No comment provided by engineer. */
"Archive contacts to chat later." = "Archiwizuj kontakty aby porozmawiać później.";
/* No comment provided by engineer. */
"Archived contacts" = "Zarchiwizowane kontakty";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Archiving database" = "Archiwizowanie bazy danych"; "Archiving database" = "Archiwizowanie bazy danych";
@@ -664,6 +676,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Better messages" = "Lepsze wiadomości"; "Better messages" = "Lepsze wiadomości";
/* No comment provided by engineer. */
"Better networking" = "Lepsze sieciowanie";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Black" = "Czarny"; "Black" = "Czarny";
@@ -697,6 +712,12 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Blocked by admin" = "Zablokowany przez admina"; "Blocked by admin" = "Zablokowany przez admina";
/* No comment provided by engineer. */
"Blur for better privacy." = "Rozmycie dla lepszej prywatności.";
/* No comment provided by engineer. */
"Blur media" = "Rozmycie mediów";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"bold" = "pogrubiona"; "bold" = "pogrubiona";
@@ -721,6 +742,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Według profilu czatu (domyślnie) lub [według połączenia](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Według profilu czatu (domyślnie) lub [według połączenia](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).";
/* No comment provided by engineer. */
"call" = "zadzwoń";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Call already ended!" = "Połączenie już zakończone!"; "Call already ended!" = "Połączenie już zakończone!";
@@ -736,15 +760,27 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Calls" = "Połączenia"; "Calls" = "Połączenia";
/* No comment provided by engineer. */
"Calls prohibited!" = "Połączenia zakazane!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Camera not available" = "Kamera nie dostępna"; "Camera not available" = "Kamera nie dostępna";
/* No comment provided by engineer. */
"Can't call contact" = "Nie można zadzwonić do kontaktu";
/* No comment provided by engineer. */
"Can't call member" = "Nie można zadzwonić do członka";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Can't invite contact!" = "Nie można zaprosić kontaktu!"; "Can't invite contact!" = "Nie można zaprosić kontaktu!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Can't invite contacts!" = "Nie można zaprosić kontaktów!"; "Can't invite contacts!" = "Nie można zaprosić kontaktów!";
/* No comment provided by engineer. */
"Can't message member" = "Nie można wysłać wiadomości do członka";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Cancel" = "Anuluj"; "Cancel" = "Anuluj";
@@ -830,6 +866,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Chat database deleted" = "Baza danych czatu usunięta"; "Chat database deleted" = "Baza danych czatu usunięta";
/* No comment provided by engineer. */
"Chat database exported" = "Wyeksportowano bazę danych czatu";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Chat database imported" = "Zaimportowano bazę danych czatu"; "Chat database imported" = "Zaimportowano bazę danych czatu";
@@ -842,6 +881,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Czat został zatrzymany. Jeśli korzystałeś już z tej bazy danych na innym urządzeniu, powinieneś przenieść ją z powrotem przed rozpoczęciem czatu."; "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Czat został zatrzymany. Jeśli korzystałeś już z tej bazy danych na innym urządzeniu, powinieneś przenieść ją z powrotem przed rozpoczęciem czatu.";
/* No comment provided by engineer. */
"Chat list" = "Lista czatów";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Chat migrated!" = "Czat zmigrowany!"; "Chat migrated!" = "Czat zmigrowany!";
@@ -893,6 +935,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Clear verification" = "Wyczyść weryfikację"; "Clear verification" = "Wyczyść weryfikację";
/* No comment provided by engineer. */
"Color chats with the new themes." = "Koloruj czaty z nowymi motywami.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Color mode" = "Tryb koloru"; "Color mode" = "Tryb koloru";
@@ -920,6 +965,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Confirm" = "Potwierdź"; "Confirm" = "Potwierdź";
/* No comment provided by engineer. */
"Confirm contact deletion?" = "Potwierdzić usunięcie kontaktu?";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Confirm database upgrades" = "Potwierdź aktualizacje bazy danych"; "Confirm database upgrades" = "Potwierdź aktualizacje bazy danych";
@@ -959,6 +1007,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"connect to SimpleX Chat developers." = "połącz się z deweloperami SimpleX Chat."; "connect to SimpleX Chat developers." = "połącz się z deweloperami SimpleX Chat.";
/* No comment provided by engineer. */
"Connect to your friends faster." = "Szybciej łącz się ze znajomymi.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Connect to yourself?" = "Połączyć się ze sobą?"; "Connect to yourself?" = "Połączyć się ze sobą?";
@@ -1025,6 +1076,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Connecting server… (error: %@)" = "Łączenie z serwerem... (błąd: %@)"; "Connecting server… (error: %@)" = "Łączenie z serwerem... (błąd: %@)";
/* No comment provided by engineer. */
"Connecting to contact, please wait or check later!" = "Łączenie z kontaktem, poczekaj lub sprawdź później!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Connecting to desktop" = "Łączenie z komputerem"; "Connecting to desktop" = "Łączenie z komputerem";
@@ -1034,6 +1088,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Connection" = "Połączenie"; "Connection" = "Połączenie";
/* No comment provided by engineer. */
"Connection and servers status." = "Stan połączenia i serwerów.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Connection error" = "Błąd połączenia"; "Connection error" = "Błąd połączenia";
@@ -1043,6 +1100,9 @@
/* chat list item title (it should not be shown */ /* chat list item title (it should not be shown */
"connection established" = "połączenie ustanowione"; "connection established" = "połączenie ustanowione";
/* No comment provided by engineer. */
"Connection notifications" = "Powiadomienia o połączeniu";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Connection request sent!" = "Prośba o połączenie wysłana!"; "Connection request sent!" = "Prośba o połączenie wysłana!";
@@ -1070,6 +1130,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Contact already exists" = "Kontakt już istnieje"; "Contact already exists" = "Kontakt już istnieje";
/* No comment provided by engineer. */
"Contact deleted!" = "Kontakt usunięty!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"contact has e2e encryption" = "kontakt posiada szyfrowanie e2e"; "contact has e2e encryption" = "kontakt posiada szyfrowanie e2e";
@@ -1082,12 +1145,18 @@
/* notification */ /* notification */
"Contact is connected" = "Kontakt jest połączony"; "Contact is connected" = "Kontakt jest połączony";
/* No comment provided by engineer. */
"Contact is deleted." = "Kontakt jest usunięty.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Contact name" = "Nazwa kontaktu"; "Contact name" = "Nazwa kontaktu";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Contact preferences" = "Preferencje kontaktu"; "Contact preferences" = "Preferencje kontaktu";
/* No comment provided by engineer. */
"Contact will be deleted - this cannot be undone!" = "Kontakt zostanie usunięty nie można tego cofnąć!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Contacts" = "Kontakty"; "Contacts" = "Kontakty";
@@ -1097,6 +1166,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Continue" = "Kontynuuj"; "Continue" = "Kontynuuj";
/* No comment provided by engineer. */
"Conversation deleted!" = "Rozmowa usunięta!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Copy" = "Kopiuj"; "Copy" = "Kopiuj";
@@ -1281,6 +1353,9 @@
swipe action */ swipe action */
"Delete" = "Usuń"; "Delete" = "Usuń";
/* No comment provided by engineer. */
"Delete %lld messages of members?" = "Usunąć %lld wiadomości członków?";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Delete %lld messages?" = "Usunąć %lld wiadomości?"; "Delete %lld messages?" = "Usunąć %lld wiadomości?";
@@ -1317,6 +1392,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Delete contact" = "Usuń kontakt"; "Delete contact" = "Usuń kontakt";
/* No comment provided by engineer. */
"Delete contact?" = "Usunąć kontakt?";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Delete database" = "Usuń bazę danych"; "Delete database" = "Usuń bazę danych";
@@ -1380,9 +1458,15 @@
/* server test step */ /* server test step */
"Delete queue" = "Usuń kolejkę"; "Delete queue" = "Usuń kolejkę";
/* No comment provided by engineer. */
"Delete up to 20 messages at once." = "Usuń do 20 wiadomości na raz.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Delete user profile?" = "Usunąć profil użytkownika?"; "Delete user profile?" = "Usunąć profil użytkownika?";
/* No comment provided by engineer. */
"Delete without notification" = "Usuń bez powiadomienia";
/* deleted chat item */ /* deleted chat item */
"deleted" = "usunięty"; "deleted" = "usunięty";
@@ -1425,9 +1509,15 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Desktop devices" = "Urządzenia komputerowe"; "Desktop devices" = "Urządzenia komputerowe";
/* No comment provided by engineer. */
"Destination server address of %@ is incompatible with forwarding server %@ settings." = "Adres serwera docelowego %@ jest niekompatybilny z ustawieniami serwera przekazującego %@.";
/* snd error text */ /* snd error text */
"Destination server error: %@" = "Błąd docelowego serwera: %@"; "Destination server error: %@" = "Błąd docelowego serwera: %@";
/* No comment provided by engineer. */
"Destination server version of %@ is incompatible with forwarding server %@." = "Wersja serwera docelowego %@ jest niekompatybilna z serwerem przekierowującym %@.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Detailed statistics" = "Szczegółowe statystyki"; "Detailed statistics" = "Szczegółowe statystyki";
@@ -1437,6 +1527,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Develop" = "Deweloperskie"; "Develop" = "Deweloperskie";
/* No comment provided by engineer. */
"Developer options" = "Opcje deweloperskie";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Developer tools" = "Narzędzia deweloperskie"; "Developer tools" = "Narzędzia deweloperskie";
@@ -1476,6 +1569,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"disabled" = "wyłączony"; "disabled" = "wyłączony";
/* No comment provided by engineer. */
"Disabled" = "Wyłączony";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Disappearing message" = "Znikająca wiadomość"; "Disappearing message" = "Znikająca wiadomość";
@@ -1623,6 +1719,9 @@
/* enabled status */ /* enabled status */
"enabled" = "włączone"; "enabled" = "włączone";
/* No comment provided by engineer. */
"Enabled" = "Włączony";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Enabled for" = "Włączony dla"; "Enabled for" = "Włączony dla";
@@ -1764,6 +1863,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Error changing setting" = "Błąd zmiany ustawienia"; "Error changing setting" = "Błąd zmiany ustawienia";
/* No comment provided by engineer. */
"Error connecting to forwarding server %@. Please try later." = "Błąd połączenia z serwerem przekierowania %@. Spróbuj ponownie później.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Error creating address" = "Błąd tworzenia adresu"; "Error creating address" = "Błąd tworzenia adresu";
@@ -2074,6 +2176,15 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Forwarded from" = "Przekazane dalej od"; "Forwarded from" = "Przekazane dalej od";
/* No comment provided by engineer. */
"Forwarding server %@ failed to connect to destination server %@. Please try later." = "Serwer przekazujący %@ nie mógł połączyć się z serwerem docelowym %@. Spróbuj ponownie później.";
/* No comment provided by engineer. */
"Forwarding server address is incompatible with network settings: %@." = "Adres serwera przekierowującego jest niekompatybilny z ustawieniami sieciowymi: %@.";
/* No comment provided by engineer. */
"Forwarding server version is incompatible with network settings: %@." = "Wersja serwera przekierowującego jest niekompatybilna z ustawieniami sieciowymi: %@.";
/* snd error text */ /* snd error text */
"Forwarding server: %@\nDestination server error: %@" = "Serwer przekazujący: %1$@\nBłąd serwera docelowego: %2$@"; "Forwarding server: %@\nDestination server error: %@" = "Serwer przekazujący: %1$@\nBłąd serwera docelowego: %2$@";
@@ -2425,6 +2536,9 @@
/* group name */ /* group name */
"invitation to group %@" = "zaproszenie do grupy %@"; "invitation to group %@" = "zaproszenie do grupy %@";
/* No comment provided by engineer. */
"invite" = "zaproś";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Invite friends" = "Zaproś znajomych"; "Invite friends" = "Zaproś znajomych";
@@ -2470,6 +2584,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"It can happen when:\n1. The messages expired in the sending client after 2 days or on the server after 30 days.\n2. Message decryption failed, because you or your contact used old database backup.\n3. The connection was compromised." = "Może to nastąpić, gdy:\n1. Wiadomości wygasły w wysyłającym kliencie po 2 dniach lub na serwerze po 30 dniach.\n2. Odszyfrowanie wiadomości nie powiodło się, ponieważ Ty lub Twój kontakt użyliście starej kopii zapasowej bazy danych.\n3. Połączenie zostało skompromitowane."; "It can happen when:\n1. The messages expired in the sending client after 2 days or on the server after 30 days.\n2. Message decryption failed, because you or your contact used old database backup.\n3. The connection was compromised." = "Może to nastąpić, gdy:\n1. Wiadomości wygasły w wysyłającym kliencie po 2 dniach lub na serwerze po 30 dniach.\n2. Odszyfrowanie wiadomości nie powiodło się, ponieważ Ty lub Twój kontakt użyliście starej kopii zapasowej bazy danych.\n3. Połączenie zostało skompromitowane.";
/* No comment provided by engineer. */
"It protects your IP address and connections." = "Chroni Twój adres IP i połączenia.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"It seems like you are already connected via this link. If it is not the case, there was an error (%@)." = "Wygląda na to, że jesteś już połączony przez ten link. Jeśli tak nie jest, wystąpił błąd (%@)."; "It seems like you are already connected via this link. If it is not the case, there was an error (%@)." = "Wygląda na to, że jesteś już połączony przez ten link. Jeśli tak nie jest, wystąpił błąd (%@).";
@@ -2512,6 +2629,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Keep" = "Zachowaj"; "Keep" = "Zachowaj";
/* No comment provided by engineer. */
"Keep conversation" = "Zachowaj rozmowę";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Keep the app open to use it from desktop" = "Zostaw aplikację otwartą i używaj ją z komputera"; "Keep the app open to use it from desktop" = "Zostaw aplikację otwartą i używaj ją z komputera";
@@ -2623,6 +2743,12 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Max 30 seconds, received instantly." = "Maksymalnie 30 sekund, odbierane natychmiast."; "Max 30 seconds, received instantly." = "Maksymalnie 30 sekund, odbierane natychmiast.";
/* No comment provided by engineer. */
"Media & file servers" = "Serwery mediów i plików";
/* blur media */
"Medium" = "Średni";
/* member role */ /* member role */
"member" = "członek"; "member" = "członek";
@@ -2650,6 +2776,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Menus" = "Menu"; "Menus" = "Menu";
/* No comment provided by engineer. */
"message" = "wiadomość";
/* item status text */ /* item status text */
"Message delivery error" = "Błąd dostarczenia wiadomości"; "Message delivery error" = "Błąd dostarczenia wiadomości";
@@ -2686,6 +2815,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Message reception" = "Odebranie wiadomości"; "Message reception" = "Odebranie wiadomości";
/* No comment provided by engineer. */
"Message servers" = "Serwery wiadomości";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Message source remains private." = "Źródło wiadomości pozostaje prywatne."; "Message source remains private." = "Źródło wiadomości pozostaje prywatne.";
@@ -2794,6 +2926,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Multiple chat profiles" = "Wiele profili czatu"; "Multiple chat profiles" = "Wiele profili czatu";
/* No comment provided by engineer. */
"mute" = "wycisz";
/* swipe action */ /* swipe action */
"Mute" = "Wycisz"; "Mute" = "Wycisz";
@@ -2827,6 +2962,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"New chat" = "Nowy czat"; "New chat" = "Nowy czat";
/* No comment provided by engineer. */
"New chat experience 🎉" = "Nowe możliwości czatu 🎉";
/* notification */ /* notification */
"New contact request" = "Nowa prośba o kontakt"; "New contact request" = "Nowa prośba o kontakt";
@@ -2845,6 +2983,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"New in %@" = "Nowość w %@"; "New in %@" = "Nowość w %@";
/* No comment provided by engineer. */
"New media options" = "Nowe opcje mediów";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"New member role" = "Nowa rola członka"; "New member role" = "Nowa rola członka";
@@ -2914,6 +3055,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Not compatible!" = "Nie kompatybilny!"; "Not compatible!" = "Nie kompatybilny!";
/* No comment provided by engineer. */
"Nothing selected" = "Nic nie jest zaznaczone";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Notifications" = "Powiadomienia"; "Notifications" = "Powiadomienia";
@@ -2970,6 +3114,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Tylko urządzenia klienckie przechowują profile użytkowników, kontakty, grupy i wiadomości wysyłane za pomocą **2-warstwowego szyfrowania end-to-end**."; "Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Tylko urządzenia klienckie przechowują profile użytkowników, kontakty, grupy i wiadomości wysyłane za pomocą **2-warstwowego szyfrowania end-to-end**.";
/* No comment provided by engineer. */
"Only delete conversation" = "Usuń tylko rozmowę";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Only group owners can change group preferences." = "Tylko właściciele grup mogą zmieniać preferencje grupy."; "Only group owners can change group preferences." = "Tylko właściciele grup mogą zmieniać preferencje grupy.";
@@ -3126,6 +3273,12 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"PING interval" = "Interwał PINGU"; "PING interval" = "Interwał PINGU";
/* No comment provided by engineer. */
"Play from the chat list." = "Odtwórz z listy czatów.";
/* No comment provided by engineer. */
"Please ask your contact to enable calls." = "Poproś kontakt o włącznie połączeń.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Please ask your contact to enable sending voice messages." = "Poproś Twój kontakt o włączenie wysyłania wiadomości głosowych."; "Please ask your contact to enable sending voice messages." = "Poproś Twój kontakt o włączenie wysyłania wiadomości głosowych.";
@@ -3306,6 +3459,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Rate the app" = "Oceń aplikację"; "Rate the app" = "Oceń aplikację";
/* No comment provided by engineer. */
"Reachable chat toolbar" = "Osiągalny pasek narzędzi czatu";
/* chat item menu */ /* chat item menu */
"React…" = "Reaguj…"; "React…" = "Reaguj…";
@@ -3493,6 +3649,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Reset" = "Resetuj"; "Reset" = "Resetuj";
/* No comment provided by engineer. */
"Reset all hints" = "Zresetuj wszystkie wskazówki";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Reset all statistics" = "Resetuj wszystkie statystyki"; "Reset all statistics" = "Resetuj wszystkie statystyki";
@@ -3568,6 +3727,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Save and notify group members" = "Zapisz i powiadom członków grupy"; "Save and notify group members" = "Zapisz i powiadom członków grupy";
/* No comment provided by engineer. */
"Save and reconnect" = "Zapisz i połącz ponownie";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Save and update group profile" = "Zapisz i zaktualizuj profil grupowy"; "Save and update group profile" = "Zapisz i zaktualizuj profil grupowy";
@@ -3643,6 +3805,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Scan server QR code" = "Zeskanuj kod QR serwera"; "Scan server QR code" = "Zeskanuj kod QR serwera";
/* No comment provided by engineer. */
"search" = "szukaj";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Search" = "Szukaj"; "Search" = "Szukaj";
@@ -3682,6 +3847,9 @@
/* chat item action */ /* chat item action */
"Select" = "Wybierz"; "Select" = "Wybierz";
/* No comment provided by engineer. */
"Selected %lld" = "Zaznaczono %lld";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Selected chat preferences prohibit this message." = "Wybrane preferencje czatu zabraniają tej wiadomości."; "Selected chat preferences prohibit this message." = "Wybrane preferencje czatu zabraniają tej wiadomości.";
@@ -3724,6 +3892,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Send live message" = "Wyślij wiadomość na żywo"; "Send live message" = "Wyślij wiadomość na żywo";
/* No comment provided by engineer. */
"Send message to enable calls." = "Wyślij wiadomość aby włączyć połączenia.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Send messages directly when IP address is protected and your or destination server does not support private routing." = "Wysyłaj wiadomości bezpośrednio, gdy adres IP jest chroniony i Twój lub docelowy serwer nie obsługuje prywatnego trasowania."; "Send messages directly when IP address is protected and your or destination server does not support private routing." = "Wysyłaj wiadomości bezpośrednio, gdy adres IP jest chroniony i Twój lub docelowy serwer nie obsługuje prywatnego trasowania.";
@@ -3904,12 +4075,18 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Share address with contacts?" = "Udostępnić adres kontaktom?"; "Share address with contacts?" = "Udostępnić adres kontaktom?";
/* No comment provided by engineer. */
"Share from other apps." = "Udostępnij z innych aplikacji.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Share link" = "Udostępnij link"; "Share link" = "Udostępnij link";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Share this 1-time invite link" = "Udostępnij ten jednorazowy link"; "Share this 1-time invite link" = "Udostępnij ten jednorazowy link";
/* No comment provided by engineer. */
"Share to SimpleX" = "Udostępnij do SimpleX";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Share with contacts" = "Udostępnij kontaktom"; "Share with contacts" = "Udostępnij kontaktom";
@@ -4003,9 +4180,18 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"SMP server" = "Serwer SMP"; "SMP server" = "Serwer SMP";
/* blur media */
"Soft" = "Łagodny";
/* No comment provided by engineer. */
"Some file(s) were not exported:" = "Niektóre plik(i) nie zostały wyeksportowane:";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Some non-fatal errors occurred during import - you may see Chat console for more details." = "Podczas importu wystąpiły niekrytyczne błędy - więcej szczegółów można znaleźć w konsoli czatu."; "Some non-fatal errors occurred during import - you may see Chat console for more details." = "Podczas importu wystąpiły niekrytyczne błędy - więcej szczegółów można znaleźć w konsoli czatu.";
/* No comment provided by engineer. */
"Some non-fatal errors occurred during import:" = "Podczas importu wystąpiły niekrytyczne błędy:";
/* notification title */ /* notification title */
"Somebody" = "Ktoś"; "Somebody" = "Ktoś";
@@ -4072,6 +4258,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"strike" = "strajk"; "strike" = "strajk";
/* blur media */
"Strong" = "Silne";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Submit" = "Zatwierdź"; "Submit" = "Zatwierdź";
@@ -4117,6 +4306,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Tap to scan" = "Dotknij, aby zeskanować"; "Tap to scan" = "Dotknij, aby zeskanować";
/* No comment provided by engineer. */
"TCP connection" = "Połączenie TCP";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"TCP connection timeout" = "Limit czasu połączenia TCP"; "TCP connection timeout" = "Limit czasu połączenia TCP";
@@ -4192,6 +4384,12 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"The message will be marked as moderated for all members." = "Wiadomość zostanie oznaczona jako moderowana dla wszystkich członków."; "The message will be marked as moderated for all members." = "Wiadomość zostanie oznaczona jako moderowana dla wszystkich członków.";
/* No comment provided by engineer. */
"The messages will be deleted for all members." = "Wiadomości zostaną usunięte dla wszystkich członków.";
/* No comment provided by engineer. */
"The messages will be marked as moderated for all members." = "Wiadomości zostaną oznaczone jako moderowane dla wszystkich członków.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"The next generation of private messaging" = "Następna generacja prywatnych wiadomości"; "The next generation of private messaging" = "Następna generacja prywatnych wiadomości";
@@ -4303,9 +4501,15 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Aby zweryfikować szyfrowanie end-to-end z Twoim kontaktem porównaj (lub zeskanuj) kod na waszych urządzeniach."; "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Aby zweryfikować szyfrowanie end-to-end z Twoim kontaktem porównaj (lub zeskanuj) kod na waszych urządzeniach.";
/* No comment provided by engineer. */
"Toggle chat list:" = "Przełącz listę czatów:";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Toggle incognito when connecting." = "Przełącz incognito przy połączeniu."; "Toggle incognito when connecting." = "Przełącz incognito przy połączeniu.";
/* No comment provided by engineer. */
"Toolbar opacity" = "Nieprzezroczystość paska narzędzi";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Total" = "Łącznie"; "Total" = "Łącznie";
@@ -4408,6 +4612,9 @@
/* authentication reason */ /* authentication reason */
"Unlock app" = "Odblokuj aplikację"; "Unlock app" = "Odblokuj aplikację";
/* No comment provided by engineer. */
"unmute" = "wyłącz wyciszenie";
/* swipe action */ /* swipe action */
"Unmute" = "Wyłącz wyciszenie"; "Unmute" = "Wyłącz wyciszenie";
@@ -4429,6 +4636,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Update network settings?" = "Zaktualizować ustawienia sieci?"; "Update network settings?" = "Zaktualizować ustawienia sieci?";
/* No comment provided by engineer. */
"Update settings?" = "Zaktualizować ustawienia?";
/* rcv group event chat item */ /* rcv group event chat item */
"updated group profile" = "zaktualizowano profil grupy"; "updated group profile" = "zaktualizowano profil grupy";
@@ -4498,6 +4708,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Use the app while in the call." = "Używaj aplikacji podczas połączenia."; "Use the app while in the call." = "Używaj aplikacji podczas połączenia.";
/* No comment provided by engineer. */
"Use the app with one hand." = "Korzystaj z aplikacji jedną ręką.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"User profile" = "Profil użytkownika"; "User profile" = "Profil użytkownika";
@@ -4552,6 +4765,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Via secure quantum resistant protocol." = "Dzięki bezpiecznemu protokołowi odpornego kwantowo."; "Via secure quantum resistant protocol." = "Dzięki bezpiecznemu protokołowi odpornego kwantowo.";
/* No comment provided by engineer. */
"video" = "wideo";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Video call" = "Połączenie wideo"; "Video call" = "Połączenie wideo";
@@ -4762,6 +4978,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You can accept calls from lock screen, without device and app authentication." = "Możesz przyjmować połączenia z ekranu blokady, bez uwierzytelniania urządzenia i aplikacji."; "You can accept calls from lock screen, without device and app authentication." = "Możesz przyjmować połączenia z ekranu blokady, bez uwierzytelniania urządzenia i aplikacji.";
/* No comment provided by engineer. */
"You can change it in Appearance settings." = "Możesz to zmienić w ustawieniach wyglądu.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You can create it later" = "Możesz go utworzyć później"; "You can create it later" = "Możesz go utworzyć później";
@@ -4783,6 +5002,9 @@
/* notification body */ /* notification body */
"You can now chat with %@" = "Możesz teraz wysyłać wiadomości do %@"; "You can now chat with %@" = "Możesz teraz wysyłać wiadomości do %@";
/* No comment provided by engineer. */
"You can send messages to %@ from Archived contacts." = "Możesz wysyłać wiadomości do %@ ze zarchiwizowanych kontaktów.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You can set lock screen notification preview via settings." = "Podgląd powiadomień na ekranie blokady można ustawić w ustawieniach."; "You can set lock screen notification preview via settings." = "Podgląd powiadomień na ekranie blokady można ustawić w ustawieniach.";
@@ -4798,6 +5020,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You can start chat via app Settings / Database or by restarting the app" = "Możesz rozpocząć czat poprzez Ustawienia aplikacji / Baza danych lub poprzez ponowne uruchomienie aplikacji"; "You can start chat via app Settings / Database or by restarting the app" = "Możesz rozpocząć czat poprzez Ustawienia aplikacji / Baza danych lub poprzez ponowne uruchomienie aplikacji";
/* No comment provided by engineer. */
"You can still view conversation with %@ in the list of chats." = "Nadal możesz przeglądać rozmowę z %@ na liście czatów.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You can turn on SimpleX Lock via Settings." = "Możesz włączyć blokadę SimpleX poprzez Ustawienia."; "You can turn on SimpleX Lock via Settings." = "Możesz włączyć blokadę SimpleX poprzez Ustawienia.";
@@ -4849,9 +5074,18 @@
/* snd group event chat item */ /* snd group event chat item */
"you left" = "wyszedłeś"; "you left" = "wyszedłeś";
/* No comment provided by engineer. */
"You may migrate the exported database." = "Możesz zmigrować wyeksportowaną bazy danych.";
/* No comment provided by engineer. */
"You may save the exported archive." = "Możesz zapisać wyeksportowane archiwum.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts." = "Musisz używać najnowszej wersji bazy danych czatu TYLKO na jednym urządzeniu, w przeciwnym razie możesz przestać otrzymywać wiadomości od niektórych kontaktów."; "You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts." = "Musisz używać najnowszej wersji bazy danych czatu TYLKO na jednym urządzeniu, w przeciwnym razie możesz przestać otrzymywać wiadomości od niektórych kontaktów.";
/* No comment provided by engineer. */
"You need to allow your contact to call to be able to call them." = "Aby móc dzwonić, musisz zezwolić kontaktowi na połączenia.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You need to allow your contact to send voice messages to be able to send them." = "Musisz zezwolić Twojemu kontaktowi na wysyłanie wiadomości głosowych, aby móc je wysyłać."; "You need to allow your contact to send voice messages to be able to send them." = "Musisz zezwolić Twojemu kontaktowi na wysyłanie wiadomości głosowych, aby móc je wysyłać.";
File diff suppressed because it is too large Load Diff
@@ -424,6 +424,7 @@ fun PreviewIncomingCallLockScreenAlert() {
) { ) {
IncomingCallLockScreenAlertLayout( IncomingCallLockScreenAlertLayout(
invitation = RcvCallInvitation( invitation = RcvCallInvitation(
callUUID = "",
remoteHostId = null, remoteHostId = null,
user = User.sampleData, user = User.sampleData,
contact = Contact.sampleData, contact = Contact.sampleData,
@@ -52,6 +52,9 @@ actual fun windowOrientation(): WindowOrientation = when (mainActivity.get()?.re
@Composable @Composable
actual fun windowWidth(): Dp = LocalConfiguration.current.screenWidthDp.dp actual fun windowWidth(): Dp = LocalConfiguration.current.screenWidthDp.dp
@Composable
actual fun windowHeight(): Dp = LocalConfiguration.current.screenHeightDp.dp
actual fun desktopExpandWindowToWidth(width: Dp) {} actual fun desktopExpandWindowToWidth(width: Dp) {}
actual fun isRtl(text: CharSequence): Boolean = BidiFormatter.getInstance().isRtl(text) actual fun isRtl(text: CharSequence): Boolean = BidiFormatter.getInstance().isRtl(text)
@@ -4,14 +4,21 @@ import androidx.compose.foundation.*
import androidx.compose.foundation.gestures.FlingBehavior import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.* import androidx.compose.foundation.lazy.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import chat.simplex.common.views.helpers.*
import kotlinx.coroutines.flow.filter
import kotlin.math.absoluteValue
@Composable @Composable
actual fun LazyColumnWithScrollBar( actual fun LazyColumnWithScrollBar(
modifier: Modifier, modifier: Modifier,
state: LazyListState, state: LazyListState?,
contentPadding: PaddingValues, contentPadding: PaddingValues,
reverseLayout: Boolean, reverseLayout: Boolean,
verticalArrangement: Arrangement.Vertical, verticalArrangement: Arrangement.Vertical,
@@ -20,7 +27,24 @@ actual fun LazyColumnWithScrollBar(
userScrollEnabled: Boolean, userScrollEnabled: Boolean,
content: LazyListScope.() -> Unit content: LazyListScope.() -> Unit
) { ) {
LazyColumn(modifier, state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content) val state = state ?: LocalAppBarHandler.current?.listState ?: rememberLazyListState()
val connection = LocalAppBarHandler.current?.connection
LaunchedEffect(Unit) {
snapshotFlow { state.firstVisibleItemScrollOffset }
.filter { state.firstVisibleItemIndex == 0 }
.collect { scrollPosition ->
val offset = connection?.appBarOffset
if (offset != null && (offset + scrollPosition).absoluteValue > 1) {
connection.appBarOffset = -scrollPosition.toFloat()
// Log.d(TAG, "Scrolling position changed from $offset to ${connection.appBarOffset}")
}
}
}
if (connection != null) {
LazyColumn(modifier.nestedScroll(connection), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content)
} else {
LazyColumn(modifier, state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content)
}
} }
@Composable @Composable
@@ -28,8 +52,34 @@ actual fun ColumnWithScrollBar(
modifier: Modifier, modifier: Modifier,
verticalArrangement: Arrangement.Vertical, verticalArrangement: Arrangement.Vertical,
horizontalAlignment: Alignment.Horizontal, horizontalAlignment: Alignment.Horizontal,
state: ScrollState, state: ScrollState?,
content: @Composable ColumnScope.() -> Unit maxIntrinsicSize: Boolean,
content: @Composable() (ColumnScope.() -> Unit)
) { ) {
Column(modifier.verticalScroll(rememberScrollState()), verticalArrangement, horizontalAlignment, content) val state = state ?: LocalAppBarHandler.current?.scrollState ?: rememberScrollState()
val connection = LocalAppBarHandler.current?.connection
LaunchedEffect(Unit) {
snapshotFlow { state.value }
.collect { scrollPosition ->
val offset = connection?.appBarOffset
if (offset != null && (offset + scrollPosition).absoluteValue > 1) {
connection.appBarOffset = -scrollPosition.toFloat()
// Log.d(TAG, "Scrolling position changed from $offset to ${connection.appBarOffset}")
}
}
}
if (connection != null) {
Column(
if (maxIntrinsicSize) {
modifier.nestedScroll(connection).verticalScroll(state).height(IntrinsicSize.Max)
} else {
modifier.nestedScroll(connection).verticalScroll(state)
}, verticalArrangement, horizontalAlignment, content)
} else {
Column(if (maxIntrinsicSize) {
modifier.verticalScroll(state).height(IntrinsicSize.Max)
} else {
modifier.verticalScroll(state)
}, verticalArrangement, horizontalAlignment, content)
}
} }
@@ -457,7 +457,7 @@ private fun DisabledBackgroundCallsButton() {
) { ) {
Text(stringResource(MR.strings.system_restricted_background_in_call_title), color = WarningOrange) Text(stringResource(MR.strings.system_restricted_background_in_call_title), color = WarningOrange)
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(8.dp))
IconButton(onClick = { show = false }, Modifier.size(24.dp)) { IconButton(onClick = { show = false }, Modifier.size(22.dp)) {
Icon(painterResource(MR.images.ic_close), null, tint = WarningOrange) Icon(painterResource(MR.images.ic_close), null, tint = WarningOrange)
} }
} }
@@ -539,7 +539,7 @@ fun CallPermissionsView(pipActive: Boolean, hasVideo: Boolean, cancel: () -> Uni
Icon( Icon(
painterResource(MR.images.ic_call_500), painterResource(MR.images.ic_call_500),
stringResource(MR.strings.permissions_record_audio), stringResource(MR.strings.permissions_record_audio),
Modifier.size(24.dp), Modifier.size(22.dp),
tint = Color(0xFFFFFFD8) tint = Color(0xFFFFFFD8)
) )
} }
@@ -547,7 +547,7 @@ fun CallPermissionsView(pipActive: Boolean, hasVideo: Boolean, cancel: () -> Uni
Icon( Icon(
painterResource(MR.images.ic_videocam), painterResource(MR.images.ic_videocam),
stringResource(MR.strings.permissions_camera), stringResource(MR.strings.permissions_camera),
Modifier.size(24.dp), Modifier.size(22.dp),
tint = Color(0xFFFFFFD8) tint = Color(0xFFFFFFD8)
) )
} }
@@ -770,6 +770,7 @@ fun PreviewActiveCallOverlayVideo() {
callState = CallState.Negotiated, callState = CallState.Negotiated,
localMedia = CallMediaType.Video, localMedia = CallMediaType.Video,
peerMedia = CallMediaType.Video, peerMedia = CallMediaType.Video,
callUUID = "",
connectionInfo = ConnectionInfo( connectionInfo = ConnectionInfo(
RTCIceCandidate(RTCIceCandidateType.Host, "tcp"), RTCIceCandidate(RTCIceCandidateType.Host, "tcp"),
RTCIceCandidate(RTCIceCandidateType.Host, "tcp") RTCIceCandidate(RTCIceCandidateType.Host, "tcp")
@@ -799,6 +800,7 @@ fun PreviewActiveCallOverlayAudio() {
callState = CallState.Negotiated, callState = CallState.Negotiated,
localMedia = CallMediaType.Audio, localMedia = CallMediaType.Audio,
peerMedia = CallMediaType.Audio, peerMedia = CallMediaType.Audio,
callUUID = "",
connectionInfo = ConnectionInfo( connectionInfo = ConnectionInfo(
RTCIceCandidate(RTCIceCandidateType.Host, "udp"), RTCIceCandidate(RTCIceCandidateType.Host, "udp"),
RTCIceCandidate(RTCIceCandidateType.Host, "udp") RTCIceCandidate(RTCIceCandidateType.Host, "udp")
@@ -14,6 +14,7 @@ actual fun authenticate(
promptSubtitle: String, promptSubtitle: String,
selfDestruct: Boolean, selfDestruct: Boolean,
usingLAMode: LAMode, usingLAMode: LAMode,
oneTime: Boolean,
completed: (LAResult) -> Unit completed: (LAResult) -> Unit
) { ) {
val activity = mainActivity.get() ?: return completed(LAResult.Error("")) val activity = mainActivity.get() ?: return completed(LAResult.Error(""))
@@ -27,7 +28,7 @@ actual fun authenticate(
else -> completed(LAResult.Unavailable()) else -> completed(LAResult.Unavailable())
} }
LAMode.PASSCODE -> { LAMode.PASSCODE -> {
authenticateWithPasscode(promptTitle, promptSubtitle, selfDestruct, completed) authenticateWithPasscode(promptTitle, promptSubtitle, selfDestruct, oneTime, completed)
} }
} }
} }
@@ -13,12 +13,13 @@ import androidx.compose.foundation.*
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme.colors import androidx.compose.material.MaterialTheme.colors
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import dev.icerock.moko.resources.compose.stringResource import dev.icerock.moko.resources.compose.stringResource
@@ -30,7 +31,6 @@ import chat.simplex.common.model.ChatModel
import chat.simplex.common.platform.* import chat.simplex.common.platform.*
import chat.simplex.common.helpers.APPLICATION_ID import chat.simplex.common.helpers.APPLICATION_ID
import chat.simplex.common.helpers.saveAppLocale import chat.simplex.common.helpers.saveAppLocale
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.res.MR import chat.simplex.res.MR
import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.ImageResource
import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.painterResource
@@ -79,7 +79,7 @@ fun AppearanceScope.AppearanceLayout(
Modifier.fillMaxWidth(), Modifier.fillMaxWidth(),
) { ) {
AppBarTitle(stringResource(MR.strings.appearance_settings)) AppBarTitle(stringResource(MR.strings.appearance_settings))
SectionView(stringResource(MR.strings.settings_section_title_interface), padding = PaddingValues()) { SectionView(stringResource(MR.strings.settings_section_title_interface), contentPadding = PaddingValues()) {
val context = LocalContext.current val context = LocalContext.current
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// SectionItemWithValue( // SectionItemWithValue(
@@ -112,15 +112,15 @@ fun AppearanceScope.AppearanceLayout(
} }
} }
SectionDividerSpaced(maxTopPadding = true) SectionDividerSpaced()
ThemesSection(systemDarkTheme) ThemesSection(systemDarkTheme)
SectionDividerSpaced(maxTopPadding = true) SectionDividerSpaced()
ProfileImageSection() ProfileImageSection()
SectionDividerSpaced() SectionDividerSpaced(maxTopPadding = true)
SectionView(stringResource(MR.strings.settings_section_title_icon), padding = PaddingValues(horizontal = DEFAULT_PADDING_HALF)) { SectionView(stringResource(MR.strings.settings_section_title_icon), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING_HALF)) {
LazyRow { LazyRow {
items(AppIcon.values().size, { index -> AppIcon.values()[index] }) { index -> items(AppIcon.values().size, { index -> AppIcon.values()[index] }) { index ->
val item = AppIcon.values()[index] val item = AppIcon.values()[index]
@@ -129,7 +129,8 @@ fun AppearanceScope.AppearanceLayout(
contentDescription = "", contentDescription = "",
contentScale = ContentScale.Fit, contentScale = ContentScale.Fit,
modifier = Modifier modifier = Modifier
.shadow(if (item == icon.value) 1.dp else 0.dp, ambientColor = colors.secondaryVariant) .border(1.dp, color = if (item == icon.value) colors.secondaryVariant else Color.Transparent, RoundedCornerShape(percent = 22))
.clip(RoundedCornerShape(percent = 22))
.size(70.dp) .size(70.dp)
.clickable { changeIcon(item) } .clickable { changeIcon(item) }
.padding(10.dp) .padding(10.dp)
@@ -143,7 +144,7 @@ fun AppearanceScope.AppearanceLayout(
} }
} }
SectionDividerSpaced(maxBottomPadding = true) SectionDividerSpaced(maxTopPadding = true)
FontScaleSection() FontScaleSection()
SectionBottomSpacer() SectionBottomSpacer()
@@ -19,9 +19,9 @@ actual fun SettingsSectionApp(
withAuth: (title: String, desc: String, block: () -> Unit) -> Unit withAuth: (title: String, desc: String, block: () -> Unit) -> Unit
) { ) {
SectionView(stringResource(MR.strings.settings_section_title_app)) { SectionView(stringResource(MR.strings.settings_section_title_app)) {
SettingsActionItem(painterResource(MR.images.ic_restart_alt), stringResource(MR.strings.settings_restart_app), ::restartApp, extraPadding = true) SettingsActionItem(painterResource(MR.images.ic_restart_alt), stringResource(MR.strings.settings_restart_app), ::restartApp)
SettingsActionItem(painterResource(MR.images.ic_power_settings_new), stringResource(MR.strings.settings_shutdown), { shutdownAppAlert(::shutdownApp) }, extraPadding = true) SettingsActionItem(painterResource(MR.images.ic_power_settings_new), stringResource(MR.strings.settings_shutdown), { shutdownAppAlert(::shutdownApp) })
SettingsActionItem(painterResource(MR.images.ic_code), stringResource(MR.strings.settings_developer_tools), showSettingsModal { DeveloperView(it, showCustomModal, withAuth) }, extraPadding = true) SettingsActionItem(painterResource(MR.images.ic_code), stringResource(MR.strings.settings_developer_tools), showSettingsModal { DeveloperView(it, showCustomModal, withAuth) })
AppVersionItem(showVersion) AppVersionItem(showVersion)
} }
} }
@@ -17,6 +17,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import chat.simplex.common.views.usersettings.SetDeliveryReceiptsView import chat.simplex.common.views.usersettings.SetDeliveryReceiptsView
import chat.simplex.common.model.* import chat.simplex.common.model.*
@@ -50,6 +51,7 @@ data class SettingsViewState(
@Composable @Composable
fun AppScreen() { fun AppScreen() {
AppBarHandler.appBarMaxHeightPx = with(LocalDensity.current) { AppBarHeight.roundToPx() }
SimpleXTheme { SimpleXTheme {
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
Surface(color = MaterialTheme.colors.background, contentColor = LocalContentColor.current) { Surface(color = MaterialTheme.colors.background, contentColor = LocalContentColor.current) {
@@ -72,6 +74,7 @@ fun MainScreen() {
LaunchedEffect(showAdvertiseLAAlert) { LaunchedEffect(showAdvertiseLAAlert) {
if ( if (
!chatModel.controller.appPrefs.laNoticeShown.get() !chatModel.controller.appPrefs.laNoticeShown.get()
&& !appPrefs.performLA.get()
&& showAdvertiseLAAlert && showAdvertiseLAAlert
&& chatModel.controller.appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete && chatModel.controller.appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete
&& chatModel.chats.size > 3 && chatModel.chats.size > 3
@@ -209,10 +212,8 @@ fun MainScreen() {
} else { } else {
ActiveCallView() ActiveCallView()
} }
} else {
// It's needed for privacy settings toggle, so it can be shown even if the app is passcode unlocked
ModalManager.fullscreen.showPasscodeInView()
} }
ModalManager.fullscreen.showOneTimePasscodeInView()
AlertManager.privacySensitive.showInView() AlertManager.privacySensitive.showInView()
if (onboarding == OnboardingStage.OnboardingComplete) { if (onboarding == OnboardingStage.OnboardingComplete) {
LaunchedEffect(chatModel.currentUser.value, chatModel.appOpenUrl.value) { LaunchedEffect(chatModel.currentUser.value, chatModel.appOpenUrl.value) {
@@ -5,6 +5,7 @@ import androidx.compose.material.*
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import chat.simplex.common.model.* import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.platform.* import chat.simplex.common.platform.*
import chat.simplex.common.views.helpers.* import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.localauth.SetAppPasscodeView import chat.simplex.common.views.localauth.SetAppPasscodeView
@@ -31,7 +32,7 @@ object AppLock {
fun showLANotice(laNoticeShown: SharedPreference<Boolean>) { fun showLANotice(laNoticeShown: SharedPreference<Boolean>) {
Log.d(TAG, "showLANotice") Log.d(TAG, "showLANotice")
if (!laNoticeShown.get()) { if (!laNoticeShown.get() && !appPrefs.performLA.get()) {
laNoticeShown.set(true) laNoticeShown.set(true)
AlertManager.shared.showAlertDialog( AlertManager.shared.showAlertDialog(
title = generalGetString(MR.strings.la_notice_title_simplex_lock), title = generalGetString(MR.strings.la_notice_title_simplex_lock),
@@ -57,6 +58,8 @@ object AppLock {
private fun showChooseLAMode() { private fun showChooseLAMode() {
Log.d(TAG, "showLANotice") Log.d(TAG, "showLANotice")
if (appPrefs.performLA.get()) return
AlertManager.shared.showAlertDialogStacked( AlertManager.shared.showAlertDialogStacked(
title = generalGetString(MR.strings.la_lock_mode), title = generalGetString(MR.strings.la_lock_mode),
text = null, text = null,
@@ -80,21 +83,23 @@ object AppLock {
authenticate( authenticate(
generalGetString(MR.strings.auth_enable_simplex_lock), generalGetString(MR.strings.auth_enable_simplex_lock),
generalGetString(MR.strings.auth_confirm_credential), generalGetString(MR.strings.auth_confirm_credential),
oneTime = true,
completed = { laResult -> completed = { laResult ->
when (laResult) { when (laResult) {
LAResult.Success -> { LAResult.Success -> {
m.performLA.value = true m.showAuthScreen.value = true
appPrefs.performLA.set(true) appPrefs.performLA.set(true)
laTurnedOnAlert() laTurnedOnAlert()
} }
is LAResult.Failed -> { /* Can be called multiple times on every failure */ } is LAResult.Failed -> { /* Can be called multiple times on every failure */ }
is LAResult.Error -> { is LAResult.Error -> {
m.performLA.value = false m.showAuthScreen.value = false
appPrefs.performLA.set(false) // Don't drop auth pref in case of state inconsistency (eg, you have set passcode but somehow bypassed toggle and turned it off and then on)
// appPrefs.performLA.set(false)
laFailedAlert() laFailedAlert()
} }
is LAResult.Unavailable -> { is LAResult.Unavailable -> {
m.performLA.value = false m.showAuthScreen.value = false
appPrefs.performLA.set(false) appPrefs.performLA.set(false)
m.showAdvertiseLAUnavailableAlert.value = true m.showAdvertiseLAUnavailableAlert.value = true
} }
@@ -104,19 +109,22 @@ object AppLock {
} }
private fun setPasscode() { private fun setPasscode() {
if (appPrefs.performLA.get()) return
val appPrefs = ChatController.appPrefs val appPrefs = ChatController.appPrefs
ModalManager.fullscreen.showCustomModal { close -> ModalManager.fullscreen.showCustomModal { close ->
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background, contentColor = LocalContentColor.current) { Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background, contentColor = LocalContentColor.current) {
SetAppPasscodeView( SetAppPasscodeView(
submit = { submit = {
ChatModel.performLA.value = true ChatModel.showAuthScreen.value = true
appPrefs.performLA.set(true) appPrefs.performLA.set(true)
appPrefs.laMode.set(LAMode.PASSCODE) appPrefs.laMode.set(LAMode.PASSCODE)
laTurnedOnAlert() laTurnedOnAlert()
}, },
cancel = { cancel = {
ChatModel.performLA.value = false ChatModel.showAuthScreen.value = false
appPrefs.performLA.set(false) // Don't drop auth pref in case of state inconsistency (eg, you have set passcode but somehow bypassed toggle and turned it off and then on)
// appPrefs.performLA.set(false)
laPasscodeNotSetAlert() laPasscodeNotSetAlert()
}, },
close = close close = close
@@ -147,6 +155,7 @@ object AppLock {
else else
generalGetString(MR.strings.auth_unlock), generalGetString(MR.strings.auth_unlock),
selfDestruct = true, selfDestruct = true,
oneTime = false,
completed = { laResult -> completed = { laResult ->
when (laResult) { when (laResult) {
LAResult.Success -> LAResult.Success ->
@@ -160,7 +169,7 @@ object AppLock {
} }
is LAResult.Unavailable -> { is LAResult.Unavailable -> {
userAuthorized.value = true userAuthorized.value = true
m.performLA.value = false m.showAuthScreen.value = false
m.controller.appPrefs.performLA.set(false) m.controller.appPrefs.performLA.set(false)
laUnavailableTurningOffAlert() laUnavailableTurningOffAlert()
} }
@@ -192,22 +201,23 @@ object AppLock {
generalGetString(MR.strings.auth_confirm_credential) generalGetString(MR.strings.auth_confirm_credential)
else else
"", "",
oneTime = true,
completed = { laResult -> completed = { laResult ->
val prefPerformLA = m.controller.appPrefs.performLA val prefPerformLA = m.controller.appPrefs.performLA
when (laResult) { when (laResult) {
LAResult.Success -> { LAResult.Success -> {
m.performLA.value = true m.showAuthScreen.value = true
prefPerformLA.set(true) prefPerformLA.set(true)
laTurnedOnAlert() laTurnedOnAlert()
} }
is LAResult.Failed -> { /* Can be called multiple times on every failure */ } is LAResult.Failed -> { /* Can be called multiple times on every failure */ }
is LAResult.Error -> { is LAResult.Error -> {
m.performLA.value = false m.showAuthScreen.value = false
prefPerformLA.set(false) prefPerformLA.set(false)
laFailedAlert() laFailedAlert()
} }
is LAResult.Unavailable -> { is LAResult.Unavailable -> {
m.performLA.value = false m.showAuthScreen.value = false
prefPerformLA.set(false) prefPerformLA.set(false)
laUnavailableInstructionAlert() laUnavailableInstructionAlert()
} }
@@ -227,12 +237,13 @@ object AppLock {
generalGetString(MR.strings.auth_confirm_credential) generalGetString(MR.strings.auth_confirm_credential)
else else
generalGetString(MR.strings.auth_disable_simplex_lock), generalGetString(MR.strings.auth_disable_simplex_lock),
oneTime = true,
completed = { laResult -> completed = { laResult ->
val prefPerformLA = m.controller.appPrefs.performLA val prefPerformLA = m.controller.appPrefs.performLA
val selfDestructPref = m.controller.appPrefs.selfDestruct val selfDestructPref = m.controller.appPrefs.selfDestruct
when (laResult) { when (laResult) {
LAResult.Success -> { LAResult.Success -> {
m.performLA.value = false m.showAuthScreen.value = false
prefPerformLA.set(false) prefPerformLA.set(false)
DatabaseUtils.ksAppPassword.remove() DatabaseUtils.ksAppPassword.remove()
selfDestructPref.set(false) selfDestructPref.set(false)
@@ -240,12 +251,12 @@ object AppLock {
} }
is LAResult.Failed -> { /* Can be called multiple times on every failure */ } is LAResult.Failed -> { /* Can be called multiple times on every failure */ }
is LAResult.Error -> { is LAResult.Error -> {
m.performLA.value = true m.showAuthScreen.value = true
prefPerformLA.set(true) prefPerformLA.set(true)
laFailedAlert() laFailedAlert()
} }
is LAResult.Unavailable -> { is LAResult.Unavailable -> {
m.performLA.value = false m.showAuthScreen.value = false
prefPerformLA.set(false) prefPerformLA.set(false)
laUnavailableTurningOffAlert() laUnavailableTurningOffAlert()
} }
@@ -20,7 +20,6 @@ import dev.icerock.moko.resources.ImageResource
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.internal.ChannelFlow
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.datetime.* import kotlinx.datetime.*
@@ -98,7 +97,7 @@ object ChatModel {
} }
) )
} }
val performLA by lazy { mutableStateOf(ChatController.appPrefs.performLA.get()) } val showAuthScreen by lazy { mutableStateOf(ChatController.appPrefs.performLA.get()) }
val showAdvertiseLAUnavailableAlert = mutableStateOf(false) val showAdvertiseLAUnavailableAlert = mutableStateOf(false)
val showChatPreviews by lazy { mutableStateOf(ChatController.appPrefs.privacyShowChatPreviews.get()) } val showChatPreviews by lazy { mutableStateOf(ChatController.appPrefs.privacyShowChatPreviews.get()) }
@@ -985,6 +984,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val fullName get() = contact.fullName override val fullName get() = contact.fullName
override val image get() = contact.image override val image get() = contact.image
override val localAlias: String get() = contact.localAlias override val localAlias: String get() = contact.localAlias
override fun anyNameContains(searchAnyCase: String): Boolean = contact.anyNameContains(searchAnyCase)
companion object { companion object {
val sampleData = Direct(Contact.sampleData) val sampleData = Direct(Contact.sampleData)
@@ -1219,6 +1219,12 @@ data class Contact(
override val localAlias get() = profile.localAlias override val localAlias get() = profile.localAlias
val verified get() = activeConn?.connectionCode != null val verified get() = activeConn?.connectionCode != null
override fun anyNameContains(searchAnyCase: String): Boolean {
val s = searchAnyCase.trim().lowercase()
return profile.chatViewName.lowercase().contains(s) || profile.displayName.lowercase().contains(s) || profile.fullName.lowercase().contains(s)
}
val directOrUsed: Boolean get() = val directOrUsed: Boolean get() =
if (activeConn != null) { if (activeConn != null) {
(activeConn.connLevel == 0 && !activeConn.viaGroupLink) || contactUsed (activeConn.connLevel == 0 && !activeConn.viaGroupLink) || contactUsed
@@ -1030,14 +1030,14 @@ object ChatController {
return null return null
} }
suspend fun apiContactQueueInfo(rh: Long?, contactId: Long): Pair<RcvMsgInfo?, QueueInfo>? { suspend fun apiContactQueueInfo(rh: Long?, contactId: Long): Pair<RcvMsgInfo?, ServerQueueInfo>? {
val r = sendCmd(rh, CC.APIContactQueueInfo(contactId)) val r = sendCmd(rh, CC.APIContactQueueInfo(contactId))
if (r is CR.QueueInfoR) return Pair(r.rcvMsgInfo, r.queueInfo) if (r is CR.QueueInfoR) return Pair(r.rcvMsgInfo, r.queueInfo)
apiErrorAlert("apiContactQueueInfo", generalGetString(MR.strings.error), r) apiErrorAlert("apiContactQueueInfo", generalGetString(MR.strings.error), r)
return null return null
} }
suspend fun apiGroupMemberQueueInfo(rh: Long?, groupId: Long, groupMemberId: Long): Pair<RcvMsgInfo?, QueueInfo>? { suspend fun apiGroupMemberQueueInfo(rh: Long?, groupId: Long, groupMemberId: Long): Pair<RcvMsgInfo?, ServerQueueInfo>? {
val r = sendCmd(rh, CC.APIGroupMemberQueueInfo(groupId, groupMemberId)) val r = sendCmd(rh, CC.APIGroupMemberQueueInfo(groupId, groupMemberId))
if (r is CR.QueueInfoR) return Pair(r.rcvMsgInfo, r.queueInfo) if (r is CR.QueueInfoR) return Pair(r.rcvMsgInfo, r.queueInfo)
apiErrorAlert("apiGroupMemberQueueInfo", generalGetString(MR.strings.error), r) apiErrorAlert("apiGroupMemberQueueInfo", generalGetString(MR.strings.error), r)
@@ -4734,7 +4734,7 @@ sealed class CR {
@Serializable @SerialName("networkConfig") class NetworkConfig(val networkConfig: NetCfg): CR() @Serializable @SerialName("networkConfig") class NetworkConfig(val networkConfig: NetCfg): CR()
@Serializable @SerialName("contactInfo") class ContactInfo(val user: UserRef, val contact: Contact, val connectionStats_: ConnectionStats? = null, val customUserProfile: Profile? = null): CR() @Serializable @SerialName("contactInfo") class ContactInfo(val user: UserRef, val contact: Contact, val connectionStats_: ConnectionStats? = null, val customUserProfile: Profile? = null): CR()
@Serializable @SerialName("groupMemberInfo") class GroupMemberInfo(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val connectionStats_: ConnectionStats? = null): CR() @Serializable @SerialName("groupMemberInfo") class GroupMemberInfo(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val connectionStats_: ConnectionStats? = null): CR()
@Serializable @SerialName("queueInfo") class QueueInfoR(val user: UserRef, val rcvMsgInfo: RcvMsgInfo?, val queueInfo: QueueInfo): CR() @Serializable @SerialName("queueInfo") class QueueInfoR(val user: UserRef, val rcvMsgInfo: RcvMsgInfo?, val queueInfo: ServerQueueInfo): CR()
@Serializable @SerialName("contactSwitchStarted") class ContactSwitchStarted(val user: UserRef, val contact: Contact, val connectionStats: ConnectionStats): CR() @Serializable @SerialName("contactSwitchStarted") class ContactSwitchStarted(val user: UserRef, val contact: Contact, val connectionStats: ConnectionStats): CR()
@Serializable @SerialName("groupMemberSwitchStarted") class GroupMemberSwitchStarted(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val connectionStats: ConnectionStats): CR() @Serializable @SerialName("groupMemberSwitchStarted") class GroupMemberSwitchStarted(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val connectionStats: ConnectionStats): CR()
@Serializable @SerialName("contactSwitchAborted") class ContactSwitchAborted(val user: UserRef, val contact: Contact, val connectionStats: ConnectionStats): CR() @Serializable @SerialName("contactSwitchAborted") class ContactSwitchAborted(val user: UserRef, val contact: Contact, val connectionStats: ConnectionStats): CR()
@@ -6409,6 +6409,16 @@ data class RcvMsgInfo (
val agentMsgMeta: String val agentMsgMeta: String
) )
@Serializable
data class ServerQueueInfo (
val server: String,
val rcvId: String,
val sndId: String,
val ntfId: String? = null,
val status: String,
val info: QueueInfo
)
@Serializable @Serializable
data class QueueInfo ( data class QueueInfo (
val qiSnd: Boolean, val qiSnd: Boolean,
@@ -28,9 +28,6 @@ interface PlatformInterface {
fun androidRestartNetworkObserver() {} fun androidRestartNetworkObserver() {}
@Composable fun androidLockPortraitOrientation() {} @Composable fun androidLockPortraitOrientation() {}
suspend fun androidAskToAllowBackgroundCalls(): Boolean = true suspend fun androidAskToAllowBackgroundCalls(): Boolean = true
@Composable fun desktopScrollBarComponents(): Triple<Animatable<Float, AnimationVector1D>, Modifier, MutableState<Job>> = remember { Triple(Animatable(0f), Modifier, mutableStateOf(Job())) }
@Composable fun desktopScrollBar(state: LazyListState, modifier: Modifier, scrollBarAlpha: Animatable<Float, AnimationVector1D>, scrollJob: MutableState<Job>, reversed: Boolean) {}
@Composable fun desktopScrollBar(state: ScrollState, modifier: Modifier, scrollBarAlpha: Animatable<Float, AnimationVector1D>, scrollJob: MutableState<Job>, reversed: Boolean) {}
@Composable fun desktopShowAppUpdateNotice() {} @Composable fun desktopShowAppUpdateNotice() {}
} }
/** /**
@@ -30,6 +30,9 @@ expect fun windowOrientation(): WindowOrientation
@Composable @Composable
expect fun windowWidth(): Dp expect fun windowWidth(): Dp
@Composable
expect fun windowHeight(): Dp
expect fun desktopExpandWindowToWidth(width: Dp) expect fun desktopExpandWindowToWidth(width: Dp)
expect fun isRtl(text: CharSequence): Boolean expect fun isRtl(text: CharSequence): Boolean
@@ -13,7 +13,7 @@ import androidx.compose.ui.unit.dp
@Composable @Composable
expect fun LazyColumnWithScrollBar( expect fun LazyColumnWithScrollBar(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
state: LazyListState = rememberLazyListState(), state: LazyListState? = null,
contentPadding: PaddingValues = PaddingValues(0.dp), contentPadding: PaddingValues = PaddingValues(0.dp),
reverseLayout: Boolean = false, reverseLayout: Boolean = false,
verticalArrangement: Arrangement.Vertical = verticalArrangement: Arrangement.Vertical =
@@ -29,6 +29,8 @@ expect fun ColumnWithScrollBar(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
verticalArrangement: Arrangement.Vertical = Arrangement.Top, verticalArrangement: Arrangement.Vertical = Arrangement.Top,
horizontalAlignment: Alignment.Horizontal = Alignment.Start, horizontalAlignment: Alignment.Horizontal = Alignment.Start,
state: ScrollState = rememberScrollState(), state: ScrollState? = null,
// set true when you want to show something in the center with respected .fillMaxSize()
maxIntrinsicSize: Boolean = false,
content: @Composable ColumnScope.() -> Unit content: @Composable ColumnScope.() -> Unit
) )
@@ -607,6 +607,8 @@ val DEFAULT_SPACE_AFTER_ICON = 4.dp
val DEFAULT_PADDING_HALF = DEFAULT_PADDING / 2 val DEFAULT_PADDING_HALF = DEFAULT_PADDING / 2
val DEFAULT_BOTTOM_PADDING = 48.dp val DEFAULT_BOTTOM_PADDING = 48.dp
val DEFAULT_BOTTOM_BUTTON_PADDING = 20.dp val DEFAULT_BOTTOM_BUTTON_PADDING = 20.dp
val DEFAULT_MIN_SECTION_ITEM_HEIGHT = 50.dp
val DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL = 15.dp
val DEFAULT_START_MODAL_WIDTH = 388.dp val DEFAULT_START_MODAL_WIDTH = 388.dp
val DEFAULT_MIN_CENTER_MODAL_WIDTH = 590.dp val DEFAULT_MIN_CENTER_MODAL_WIDTH = 590.dp
@@ -49,7 +49,9 @@ object ThemeManager {
return perUserTheme return perUserTheme
} }
val defaultTheme = defaultActiveTheme(appSettingsTheme) val defaultTheme = defaultActiveTheme(appSettingsTheme)
return ThemeModeOverride(colors = defaultTheme?.colors ?: ThemeColors(), wallpaper = defaultTheme?.wallpaper) return ThemeModeOverride(colors = defaultTheme?.colors ?: ThemeColors(), wallpaper = defaultTheme?.wallpaper
// Situation when user didn't change global theme at all (it is not saved yet). Using defaults
?: ThemeWallpaper.from(PresetWallpaper.SCHOOL.toType(CurrentColors.value.base), null, null))
} }
fun currentColors(themeOverridesForType: WallpaperType?, perChatTheme: ThemeModeOverride?, perUserTheme: ThemeModeOverrides?, appSettingsTheme: List<ThemeOverrides>): ActiveTheme { fun currentColors(themeOverridesForType: WallpaperType?, perChatTheme: ThemeModeOverride?, perUserTheme: ThemeModeOverrides?, appSettingsTheme: List<ThemeOverrides>): ActiveTheme {
@@ -125,19 +125,13 @@ fun TerminalLayout(
} }
} }
private var lazyListState = 0 to 0
@Composable @Composable
fun TerminalLog() { fun TerminalLog() {
val listState = rememberLazyListState(lazyListState.first, lazyListState.second)
DisposableEffect(Unit) {
onDispose { lazyListState = listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset }
}
val reversedTerminalItems by remember { val reversedTerminalItems by remember {
derivedStateOf { chatModel.terminalItems.value.asReversed() } derivedStateOf { chatModel.terminalItems.value.asReversed() }
} }
val clipboard = LocalClipboardManager.current val clipboard = LocalClipboardManager.current
LazyColumnWithScrollBar(state = listState, reverseLayout = true) { LazyColumnWithScrollBar(reverseLayout = true) {
items(reversedTerminalItems) { item -> items(reversedTerminalItems) { item ->
val rhId = item.remoteHostId val rhId = item.remoteHostId
val rhIdStr = if (rhId == null) "" else "$rhId " val rhIdStr = if (rhId == null) "" else "$rhId "
@@ -111,53 +111,59 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) {
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
val keyboardState by getKeyboardState() val keyboardState by getKeyboardState()
var savedKeyboardState by remember { mutableStateOf(keyboardState) } var savedKeyboardState by remember { mutableStateOf(keyboardState) }
val handler = remember { AppBarHandler() }
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { CompositionLocalProvider(
Column( LocalAppBarHandler provides handler
modifier = Modifier ) {
.fillMaxSize() ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
.themedBackground(), Column(
horizontalAlignment = Alignment.CenterHorizontally modifier = Modifier
) { .fillMaxSize()
CloseSheetBar(close = { .themedBackground(),
if (chatModel.users.none { !it.user.hidden }) { horizontalAlignment = Alignment.CenterHorizontally
appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
} else {
close()
}
})
BackHandler(onBack = {
appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
})
ColumnWithScrollBar(
modifier = if (appPlatform.isAndroid) Modifier.fillMaxSize() else Modifier.widthIn(max = 600.dp).fillMaxHeight(),
) { ) {
val displayName = rememberSaveable { mutableStateOf("") } CloseSheetBar(close = {
val focusRequester = remember { FocusRequester() } if (chatModel.users.none { !it.user.hidden }) {
Column(Modifier.padding(horizontal = DEFAULT_PADDING)) { appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
AppBarTitle(stringResource(MR.strings.create_profile), bottomPadding = DEFAULT_PADDING) } else {
ProfileNameField(displayName, stringResource(MR.strings.display_name), { it.trim() == mkValidName(it) }, focusRequester) close()
Spacer(Modifier.height(DEFAULT_PADDING)) }
ReadableText(MR.strings.your_profile_is_stored_on_your_device, TextAlign.Start, padding = PaddingValues(), style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary)) })
ReadableText(MR.strings.profile_is_only_shared_with_your_contacts, TextAlign.Start, style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary)) BackHandler(onBack = {
} appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
Spacer(Modifier.fillMaxHeight().weight(1f)) })
Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
OnboardingActionButton(
if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp),
labelId = MR.strings.create_profile_button,
onboarding = null,
enabled = canCreateProfile(displayName.value),
onclick = { createProfileOnboarding(chat.simplex.common.platform.chatModel, displayName.value, close) }
)
// Reserve space
TextButtonBelowOnboardingButton("", null)
}
LaunchedEffect(Unit) { ColumnWithScrollBar(
delay(300) modifier = Modifier.fillMaxSize()
focusRequester.requestFocus() ) {
val displayName = rememberSaveable { mutableStateOf("") }
val focusRequester = remember { FocusRequester() }
Column(if (appPlatform.isAndroid) Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING) else Modifier.widthIn(max = 600.dp).fillMaxHeight().padding(horizontal = DEFAULT_PADDING).align(Alignment.CenterHorizontally)) {
Box(Modifier.align(Alignment.CenterHorizontally)) {
AppBarTitle(stringResource(MR.strings.create_profile), bottomPadding = DEFAULT_PADDING, withPadding = false)
}
ProfileNameField(displayName, stringResource(MR.strings.display_name), { it.trim() == mkValidName(it) }, focusRequester)
Spacer(Modifier.height(DEFAULT_PADDING))
ReadableText(MR.strings.your_profile_is_stored_on_your_device, TextAlign.Start, padding = PaddingValues(), style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary))
ReadableText(MR.strings.profile_is_only_shared_with_your_contacts, TextAlign.Start, style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary))
}
Spacer(Modifier.fillMaxHeight().weight(1f))
Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
OnboardingActionButton(
if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp),
labelId = MR.strings.create_profile_button,
onboarding = null,
enabled = canCreateProfile(displayName.value),
onclick = { createProfileOnboarding(chat.simplex.common.platform.chatModel, displayName.value, close) }
)
// Reserve space
TextButtonBelowOnboardingButton("", null)
}
LaunchedEffect(Unit) {
delay(300)
focusRequester.requestFocus()
}
} }
} }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -47,6 +47,7 @@ class CallManager(val chatModel: ChatModel) {
remoteHostId = invitation.remoteHostId, remoteHostId = invitation.remoteHostId,
userProfile = userProfile, userProfile = userProfile,
contact = invitation.contact, contact = invitation.contact,
callUUID = invitation.callUUID,
callState = CallState.InvitationAccepted, callState = CallState.InvitationAccepted,
localMedia = invitation.callType.media, localMedia = invitation.callType.media,
sharedKey = invitation.sharedKey, sharedKey = invitation.sharedKey,
@@ -115,6 +115,7 @@ fun PreviewIncomingCallAlertLayout() {
contact = Contact.sampleData, contact = Contact.sampleData,
callType = CallType(media = CallMediaType.Audio, capabilities = CallCapabilities(encryption = false)), callType = CallType(media = CallMediaType.Audio, capabilities = CallCapabilities(encryption = false)),
sharedKey = null, sharedKey = null,
callUUID = "",
callTs = Clock.System.now() callTs = Clock.System.now()
), ),
chatModel = ChatModel, chatModel = ChatModel,
@@ -13,6 +13,7 @@ data class Call(
val remoteHostId: Long?, val remoteHostId: Long?,
val userProfile: Profile, val userProfile: Profile,
val contact: Contact, val contact: Contact,
val callUUID: String?,
val callState: CallState, val callState: CallState,
val localMedia: CallMediaType, val localMedia: CallMediaType,
val localCapabilities: CallCapabilities? = null, val localCapabilities: CallCapabilities? = null,
@@ -105,6 +106,7 @@ sealed class WCallResponse {
val contact: Contact, val contact: Contact,
val callType: CallType, val callType: CallType,
val sharedKey: String? = null, val sharedKey: String? = null,
val callUUID: String,
val callTs: Instant val callTs: Instant
) { ) {
val callTypeText: String get() = generalGetString(when(callType.media) { val callTypeText: String get() = generalGetString(when(callType.media) {
@@ -599,7 +599,7 @@ fun ChatInfoLayout(
} }
} }
} }
SectionDividerSpaced() SectionDividerSpaced(maxBottomPadding = false)
val conn = contact.activeConn val conn = contact.activeConn
if (conn != null) { if (conn != null) {
@@ -616,7 +616,7 @@ fun ChatInfoLayout(
ShareAddressButton { clipboard.shareText(simplexChatLink(contact.contactLink)) } ShareAddressButton { clipboard.shareText(simplexChatLink(contact.contactLink)) }
SectionTextFooter(stringResource(MR.strings.you_can_share_this_address_with_your_contacts).format(contact.displayName)) SectionTextFooter(stringResource(MR.strings.you_can_share_this_address_with_your_contacts).format(contact.displayName))
} }
SectionDividerSpaced() SectionDividerSpaced(maxTopPadding = true)
} }
if (contact.ready && contact.active) { if (contact.ready && contact.active) {
@@ -650,7 +650,7 @@ fun ChatInfoLayout(
} }
} }
} }
SectionDividerSpaced() SectionDividerSpaced(maxBottomPadding = false)
} }
SectionView { SectionView {
@@ -970,7 +970,7 @@ fun InfoViewActionButton(
Icon( Icon(
icon, icon,
contentDescription = null, contentDescription = null,
Modifier.size(24.dp * fontSizeSqrtMultiplier), Modifier.size(22.dp * fontSizeSqrtMultiplier),
tint = if (disabledLook) MaterialTheme.colors.secondary else MaterialTheme.colors.onPrimary tint = if (disabledLook) MaterialTheme.colors.secondary else MaterialTheme.colors.onPrimary
) )
} }
@@ -1268,7 +1268,7 @@ fun showSyncConnectionForceAlert(syncConnectionForce: () -> Unit) {
) )
} }
fun queueInfoText(info: Pair<RcvMsgInfo?, QueueInfo>): String { fun queueInfoText(info: Pair<RcvMsgInfo?, ServerQueueInfo>): String {
val (rcvMsgInfo, qInfo) = info val (rcvMsgInfo, qInfo) = info
val msgInfo: String = if (rcvMsgInfo != null) json.encodeToString(rcvMsgInfo) else generalGetString(MR.strings.message_queue_info_none) val msgInfo: String = if (rcvMsgInfo != null) json.encodeToString(rcvMsgInfo) else generalGetString(MR.strings.message_queue_info_none)
return generalGetString(MR.strings.message_queue_info_server_info).format(json.encodeToString(qInfo), msgInfo) return generalGetString(MR.strings.message_queue_info_server_info).format(json.encodeToString(qInfo), msgInfo)
@@ -224,7 +224,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
Row( Row(
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.sizeIn(minHeight = 46.dp) .sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT)
.padding(PaddingValues(horizontal = DEFAULT_PADDING)) .padding(PaddingValues(horizontal = DEFAULT_PADDING))
.clickable { expanded.value = !expanded.value }, .clickable { expanded.value = !expanded.value },
horizontalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp),
@@ -277,20 +277,19 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
@Composable @Composable
fun HistoryTab() { fun HistoryTab() {
// LALAL SCROLLBAR DOESN'T WORK
ColumnWithScrollBar(Modifier.fillMaxWidth()) { ColumnWithScrollBar(Modifier.fillMaxWidth()) {
Details() Details()
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true) SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true)
val versions = ciInfo.itemVersions val versions = ciInfo.itemVersions
if (versions.isNotEmpty()) { if (versions.isNotEmpty()) {
SectionView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
Text(stringResource(MR.strings.edit_history), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = DEFAULT_PADDING)) Text(stringResource(MR.strings.edit_history), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = DEFAULT_PADDING))
versions.forEachIndexed { i, ciVersion -> versions.forEachIndexed { i, ciVersion ->
ItemVersionView(ciVersion, current = i == 0) ItemVersionView(ciVersion, current = i == 0)
} }
} }
} else { } else {
SectionView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(stringResource(MR.strings.no_history), color = MaterialTheme.colors.secondary) Text(stringResource(MR.strings.no_history), color = MaterialTheme.colors.secondary)
} }
@@ -302,11 +301,10 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
@Composable @Composable
fun QuoteTab(qi: CIQuote) { fun QuoteTab(qi: CIQuote) {
// LALAL SCROLLBAR DOESN'T WORK
ColumnWithScrollBar(Modifier.fillMaxWidth()) { ColumnWithScrollBar(Modifier.fillMaxWidth()) {
Details() Details()
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true) SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true)
SectionView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
Text(stringResource(MR.strings.in_reply_to), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = DEFAULT_PADDING)) Text(stringResource(MR.strings.in_reply_to), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = DEFAULT_PADDING))
QuotedMsgView(qi) QuotedMsgView(qi)
} }
@@ -316,7 +314,6 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
@Composable @Composable
fun ForwardedFromTab(forwardedFromItem: AChatItem) { fun ForwardedFromTab(forwardedFromItem: AChatItem) {
// LALAL SCROLLBAR DOESN'T WORK
ColumnWithScrollBar(Modifier.fillMaxWidth()) { ColumnWithScrollBar(Modifier.fillMaxWidth()) {
Details() Details()
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true) SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true)
@@ -379,20 +376,19 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
@Composable @Composable
fun DeliveryTab(memberDeliveryStatuses: List<MemberDeliveryStatus>) { fun DeliveryTab(memberDeliveryStatuses: List<MemberDeliveryStatus>) {
// LALAL SCROLLBAR DOESN'T WORK
ColumnWithScrollBar(Modifier.fillMaxWidth()) { ColumnWithScrollBar(Modifier.fillMaxWidth()) {
Details() Details()
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true) SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true)
val mss = membersStatuses(chatModel, memberDeliveryStatuses) val mss = membersStatuses(chatModel, memberDeliveryStatuses)
if (mss.isNotEmpty()) { if (mss.isNotEmpty()) {
SectionView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
Text(stringResource(MR.strings.delivery), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = DEFAULT_PADDING)) Text(stringResource(MR.strings.delivery), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = DEFAULT_PADDING))
mss.forEach { (member, status, sentViaProxy) -> mss.forEach { (member, status, sentViaProxy) ->
MemberDeliveryStatusView(member, status, sentViaProxy) MemberDeliveryStatusView(member, status, sentViaProxy)
} }
} }
} else { } else {
SectionView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(stringResource(MR.strings.no_info_on_delivery), color = MaterialTheme.colors.secondary) Text(stringResource(MR.strings.no_info_on_delivery), color = MaterialTheme.colors.secondary)
} }
@@ -86,6 +86,7 @@ fun ChatView(staleChatId: State<String?>, onComposed: suspend (chatId: String) -
.collect { chatId -> .collect { chatId ->
markUnreadChatAsRead(chatId) markUnreadChatAsRead(chatId)
showSearch.value = false showSearch.value = false
searchText.value = ""
selectedChatItems.value = null selectedChatItems.value = null
} }
} }
@@ -504,24 +505,34 @@ fun ChatView(staleChatId: State<String?>, onComposed: suspend (chatId: String) -
} }
is ChatInfo.ContactConnection -> { is ChatInfo.ContactConnection -> {
val close = { chatModel.chatId.value = null } val close = { chatModel.chatId.value = null }
ModalView(close, showClose = appPlatform.isAndroid, content = { val handler = remember { AppBarHandler() }
ContactConnectionInfoView(chatModel, chatRh, chatInfo.contactConnection.connReqInv, chatInfo.contactConnection, false, close) CompositionLocalProvider(
}) LocalAppBarHandler provides handler
LaunchedEffect(chatInfo.id) { ) {
onComposed(chatInfo.id) ModalView(close, showClose = appPlatform.isAndroid, content = {
ModalManager.end.closeModals() ContactConnectionInfoView(chatModel, chatRh, chatInfo.contactConnection.connReqInv, chatInfo.contactConnection, false, close)
chatModel.chatItems.clear() })
LaunchedEffect(chatInfo.id) {
onComposed(chatInfo.id)
ModalManager.end.closeModals()
chatModel.chatItems.clear()
}
} }
} }
is ChatInfo.InvalidJSON -> { is ChatInfo.InvalidJSON -> {
val close = { chatModel.chatId.value = null } val close = { chatModel.chatId.value = null }
ModalView(close, showClose = appPlatform.isAndroid, endButtons = { ShareButton { clipboard.shareText(chatInfo.json) } }, content = { val handler = remember { AppBarHandler() }
InvalidJSONView(chatInfo.json) CompositionLocalProvider(
}) LocalAppBarHandler provides handler
LaunchedEffect(chatInfo.id) { ) {
onComposed(chatInfo.id) ModalView(close, showClose = appPlatform.isAndroid, endButtons = { ShareButton { clipboard.shareText(chatInfo.json) } }, content = {
ModalManager.end.closeModals() InvalidJSONView(chatInfo.json)
chatModel.chatItems.clear() })
LaunchedEffect(chatInfo.id) {
onComposed(chatInfo.id)
ModalManager.end.closeModals()
chatModel.chatItems.clear()
}
} }
} }
else -> {} else -> {}
@@ -534,7 +545,7 @@ fun startChatCall(remoteHostId: Long?, chatInfo: ChatInfo, media: CallMediaType)
if (chatInfo is ChatInfo.Direct) { if (chatInfo is ChatInfo.Direct) {
val contactInfo = chatModel.controller.apiContactInfo(remoteHostId, chatInfo.contact.contactId) val contactInfo = chatModel.controller.apiContactInfo(remoteHostId, chatInfo.contact.contactId)
val profile = contactInfo?.second ?: chatModel.currentUser.value?.profile?.toProfile() ?: return@withBGApi val profile = contactInfo?.second ?: chatModel.currentUser.value?.profile?.toProfile() ?: return@withBGApi
chatModel.activeCall.value = Call(remoteHostId = remoteHostId, contact = chatInfo.contact, callState = CallState.WaitCapabilities, localMedia = media, userProfile = profile) chatModel.activeCall.value = Call(remoteHostId = remoteHostId, contact = chatInfo.contact, callUUID = null, callState = CallState.WaitCapabilities, localMedia = media, userProfile = profile)
chatModel.showCallView.value = true chatModel.showCallView.value = true
chatModel.callCommand.add(WCallCommand.Capabilities(media)) chatModel.callCommand.add(WCallCommand.Capabilities(media))
} }
@@ -93,22 +93,22 @@ private fun ContactPreferencesLayout(
TimedMessagesFeatureSection(featuresAllowed, contact.mergedPreferences.timedMessages, timedMessages, onTTLUpdated) { allowed, ttl -> TimedMessagesFeatureSection(featuresAllowed, contact.mergedPreferences.timedMessages, timedMessages, onTTLUpdated) { allowed, ttl ->
applyPrefs(featuresAllowed.copy(timedMessagesAllowed = allowed, timedMessagesTTL = ttl ?: currentFeaturesAllowed.timedMessagesTTL)) applyPrefs(featuresAllowed.copy(timedMessagesAllowed = allowed, timedMessagesTTL = ttl ?: currentFeaturesAllowed.timedMessagesTTL))
} }
SectionDividerSpaced(true, maxBottomPadding = false) SectionDividerSpaced(true)
val allowFullDeletion: MutableState<ContactFeatureAllowed> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.fullDelete) } val allowFullDeletion: MutableState<ContactFeatureAllowed> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.fullDelete) }
FeatureSection(ChatFeature.FullDelete, user.fullPreferences.fullDelete.allow, contact.mergedPreferences.fullDelete, allowFullDeletion) { FeatureSection(ChatFeature.FullDelete, user.fullPreferences.fullDelete.allow, contact.mergedPreferences.fullDelete, allowFullDeletion) {
applyPrefs(featuresAllowed.copy(fullDelete = it)) applyPrefs(featuresAllowed.copy(fullDelete = it))
} }
SectionDividerSpaced(true, maxBottomPadding = false) SectionDividerSpaced(true)
val allowReactions: MutableState<ContactFeatureAllowed> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.reactions) } val allowReactions: MutableState<ContactFeatureAllowed> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.reactions) }
FeatureSection(ChatFeature.Reactions, user.fullPreferences.reactions.allow, contact.mergedPreferences.reactions, allowReactions) { FeatureSection(ChatFeature.Reactions, user.fullPreferences.reactions.allow, contact.mergedPreferences.reactions, allowReactions) {
applyPrefs(featuresAllowed.copy(reactions = it)) applyPrefs(featuresAllowed.copy(reactions = it))
} }
SectionDividerSpaced(true, maxBottomPadding = false) SectionDividerSpaced(true)
val allowVoice: MutableState<ContactFeatureAllowed> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.voice) } val allowVoice: MutableState<ContactFeatureAllowed> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.voice) }
FeatureSection(ChatFeature.Voice, user.fullPreferences.voice.allow, contact.mergedPreferences.voice, allowVoice) { FeatureSection(ChatFeature.Voice, user.fullPreferences.voice.allow, contact.mergedPreferences.voice, allowVoice) {
applyPrefs(featuresAllowed.copy(voice = it)) applyPrefs(featuresAllowed.copy(voice = it))
} }
SectionDividerSpaced(true, maxBottomPadding = false) SectionDividerSpaced(true)
val allowCalls: MutableState<ContactFeatureAllowed> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.calls) } val allowCalls: MutableState<ContactFeatureAllowed> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.calls) }
FeatureSection(ChatFeature.Calls, user.fullPreferences.calls.allow, contact.mergedPreferences.calls, allowCalls) { FeatureSection(ChatFeature.Calls, user.fullPreferences.calls.allow, contact.mergedPreferences.calls, allowCalls) {
applyPrefs(featuresAllowed.copy(calls = it)) applyPrefs(featuresAllowed.copy(calls = it))
@@ -66,7 +66,7 @@ fun SelectedItemsBottomToolbar(
Icon( Icon(
painterResource(MR.images.ic_delete), painterResource(MR.images.ic_delete),
null, null,
Modifier.size(24.dp), Modifier.size(22.dp),
tint = if (!deleteEnabled.value || allButtonsDisabled.value) MaterialTheme.colors.secondary else MaterialTheme.colors.error tint = if (!deleteEnabled.value || allButtonsDisabled.value) MaterialTheme.colors.secondary else MaterialTheme.colors.error
) )
} }
@@ -75,7 +75,7 @@ fun SelectedItemsBottomToolbar(
Icon( Icon(
painterResource(MR.images.ic_flag), painterResource(MR.images.ic_flag),
null, null,
Modifier.size(24.dp), Modifier.size(22.dp),
tint = if (!moderateEnabled.value || allButtonsDisabled.value) MaterialTheme.colors.secondary else MaterialTheme.colors.error tint = if (!moderateEnabled.value || allButtonsDisabled.value) MaterialTheme.colors.secondary else MaterialTheme.colors.error
) )
} }
@@ -84,7 +84,7 @@ fun SelectedItemsBottomToolbar(
Icon( Icon(
painterResource(MR.images.ic_share), painterResource(MR.images.ic_share),
null, null,
Modifier.size(24.dp), Modifier.size(22.dp),
tint = if (allButtonsDisabled.value) MaterialTheme.colors.secondary else MaterialTheme.colors.primary tint = if (allButtonsDisabled.value) MaterialTheme.colors.secondary else MaterialTheme.colors.primary
) )
} }
@@ -4,6 +4,7 @@ import SectionBottomSpacer
import SectionCustomFooter import SectionCustomFooter
import SectionDividerSpaced import SectionDividerSpaced
import SectionItemView import SectionItemView
import SectionItemViewWithoutMinPadding
import SectionSpacer import SectionSpacer
import SectionView import SectionView
import androidx.compose.foundation.* import androidx.compose.foundation.*
@@ -177,7 +178,7 @@ fun AddGroupMembersLayout(
InviteSectionFooter(selectedContactsCount = selectedContacts.size, allowModifyMembers, clearSelection) InviteSectionFooter(selectedContactsCount = selectedContacts.size, allowModifyMembers, clearSelection)
} }
SectionDividerSpaced(maxTopPadding = true) SectionDividerSpaced(maxTopPadding = true)
SectionView(stringResource(MR.strings.select_contacts)) { SectionView(stringResource(MR.strings.select_contacts).uppercase()) {
SectionItemView(padding = PaddingValues(start = DEFAULT_PADDING, end = DEFAULT_PADDING_HALF)) { SectionItemView(padding = PaddingValues(start = DEFAULT_PADDING, end = DEFAULT_PADDING_HALF)) {
SearchRowView(searchText) SearchRowView(searchText)
} }
@@ -255,7 +256,8 @@ fun InviteSectionFooter(selectedContactsCount: Int, enabled: Boolean, clearSelec
Text( Text(
String.format(generalGetString(MR.strings.num_contacts_selected), selectedContactsCount), String.format(generalGetString(MR.strings.num_contacts_selected), selectedContactsCount),
color = MaterialTheme.colors.secondary, color = MaterialTheme.colors.secondary,
fontSize = 12.sp lineHeight = 18.sp,
fontSize = 14.sp
) )
Box( Box(
Modifier.clickable { if (enabled) clearSelection() } Modifier.clickable { if (enabled) clearSelection() }
@@ -263,14 +265,16 @@ fun InviteSectionFooter(selectedContactsCount: Int, enabled: Boolean, clearSelec
Text( Text(
stringResource(MR.strings.clear_contacts_selection_button), stringResource(MR.strings.clear_contacts_selection_button),
color = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, color = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary,
fontSize = 12.sp lineHeight = 18.sp,
fontSize = 14.sp,
) )
} }
} else { } else {
Text( Text(
stringResource(MR.strings.no_contacts_selected), stringResource(MR.strings.no_contacts_selected),
color = MaterialTheme.colors.secondary, color = MaterialTheme.colors.secondary,
fontSize = 12.sp lineHeight = 18.sp,
fontSize = 14.sp,
) )
} }
} }
@@ -318,7 +322,7 @@ fun ContactCheckRow(
icon = painterResource(MR.images.ic_circle) icon = painterResource(MR.images.ic_circle)
iconColor = MaterialTheme.colors.secondary iconColor = MaterialTheme.colors.secondary
} }
SectionItemView( SectionItemViewWithoutMinPadding(
click = if (enabled) { click = if (enabled) {
{ {
if (prohibitedToInviteIncognito) { if (prohibitedToInviteIncognito) {
@@ -284,7 +284,6 @@ fun GroupChatInfoLayout(
if (s.isEmpty()) members else members.filter { m -> m.anyNameContains(s) } if (s.isEmpty()) members else members.filter { m -> m.anyNameContains(s) }
} }
} }
// LALAL strange scrolling
LazyColumnWithScrollBar( LazyColumnWithScrollBar(
Modifier Modifier
.fillMaxWidth(), .fillMaxWidth(),
@@ -366,7 +365,7 @@ fun GroupChatInfoLayout(
SearchRowView(searchText) SearchRowView(searchText)
} }
} }
SectionItemView(minHeight = 54.dp) { SectionItemView(minHeight = 54.dp, padding = PaddingValues(horizontal = DEFAULT_PADDING)) {
MemberRow(groupInfo.membership, user = true) MemberRow(groupInfo.membership, user = true)
} }
} }
@@ -374,7 +373,7 @@ fun GroupChatInfoLayout(
items(filteredMembers.value) { member -> items(filteredMembers.value) { member ->
Divider() Divider()
val showMenu = remember { mutableStateOf(false) } val showMenu = remember { mutableStateOf(false) }
SectionItemViewLongClickable({ showMemberInfo(member) }, { showMenu.value = true }, minHeight = 54.dp) { SectionItemViewLongClickable({ showMemberInfo(member) }, { showMenu.value = true }, minHeight = 54.dp, padding = PaddingValues(horizontal = DEFAULT_PADDING)) {
DropDownMenuForMember(chat.remoteHostId, member, groupInfo, showMenu) DropDownMenuForMember(chat.remoteHostId, member, groupInfo, showMenu)
MemberRow(member, onClick = { showMemberInfo(member) }) MemberRow(member, onClick = { showMemberInfo(member) })
} }
@@ -510,11 +509,11 @@ private fun MemberRow(member: GroupMember, user: Boolean = false, onClick: (() -
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Row( Row(
Modifier.weight(1f).padding(end = DEFAULT_PADDING), Modifier.weight(1f).padding(top = 8.dp, end = DEFAULT_PADDING, bottom = 8.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp) horizontalArrangement = Arrangement.spacedBy(4.dp)
) { ) {
MemberProfileImage(size = 46.dp, member) MemberProfileImage(size = 42.dp, member)
Spacer(Modifier.width(DEFAULT_PADDING_HALF)) Spacer(Modifier.width(DEFAULT_PADDING_HALF))
Column { Column {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
@@ -133,13 +133,7 @@ private fun GroupWelcomeLayout(
val clipboard = LocalClipboardManager.current val clipboard = LocalClipboardManager.current
CopyTextButton { clipboard.setText(AnnotatedString(wt.value)) } CopyTextButton { clipboard.setText(AnnotatedString(wt.value)) }
Divider( SectionDividerSpaced(maxBottomPadding = false)
Modifier.padding(
start = DEFAULT_PADDING_HALF,
top = 8.dp,
end = DEFAULT_PADDING_HALF,
bottom = 8.dp)
)
SaveButton( SaveButton(
save = save, save = save,
@@ -185,7 +185,14 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf
scaffoldState = scaffoldState, scaffoldState = scaffoldState,
drawerContent = { drawerContent = {
tryOrShowError("Settings", error = { ErrorSettingsView() }) { tryOrShowError("Settings", error = { ErrorSettingsView() }) {
SettingsView(chatModel, setPerformLA, scaffoldState.drawerState) val handler = remember { AppBarHandler() }
CompositionLocalProvider(
LocalAppBarHandler provides handler
) {
ModalView(showClose = appPlatform.isDesktop, close = { scope.launch { scaffoldState.drawerState.close() } }) {
SettingsView(chatModel, setPerformLA, scaffoldState.drawerState)
}
}
} }
}, },
contentColor = LocalContentColor.current, contentColor = LocalContentColor.current,
@@ -212,7 +219,7 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf
backgroundColor = if (!stopped) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, backgroundColor = if (!stopped) MaterialTheme.colors.primary else MaterialTheme.colors.secondary,
contentColor = Color.White contentColor = Color.White
) { ) {
Icon(painterResource(MR.images.ic_edit_filled), stringResource(MR.strings.add_contact_or_create_group), Modifier.size(24.dp * fontSizeSqrtMultiplier)) Icon(painterResource(MR.images.ic_edit_filled), stringResource(MR.strings.add_contact_or_create_group), Modifier.size(22.dp * fontSizeSqrtMultiplier))
} }
} }
} }
@@ -513,7 +520,7 @@ private fun ChatListSearchBar(listState: LazyListState, searchText: MutableState
Icon( Icon(
painterResource(MR.images.ic_search), painterResource(MR.images.ic_search),
contentDescription = null, contentDescription = null,
Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING_HALF).size(24.dp * fontSizeSqrtMultiplier), Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING_HALF).size(22.dp * fontSizeSqrtMultiplier),
tint = MaterialTheme.colors.secondary tint = MaterialTheme.colors.secondary
) )
SearchTextField( SearchTextField(
@@ -546,15 +546,19 @@ fun XFTPServerSummaryLayout(summary: XFTPServerSummary, statsStartedAt: Instant,
}) { }) {
Text(generalGetString(MR.strings.open_server_settings_button)) Text(generalGetString(MR.strings.open_server_settings_button))
} }
if (summary.stats != null || summary.sessions != null) {
SectionDividerSpaced()
}
} }
if (summary.stats != null) { if (summary.stats != null) {
SectionDividerSpaced()
XFTPStatsView(stats = summary.stats, rh = rh, statsStartedAt = statsStartedAt) XFTPStatsView(stats = summary.stats, rh = rh, statsStartedAt = statsStartedAt)
if (summary.sessions != null) {
SectionDividerSpaced(maxTopPadding = true)
}
} }
if (summary.sessions != null) { if (summary.sessions != null) {
SectionDividerSpaced()
ServerSessionsView(summary.sessions) ServerSessionsView(summary.sessions)
} }
} }
@@ -581,20 +585,24 @@ fun SMPServerSummaryLayout(summary: SMPServerSummary, statsStartedAt: Instant, r
}) { }) {
Text(generalGetString(MR.strings.open_server_settings_button)) Text(generalGetString(MR.strings.open_server_settings_button))
} }
SectionDividerSpaced()
} }
if (summary.stats != null) { if (summary.stats != null) {
SectionDividerSpaced()
SMPStatsView(stats = summary.stats, remoteHostInfo = rh, statsStartedAt = statsStartedAt) SMPStatsView(stats = summary.stats, remoteHostInfo = rh, statsStartedAt = statsStartedAt)
if (summary.subs != null || summary.sessions != null) {
SectionDividerSpaced(maxTopPadding = true)
}
} }
if (summary.subs != null) { if (summary.subs != null) {
SectionDividerSpaced()
SMPSubscriptionsSection(subs = summary.subs, summary = summary, rh = rh) SMPSubscriptionsSection(subs = summary.subs, summary = summary, rh = rh)
if (summary.sessions != null) {
SectionDividerSpaced()
}
} }
if (summary.sessions != null) { if (summary.sessions != null) {
SectionDividerSpaced()
ServerSessionsView(summary.sessions) ServerSessionsView(summary.sessions)
} }
} }
@@ -615,14 +623,12 @@ fun ModalData.SMPServerSummaryView(
ColumnWithScrollBar( ColumnWithScrollBar(
Modifier.fillMaxSize(), Modifier.fillMaxSize(),
) { ) {
Box(contentAlignment = Alignment.Center) { val bottomPadding = DEFAULT_PADDING
val bottomPadding = DEFAULT_PADDING AppBarTitle(
AppBarTitle( stringResource(MR.strings.smp_server),
stringResource(MR.strings.smp_server), hostDevice(rh?.remoteHostId),
hostDevice(rh?.remoteHostId), bottomPadding = bottomPadding
bottomPadding = bottomPadding )
)
}
SMPServerSummaryLayout(summary, statsStartedAt, rh) SMPServerSummaryLayout(summary, statsStartedAt, rh)
} }
} }
@@ -709,7 +715,7 @@ fun ModalData.XFTPServerSummaryView(
@Composable @Composable
fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableState<PresentedServersSummary?>) { fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableState<PresentedServersSummary?>) {
Column( ColumnWithScrollBar(
Modifier.fillMaxSize(), Modifier.fillMaxSize(),
) { ) {
var showUserSelection by remember { mutableStateOf(false) } var showUserSelection by remember { mutableStateOf(false) }
@@ -760,14 +766,12 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta
Column( Column(
Modifier.fillMaxSize(), Modifier.fillMaxSize(),
) { ) {
Box(contentAlignment = Alignment.Center) { val bottomPadding = DEFAULT_PADDING
val bottomPadding = DEFAULT_PADDING AppBarTitle(
AppBarTitle( stringResource(MR.strings.servers_info),
stringResource(MR.strings.servers_info), hostDevice(rh?.remoteHostId),
hostDevice(rh?.remoteHostId), bottomPadding = bottomPadding
bottomPadding = bottomPadding )
)
}
if (serversSummary.value == null) { if (serversSummary.value == null) {
Box( Box(
modifier = Modifier modifier = Modifier
@@ -827,7 +831,7 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta
verticalAlignment = Alignment.Top, verticalAlignment = Alignment.Top,
userScrollEnabled = appPlatform.isAndroid userScrollEnabled = appPlatform.isAndroid
) { index -> ) { index ->
ColumnWithScrollBar( Column(
Modifier Modifier
.fillMaxSize(), .fillMaxSize(),
verticalArrangement = Arrangement.Top verticalArrangement = Arrangement.Top
@@ -858,7 +862,7 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta
val statsStartedAt = it.statsStartedAt val statsStartedAt = it.statsStartedAt
SMPStatsView(totals.stats, statsStartedAt, rh) SMPStatsView(totals.stats, statsStartedAt, rh)
SectionDividerSpaced() SectionDividerSpaced(maxTopPadding = true)
SMPSubscriptionsSection(totals) SMPSubscriptionsSection(totals)
SectionDividerSpaced() SectionDividerSpaced()
@@ -890,7 +894,7 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta
footer = generalGetString(MR.strings.servers_info_proxied_servers_section_footer), footer = generalGetString(MR.strings.servers_info_proxied_servers_section_footer),
rh = rh rh = rh
) )
SectionDividerSpaced() SectionDividerSpaced(maxTopPadding = true)
} }
ServerSessionsView(totals.sessions) ServerSessionsView(totals.sessions)
@@ -907,7 +911,7 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta
val previouslyUsedXFTPServers = xftpSummary.previouslyUsedXFTPServers val previouslyUsedXFTPServers = xftpSummary.previouslyUsedXFTPServers
XFTPStatsView(totals.stats, statsStartedAt, rh) XFTPStatsView(totals.stats, statsStartedAt, rh)
SectionDividerSpaced() SectionDividerSpaced(maxTopPadding = true)
if (currentlyUsedXFTPServers.isNotEmpty()) { if (currentlyUsedXFTPServers.isNotEmpty()) {
XFTPServersListView( XFTPServersListView(
@@ -934,7 +938,7 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta
} }
} }
SectionDividerSpaced() SectionDividerSpaced(maxBottomPadding = false)
SectionView { SectionView {
ReconnectAllServersButton(rh) ReconnectAllServersButton(rh)
@@ -80,7 +80,7 @@ private fun ShareListNavLinkLayout(
click: () -> Unit, click: () -> Unit,
stopped: Boolean, stopped: Boolean,
) { ) {
SectionItemView(minHeight = 50.dp, click = click, disabled = stopped) { SectionItemView(padding = PaddingValues(horizontal = DEFAULT_PADDING, vertical = 8.dp), click = click, disabled = stopped) {
chatLinkPreview() chatLinkPreview()
} }
Divider(Modifier.padding(horizontal = 8.dp)) Divider(Modifier.padding(horizontal = 8.dp))
@@ -98,9 +98,11 @@ private fun SharePreviewView(chat: Chat, disabled: Boolean) {
horizontalArrangement = Arrangement.spacedBy(4.dp) horizontalArrangement = Arrangement.spacedBy(4.dp)
) { ) {
if (chat.chatInfo is ChatInfo.Local) { if (chat.chatInfo is ChatInfo.Local) {
ProfileImage(size = 46.dp, null, icon = MR.images.ic_folder_filled, color = NoteFolderIconColor) ProfileImage(size = 42.dp, null, icon = MR.images.ic_folder_filled, color = NoteFolderIconColor)
} else if (chat.chatInfo is ChatInfo.Group) {
ProfileImage(size = 42.dp, chat.chatInfo.image, icon = MR.images.ic_supervised_user_circle_filled)
} else { } else {
ProfileImage(size = 46.dp, chat.chatInfo.image) ProfileImage(size = 42.dp, chat.chatInfo.image)
} }
Text( Text(
chat.chatInfo.chatViewName, maxLines = 1, overflow = TextOverflow.Ellipsis, chat.chatInfo.chatViewName, maxLines = 1, overflow = TextOverflow.Ellipsis,
@@ -94,10 +94,17 @@ fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stoppe
} }
if (appPlatform.isAndroid) { if (appPlatform.isAndroid) {
tryOrShowError("UserPicker", error = {}) { tryOrShowError("UserPicker", error = {}) {
UserPicker(chatModel, userPickerState, showSettings = false, showCancel = true, cancelClicked = { UserPicker(
chatModel.sharedContent.value = null chatModel,
userPickerState.value = AnimatedViewState.GONE userPickerState,
}) showSettings = false,
showCancel = true,
contentAlignment = if (oneHandUI.value) Alignment.BottomStart else Alignment.TopStart,
cancelClicked = {
chatModel.sharedContent.value = null
userPickerState.value = AnimatedViewState.GONE
}
)
} }
} }
} }
@@ -262,7 +262,7 @@ fun UserProfilePickerItem(
Row( Row(
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.sizeIn(minHeight = 46.dp) .sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT)
.combinedClickable( .combinedClickable(
enabled = enabled, enabled = enabled,
onClick = if (u.activeUser) openSettings else onClick, onClick = if (u.activeUser) openSettings else onClick,
@@ -330,7 +330,7 @@ fun RemoteHostPickerItem(h: RemoteHostInfo, onLongClick: () -> Unit = {}, action
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.background(color = if (h.activeHost) MaterialTheme.colors.surface.mixWith(MaterialTheme.colors.onBackground, 0.95f) else Color.Unspecified) .background(color = if (h.activeHost) MaterialTheme.colors.surface.mixWith(MaterialTheme.colors.onBackground, 0.95f) else Color.Unspecified)
.sizeIn(minHeight = 46.dp) .sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT)
.combinedClickable( .combinedClickable(
onClick = onClick, onClick = onClick,
onLongClick = onLongClick onLongClick = onLongClick
@@ -373,7 +373,7 @@ fun LocalDevicePickerItem(active: Boolean, onLongClick: () -> Unit = {}, onClick
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.background(color = if (active) MaterialTheme.colors.surface.mixWith(MaterialTheme.colors.onBackground, 0.95f) else Color.Unspecified) .background(color = if (active) MaterialTheme.colors.surface.mixWith(MaterialTheme.colors.onBackground, 0.95f) else Color.Unspecified)
.sizeIn(minHeight = 46.dp) .sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT)
.combinedClickable( .combinedClickable(
onClick = if (active) {{}} else onClick, onClick = if (active) {{}} else onClick,
onLongClick = onLongClick, onLongClick = onLongClick,
@@ -107,101 +107,104 @@ fun DatabaseEncryptionLayout(
migration: Boolean, migration: Boolean,
onConfirmEncrypt: () -> Unit, onConfirmEncrypt: () -> Unit,
) { ) {
val (scrollBarAlpha, scrollModifier, scrollJob) = platform.desktopScrollBarComponents() @Composable
val scrollState = rememberScrollState() fun Layout() {
Column( Column {
if (!migration) Modifier.fillMaxWidth().verticalScroll(scrollState).then(if (appPlatform.isDesktop) scrollModifier else Modifier) else Modifier.fillMaxWidth(), if (!migration) {
) { AppBarTitle(stringResource(MR.strings.database_passphrase))
if (!migration) { } else {
AppBarTitle(stringResource(MR.strings.database_passphrase)) ChatStoppedView()
} else { SectionSpacer()
ChatStoppedView()
SectionSpacer()
}
SectionView(if (migration) generalGetString(MR.strings.database_passphrase).uppercase() else null) {
SavePassphraseSetting(
useKeychain.value,
initialRandomDBPassphrase.value,
storedKey.value,
enabled = (!initialRandomDBPassphrase.value && !progressIndicator.value) || migration
) { checked ->
if (checked) {
setUseKeychain(true, useKeychain, migration)
} else if (storedKey.value && !migration) {
// Don't show in migration process since it will remove the key after successful encryption
removePassphraseAlert {
removePassphraseFromKeyChain(useKeychain, storedKey, false)
}
} else {
setUseKeychain(false, useKeychain, migration)
}
} }
SectionView(if (migration) generalGetString(MR.strings.database_passphrase).uppercase() else null) {
SavePassphraseSetting(
useKeychain.value,
initialRandomDBPassphrase.value,
storedKey.value,
enabled = (!initialRandomDBPassphrase.value && !progressIndicator.value) || migration
) { checked ->
if (checked) {
setUseKeychain(true, useKeychain, migration)
} else if (storedKey.value && !migration) {
// Don't show in migration process since it will remove the key after successful encryption
removePassphraseAlert {
removePassphraseFromKeyChain(useKeychain, storedKey, false)
}
} else {
setUseKeychain(false, useKeychain, migration)
}
}
if (!initialRandomDBPassphrase.value && chatDbEncrypted == true) {
PassphraseField(
currentKey,
generalGetString(MR.strings.current_passphrase),
modifier = Modifier.padding(horizontal = DEFAULT_PADDING),
isValid = ::validKey,
keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }),
)
}
if (!initialRandomDBPassphrase.value && chatDbEncrypted == true) {
PassphraseField( PassphraseField(
currentKey, newKey,
generalGetString(MR.strings.current_passphrase), generalGetString(MR.strings.new_passphrase),
modifier = Modifier.padding(horizontal = DEFAULT_PADDING), modifier = Modifier.padding(horizontal = DEFAULT_PADDING),
showStrength = true,
isValid = ::validKey, isValid = ::validKey,
keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }),
) )
} val onClickUpdate = {
// Don't do things concurrently. Shouldn't be here concurrently, just in case
PassphraseField( if (!progressIndicator.value) {
newKey, if (currentKey.value == "") {
generalGetString(MR.strings.new_passphrase), if (useKeychain.value)
modifier = Modifier.padding(horizontal = DEFAULT_PADDING), encryptDatabaseSavedAlert(onConfirmEncrypt)
showStrength = true, else
isValid = ::validKey, encryptDatabaseAlert(onConfirmEncrypt)
keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), } else {
) if (useKeychain.value)
val onClickUpdate = { changeDatabaseKeySavedAlert(onConfirmEncrypt)
// Don't do things concurrently. Shouldn't be here concurrently, just in case else
if (!progressIndicator.value) { changeDatabaseKeyAlert(onConfirmEncrypt)
if (currentKey.value == "") { }
if (useKeychain.value)
encryptDatabaseSavedAlert(onConfirmEncrypt)
else
encryptDatabaseAlert(onConfirmEncrypt)
} else {
if (useKeychain.value)
changeDatabaseKeySavedAlert(onConfirmEncrypt)
else
changeDatabaseKeyAlert(onConfirmEncrypt)
} }
} }
val disabled = currentKey.value == newKey.value ||
newKey.value != confirmNewKey.value ||
newKey.value.isEmpty() ||
!validKey(currentKey.value) ||
!validKey(newKey.value) ||
progressIndicator.value
PassphraseField(
confirmNewKey,
generalGetString(MR.strings.confirm_new_passphrase),
modifier = Modifier.padding(horizontal = DEFAULT_PADDING),
isValid = { confirmNewKey.value == "" || newKey.value == confirmNewKey.value },
keyboardActions = KeyboardActions(onDone = {
if (!disabled) onClickUpdate()
defaultKeyboardAction(ImeAction.Done)
}),
)
SectionItemViewSpaceBetween(onClickUpdate, disabled = disabled, minHeight = TextFieldDefaults.MinHeight) {
Text(generalGetString(if (migration) MR.strings.set_passphrase else MR.strings.update_database_passphrase), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
}
} }
val disabled = currentKey.value == newKey.value ||
newKey.value != confirmNewKey.value ||
newKey.value.isEmpty() ||
!validKey(currentKey.value) ||
!validKey(newKey.value) ||
progressIndicator.value
PassphraseField( Column {
confirmNewKey, DatabaseEncryptionFooter(useKeychain, chatDbEncrypted, storedKey, initialRandomDBPassphrase, migration)
generalGetString(MR.strings.confirm_new_passphrase),
modifier = Modifier.padding(horizontal = DEFAULT_PADDING),
isValid = { confirmNewKey.value == "" || newKey.value == confirmNewKey.value },
keyboardActions = KeyboardActions(onDone = {
if (!disabled) onClickUpdate()
defaultKeyboardAction(ImeAction.Done)
}),
)
SectionItemViewSpaceBetween(onClickUpdate, disabled = disabled, minHeight = TextFieldDefaults.MinHeight) {
Text(generalGetString(if (migration) MR.strings.set_passphrase else MR.strings.update_database_passphrase), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
} }
SectionBottomSpacer()
} }
Column {
DatabaseEncryptionFooter(useKeychain, chatDbEncrypted, storedKey, initialRandomDBPassphrase, migration)
}
SectionBottomSpacer()
} }
if (appPlatform.isDesktop && !migration) { if (migration) {
Box(Modifier.fillMaxSize()) { Column(Modifier.fillMaxWidth()) {
platform.desktopScrollBar(scrollState, Modifier.align(Alignment.CenterEnd).fillMaxHeight(), scrollBarAlpha, scrollJob, false) Layout()
}
} else {
ColumnWithScrollBar(Modifier.fillMaxWidth(), maxIntrinsicSize = true) {
Layout()
} }
} }
} }
@@ -5,9 +5,7 @@ import SectionSpacer
import SectionView import SectionView
import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -66,7 +64,7 @@ fun DatabaseErrorView(
Modifier.padding(start = DEFAULT_PADDING, top = DEFAULT_PADDING, bottom = DEFAULT_PADDING), Modifier.padding(start = DEFAULT_PADDING, top = DEFAULT_PADDING, bottom = DEFAULT_PADDING),
style = MaterialTheme.typography.h1 style = MaterialTheme.typography.h1
) )
SectionView(null, padding = PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF), content) SectionView(null, contentPadding = PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF), content = content)
} }
@Composable @Composable
@@ -195,7 +195,7 @@ fun DatabaseLayout(
stringResource(MR.strings.stop_chat_to_enable_database_actions) stringResource(MR.strings.stop_chat_to_enable_database_actions)
} }
) )
SectionDividerSpaced() SectionDividerSpaced(maxTopPadding = true)
} }
SectionView(stringResource(MR.strings.chat_database_section)) { SectionView(stringResource(MR.strings.chat_database_section)) {
@@ -264,7 +264,7 @@ fun DatabaseLayout(
disabled = operationsDisabled disabled = operationsDisabled
) )
} }
SectionDividerSpaced(maxTopPadding = true) SectionDividerSpaced()
SectionView(stringResource(MR.strings.files_and_media_section).uppercase()) { SectionView(stringResource(MR.strings.files_and_media_section).uppercase()) {
val deleteFilesDisabled = operationsDisabled || appFilesCountAndSize.value.first == 0 val deleteFilesDisabled = operationsDisabled || appFilesCountAndSize.value.first == 0
@@ -423,6 +423,7 @@ fun authStopChat(m: ChatModel, progressIndicator: MutableState<Boolean>? = null,
authenticate( authenticate(
generalGetString(MR.strings.auth_stop_chat), generalGetString(MR.strings.auth_stop_chat),
generalGetString(MR.strings.auth_log_in_using_credential), generalGetString(MR.strings.auth_log_in_using_credential),
oneTime = true,
completed = { laResult -> completed = { laResult ->
when (laResult) { when (laResult) {
LAResult.Success, is LAResult.Unavailable -> { LAResult.Success, is LAResult.Unavailable -> {
@@ -6,59 +6,91 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.ui.draw.*
import androidx.compose.ui.graphics.*
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.*
import androidx.compose.ui.unit.dp import chat.simplex.common.platform.appPlatform
import chat.simplex.common.ui.theme.* import chat.simplex.common.ui.theme.*
import chat.simplex.res.MR import chat.simplex.res.MR
import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.painterResource
import kotlin.math.absoluteValue
@Composable @Composable
fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, tintColor: Color = if (close != null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, arrangement: Arrangement.Vertical = Arrangement.Top, closeBarTitle: String? = null, barPaddingValues: PaddingValues = PaddingValues(horizontal = AppBarHorizontalPadding), endButtons: @Composable RowScope.() -> Unit = {}) { fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, tintColor: Color = if (close != null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, arrangement: Arrangement.Vertical = Arrangement.Top, closeBarTitle: String? = null, barPaddingValues: PaddingValues = PaddingValues(horizontal = AppBarHorizontalPadding), endButtons: @Composable RowScope.() -> Unit = {}) {
var rowModifier = Modifier var rowModifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(AppBarHeight * fontSizeSqrtMultiplier) .height(AppBarHeight * fontSizeSqrtMultiplier)
val themeBackgroundMix = MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.97f)
if (!closeBarTitle.isNullOrEmpty()) { if (!closeBarTitle.isNullOrEmpty()) {
rowModifier = rowModifier.background(MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.97f)) rowModifier = rowModifier.background(themeBackgroundMix)
} }
val handler = LocalAppBarHandler.current
val connection = LocalAppBarHandler.current?.connection
val title = remember(handler?.title?.value) { handler?.title ?: mutableStateOf("") }
Column( Column(
verticalArrangement = arrangement, verticalArrangement = arrangement,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.heightIn(min = AppBarHeight * fontSizeSqrtMultiplier) .heightIn(min = AppBarHeight * fontSizeSqrtMultiplier)
.drawWithCache {
val backgroundColor = if (appPlatform.isDesktop && connection != null) themeBackgroundMix.copy(alpha = topTitleAlpha(connection)) else Color.Transparent
onDrawBehind {
if (appPlatform.isDesktop) {
drawRect(backgroundColor)
}
}
}
) { ) {
Row( Row(
modifier = Modifier.padding(barPaddingValues), modifier = Modifier.padding(barPaddingValues),
content = { content = {
Row( Row(
rowModifier, rowModifier,
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
if (showClose) { if (showClose) {
NavigationButtonBack(tintColor = tintColor, onButtonClicked = close) NavigationButtonBack(tintColor = tintColor, onButtonClicked = close)
} else { } else {
Spacer(Modifier) Spacer(Modifier)
} }
if (!closeBarTitle.isNullOrEmpty()) { if (!closeBarTitle.isNullOrEmpty()) {
Row( Row(
Modifier.weight(1f),
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Text( Text(
closeBarTitle, closeBarTitle,
color = MaterialTheme.colors.onBackground,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
maxLines = 1
) )
} }
} else if (title.value.isNotEmpty() && connection != null) {
Row(
Modifier
.padding(start = if (showClose) 0.dp else DEFAULT_PADDING_HALF)
.weight(1f) // hides the title if something wants full width (eg, search field in chat profiles screen)
.graphicsLayer {
alpha = topTitleAlpha((connection))
}
.padding(start = 4.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
title.value,
fontWeight = FontWeight.SemiBold,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
} else {
Spacer(Modifier.weight(1f))
} }
Row { Row {
endButtons() endButtons()
@@ -66,11 +98,24 @@ fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, tintColor: Co
} }
} }
) )
if (closeBarTitle.isNullOrEmpty() && title.value.isNotEmpty() && connection != null) {
Divider(
Modifier
.graphicsLayer {
alpha = topTitleAlpha(connection)
}
)
}
} }
} }
@Composable @Composable
fun AppBarTitle(title: String, hostDevice: Pair<Long?, String>? = null, withPadding: Boolean = true, bottomPadding: Dp = DEFAULT_PADDING * 1.5f + 8.dp) { fun AppBarTitle(title: String, hostDevice: Pair<Long?, String>? = null, withPadding: Boolean = true, bottomPadding: Dp = DEFAULT_PADDING * 1.5f + 8.dp) {
val handler = LocalAppBarHandler.current
val connection = handler?.connection
LaunchedEffect(title) {
handler?.title?.value = title
}
val theme = CurrentColors.collectAsState() val theme = CurrentColors.collectAsState()
val titleColor = MaterialTheme.appColors.title val titleColor = MaterialTheme.appColors.title
val brush = if (theme.value.base == DefaultTheme.SIMPLEX) val brush = if (theme.value.base == DefaultTheme.SIMPLEX)
@@ -81,23 +126,37 @@ fun AppBarTitle(title: String, hostDevice: Pair<Long?, String>? = null, withPad
Text( Text(
title, title,
Modifier Modifier
.fillMaxWidth() .padding(start = if (withPadding) DEFAULT_PADDING else 0.dp, top = DEFAULT_PADDING_HALF, end = if (withPadding) DEFAULT_PADDING else 0.dp,)
.padding(start = if (withPadding) DEFAULT_PADDING else 0.dp, end = if (withPadding) DEFAULT_PADDING else 0.dp,), .graphicsLayer {
alpha = bottomTitleAlpha(connection)
},
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.h1.copy(brush = brush), style = MaterialTheme.typography.h1.copy(brush = brush),
color = MaterialTheme.colors.primaryVariant, color = MaterialTheme.colors.primaryVariant,
textAlign = TextAlign.Center textAlign = TextAlign.Start
) )
if (hostDevice != null) { if (hostDevice != null) {
HostDeviceTitle(hostDevice) Box(Modifier.padding(start = if (withPadding) DEFAULT_PADDING else 0.dp, end = if (withPadding) DEFAULT_PADDING else 0.dp).graphicsLayer {
alpha = bottomTitleAlpha(connection)
}) {
HostDeviceTitle(hostDevice)
}
} }
Spacer(Modifier.height(bottomPadding)) Spacer(Modifier.height(bottomPadding))
} }
} }
private fun topTitleAlpha(connection: CollapsingAppBarNestedScrollConnection) =
if (connection.appBarOffset.absoluteValue < AppBarHandler.appBarMaxHeightPx / 3) 0f
else ((-connection.appBarOffset * 1.5f) / (AppBarHandler.appBarMaxHeightPx)).coerceIn(0f, 1f)
private fun bottomTitleAlpha(connection: CollapsingAppBarNestedScrollConnection?) =
if ((connection?.appBarOffset ?: 0f).absoluteValue < AppBarHandler.appBarMaxHeightPx / 3) 1f
else ((AppBarHandler.appBarMaxHeightPx) + (connection?.appBarOffset ?: 0f) / 1.5f).coerceAtLeast(0f) / AppBarHandler.appBarMaxHeightPx
@Composable @Composable
private fun HostDeviceTitle(hostDevice: Pair<Long?, String>, extraPadding: Boolean = false) { private fun HostDeviceTitle(hostDevice: Pair<Long?, String>, extraPadding: Boolean = false) {
Row(Modifier.fillMaxWidth().padding(top = 5.dp, bottom = if (extraPadding) DEFAULT_PADDING * 2 else DEFAULT_PADDING_HALF), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center) { Row(Modifier.fillMaxWidth().padding(top = 5.dp, bottom = if (extraPadding) DEFAULT_PADDING * 2 else DEFAULT_PADDING_HALF), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Start) {
Icon(painterResource(if (hostDevice.first == null) MR.images.ic_desktop else MR.images.ic_smartphone_300), null, Modifier.size(15.dp), tint = MaterialTheme.colors.secondary) Icon(painterResource(if (hostDevice.first == null) MR.images.ic_desktop else MR.images.ic_smartphone_300), null, Modifier.size(15.dp), tint = MaterialTheme.colors.secondary)
Spacer(Modifier.width(10.dp)) Spacer(Modifier.width(10.dp))
Text(hostDevice.second, color = MaterialTheme.colors.secondary) Text(hostDevice.second, color = MaterialTheme.colors.secondary)
@@ -0,0 +1,44 @@
package chat.simplex.common.views.helpers
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.*
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.unit.Velocity
val LocalAppBarHandler: ProvidableCompositionLocal<AppBarHandler?> = staticCompositionLocalOf { null }
@Stable
class AppBarHandler(
listState: LazyListState = LazyListState(0, 0),
scrollState: ScrollState = ScrollState(initial = 0)
) {
val title = mutableStateOf("")
var listState by mutableStateOf(listState, structuralEqualityPolicy())
internal set
var scrollState by mutableStateOf(scrollState, structuralEqualityPolicy())
internal set
val connection = CollapsingAppBarNestedScrollConnection()
companion object {
var appBarMaxHeightPx: Int = 0
}
}
class CollapsingAppBarNestedScrollConnection(): NestedScrollConnection {
var appBarOffset: Float by mutableFloatStateOf(0f)
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
appBarOffset += available.y
return Offset(0f, 0f)
}
override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {
appBarOffset -= available.y
return Offset(x = 0f, 0f)
}
}
@@ -25,9 +25,9 @@ fun DefaultSwitch(
) )
) { ) {
val color = if (checked) MaterialTheme.colors.primary.copy(alpha = 0.3f) else MaterialTheme.colors.secondary.copy(alpha = 0.3f) val color = if (checked) MaterialTheme.colors.primary.copy(alpha = 0.3f) else MaterialTheme.colors.secondary.copy(alpha = 0.3f)
val size = with(LocalDensity.current) { Size(46.dp.toPx(), 28.dp.toPx()) } val size = with(LocalDensity.current) { Size(40.dp.toPx(), 26.dp.toPx()) }
val offset = with(LocalDensity.current) { Offset(1.dp.toPx(), 10.dp.toPx()) } val offset = with(LocalDensity.current) { Offset(4.dp.toPx(), 11.dp.toPx()) }
val radius = with(LocalDensity.current) { 28.dp.toPx() } val radius = with(LocalDensity.current) { 13.dp.toPx() }
Switch( Switch(
checked = checked, checked = checked,
onCheckedChange = onCheckedChange, onCheckedChange = onCheckedChange,
@@ -50,7 +50,7 @@ fun <T> ExposedDropDownSetting(
) )
Spacer(Modifier.size(12.dp)) Spacer(Modifier.size(12.dp))
Icon( Icon(
if (!expanded.value) painterResource(MR.images.ic_expand_more) else painterResource(MR.images.ic_expand_less), if (!expanded.value) painterResource(MR.images.ic_arrow_drop_down) else painterResource(MR.images.ic_arrow_drop_up),
generalGetString(MR.strings.icon_descr_more_button), generalGetString(MR.strings.icon_descr_more_button),
tint = MaterialTheme.colors.secondary tint = MaterialTheme.colors.secondary
) )
@@ -34,6 +34,7 @@ expect fun authenticate(
promptSubtitle: String, promptSubtitle: String,
selfDestruct: Boolean = false, selfDestruct: Boolean = false,
usingLAMode: LAMode = ChatModel.controller.appPrefs.laMode.get(), usingLAMode: LAMode = ChatModel.controller.appPrefs.laMode.get(),
oneTime: Boolean,
completed: (LAResult) -> Unit completed: (LAResult) -> Unit
) )
@@ -41,10 +42,11 @@ fun authenticateWithPasscode(
promptTitle: String, promptTitle: String,
promptSubtitle: String, promptSubtitle: String,
selfDestruct: Boolean, selfDestruct: Boolean,
oneTime: Boolean,
completed: (LAResult) -> Unit completed: (LAResult) -> Unit
) { ) {
val password = DatabaseUtils.ksAppPassword.get() ?: return completed(LAResult.Unavailable(generalGetString(MR.strings.la_no_app_password))) val password = DatabaseUtils.ksAppPassword.get() ?: return completed(LAResult.Unavailable(generalGetString(MR.strings.la_no_app_password)))
ModalManager.fullscreen.showPasscodeCustomModal { close -> ModalManager.fullscreen.showPasscodeCustomModal(oneTime) { close ->
BackHandler { BackHandler {
close() close()
completed(LAResult.Error(generalGetString(MR.strings.authentication_cancelled))) completed(LAResult.Error(generalGetString(MR.strings.authentication_cancelled)))
@@ -2,11 +2,12 @@ package chat.simplex.common.views.helpers
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.animation.core.* import androidx.compose.animation.core.*
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatController.appPrefs
@@ -48,13 +49,15 @@ enum class ModalPlacement {
START, CENTER, END, FULLSCREEN START, CENTER, END, FULLSCREEN
} }
class ModalData { class ModalData() {
private val state = mutableMapOf<String, MutableState<Any?>>() private val state = mutableMapOf<String, MutableState<Any?>>()
fun <T> stateGetOrPut (key: String, default: () -> T): MutableState<T> = fun <T> stateGetOrPut (key: String, default: () -> T): MutableState<T> =
state.getOrPut(key) { mutableStateOf(default() as Any) } as MutableState<T> state.getOrPut(key) { mutableStateOf(default() as Any) } as MutableState<T>
fun <T> stateGetOrPutNullable (key: String, default: () -> T?): MutableState<T?> = fun <T> stateGetOrPutNullable (key: String, default: () -> T?): MutableState<T?> =
state.getOrPut(key) { mutableStateOf(default() as Any?) } as MutableState<T?> state.getOrPut(key) { mutableStateOf(default() as Any?) } as MutableState<T?>
val appBarHandler = AppBarHandler()
} }
class ModalManager(private val placement: ModalPlacement? = null) { class ModalManager(private val placement: ModalPlacement? = null) {
@@ -66,6 +69,7 @@ class ModalManager(private val placement: ModalPlacement? = null) {
// Don't use mutableStateOf() here, because it produces this if showing from SimpleXAPI.startChat(): // Don't use mutableStateOf() here, because it produces this if showing from SimpleXAPI.startChat():
// java.lang.IllegalStateException: Reading a state that was created after the snapshot was taken or in a snapshot that has not yet been applied // java.lang.IllegalStateException: Reading a state that was created after the snapshot was taken or in a snapshot that has not yet been applied
private var passcodeView: MutableStateFlow<(@Composable (close: () -> Unit) -> Unit)?> = MutableStateFlow(null) private var passcodeView: MutableStateFlow<(@Composable (close: () -> Unit) -> Unit)?> = MutableStateFlow(null)
private var onTimePasscodeView: MutableStateFlow<(@Composable (close: () -> Unit) -> Unit)?> = MutableStateFlow(null)
fun showModal(settings: Boolean = false, showClose: Boolean = true, closeOnTop: Boolean = true, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable ModalData.() -> Unit) { fun showModal(settings: Boolean = false, showClose: Boolean = true, closeOnTop: Boolean = true, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable ModalData.() -> Unit) {
val data = ModalData() val data = ModalData()
@@ -102,9 +106,13 @@ class ModalManager(private val placement: ModalPlacement? = null) {
} }
} }
fun showPasscodeCustomModal(modal: @Composable (close: () -> Unit) -> Unit) { fun showPasscodeCustomModal(oneTime: Boolean, modal: @Composable (close: () -> Unit) -> Unit) {
Log.d(TAG, "ModalManager.showPasscodeCustomModal") Log.d(TAG, "ModalManager.showPasscodeCustomModal, oneTime: $oneTime")
passcodeView.value = modal if (oneTime) {
onTimePasscodeView.value = modal
} else {
passcodeView.value = modal
}
} }
fun hasModalsOpen() = modalCount.value > 0 fun hasModalsOpen() = modalCount.value > 0
@@ -139,7 +147,13 @@ class ModalManager(private val placement: ModalPlacement? = null) {
fun showInView() { fun showInView() {
// Without animation // Without animation
if (modalCount.value > 0 && modalViews.lastOrNull()?.first == false) { if (modalCount.value > 0 && modalViews.lastOrNull()?.first == false) {
modalViews.lastOrNull()?.let { it.third(it.second, ::closeModal) } modalViews.lastOrNull()?.let {
CompositionLocalProvider(
LocalAppBarHandler provides it.second.appBarHandler
) {
it.third(it.second, ::closeModal)
}
}
return return
} }
AnimatedContent(targetState = modalCount.value, AnimatedContent(targetState = modalCount.value,
@@ -151,7 +165,13 @@ class ModalManager(private val placement: ModalPlacement? = null) {
}.using(SizeTransform(clip = false)) }.using(SizeTransform(clip = false))
} }
) { ) {
modalViews.getOrNull(it - 1)?.let { it.third(it.second, ::closeModal) } modalViews.getOrNull(it - 1)?.let {
CompositionLocalProvider(
LocalAppBarHandler provides it.second.appBarHandler
) {
it.third(it.second, ::closeModal)
}
}
// This is needed because if we delete from modalViews immediately on request, animation will be bad // This is needed because if we delete from modalViews immediately on request, animation will be bad
if (toRemove.isNotEmpty() && it == modalCount.value && transition.currentState == EnterExitState.Visible && !transition.isRunning) { if (toRemove.isNotEmpty() && it == modalCount.value && transition.currentState == EnterExitState.Visible && !transition.isRunning) {
runAtomically { toRemove.removeIf { elem -> modalViews.removeAt(elem); true } } runAtomically { toRemove.removeIf { elem -> modalViews.removeAt(elem); true } }
@@ -164,6 +184,11 @@ class ModalManager(private val placement: ModalPlacement? = null) {
passcodeView.collectAsState().value?.invoke { passcodeView.value = null } passcodeView.collectAsState().value?.invoke { passcodeView.value = null }
} }
@Composable
fun showOneTimePasscodeInView() {
onTimePasscodeView.collectAsState().value?.invoke { onTimePasscodeView.value = null }
}
/** /**
* Allows to modify a list without getting [ConcurrentModificationException] * Allows to modify a list without getting [ConcurrentModificationException]
* */ * */
@@ -12,7 +12,6 @@ import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.* import androidx.compose.ui.unit.*
import chat.simplex.common.model.NotificationsMode
import chat.simplex.common.platform.onRightClick import chat.simplex.common.platform.onRightClick
import chat.simplex.common.platform.windowWidth import chat.simplex.common.platform.windowWidth
import chat.simplex.common.ui.theme.* import chat.simplex.common.ui.theme.*
@@ -20,18 +19,17 @@ import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.onboarding.SelectableCard import chat.simplex.common.views.onboarding.SelectableCard
import chat.simplex.common.views.usersettings.SettingsActionItemWithContent import chat.simplex.common.views.usersettings.SettingsActionItemWithContent
import chat.simplex.res.MR import chat.simplex.res.MR
import dev.icerock.moko.resources.compose.stringResource
@Composable @Composable
fun SectionView(title: String? = null, padding: PaddingValues = PaddingValues(), content: (@Composable ColumnScope.() -> Unit)) { fun SectionView(title: String? = null, contentPadding: PaddingValues = PaddingValues(), headerBottomPadding: Dp = DEFAULT_PADDING, content: (@Composable ColumnScope.() -> Unit)) {
Column { Column {
if (title != null) { if (title != null) {
Text( Text(
title, color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2, title, color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2,
modifier = Modifier.padding(start = DEFAULT_PADDING, bottom = 5.dp), fontSize = 12.sp modifier = Modifier.padding(start = DEFAULT_PADDING, bottom = headerBottomPadding), fontSize = 12.sp
) )
} }
Column(Modifier.padding(padding).fillMaxWidth()) { content() } Column(Modifier.padding(contentPadding).fillMaxWidth()) { content() }
} }
} }
@@ -101,13 +99,13 @@ fun <T> SectionViewSelectableCards(
@Composable @Composable
fun SectionItemView( fun SectionItemView(
click: (() -> Unit)? = null, click: (() -> Unit)? = null,
minHeight: Dp = 46.dp, minHeight: Dp = DEFAULT_MIN_SECTION_ITEM_HEIGHT,
disabled: Boolean = false, disabled: Boolean = false,
extraPadding: Boolean = false, extraPadding: Boolean = false,
padding: PaddingValues = if (extraPadding) padding: PaddingValues = if (extraPadding)
PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING) PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING, top = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL, bottom = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL)
else else
PaddingValues(horizontal = DEFAULT_PADDING), PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL),
content: (@Composable RowScope.() -> Unit) content: (@Composable RowScope.() -> Unit)
) { ) {
val modifier = Modifier val modifier = Modifier
@@ -122,10 +120,9 @@ fun SectionItemView(
} }
@Composable @Composable
fun SectionItemViewLongClickable( fun SectionItemViewWithoutMinPadding(
click: () -> Unit, click: (() -> Unit)? = null,
longClick: () -> Unit, minHeight: Dp = DEFAULT_MIN_SECTION_ITEM_HEIGHT,
minHeight: Dp = 46.dp,
disabled: Boolean = false, disabled: Boolean = false,
extraPadding: Boolean = false, extraPadding: Boolean = false,
padding: PaddingValues = if (extraPadding) padding: PaddingValues = if (extraPadding)
@@ -133,6 +130,22 @@ fun SectionItemViewLongClickable(
else else
PaddingValues(horizontal = DEFAULT_PADDING), PaddingValues(horizontal = DEFAULT_PADDING),
content: (@Composable RowScope.() -> Unit) content: (@Composable RowScope.() -> Unit)
) {
SectionItemView(click, minHeight, disabled, extraPadding, padding, content)
}
@Composable
fun SectionItemViewLongClickable(
click: () -> Unit,
longClick: () -> Unit,
minHeight: Dp = DEFAULT_MIN_SECTION_ITEM_HEIGHT,
disabled: Boolean = false,
extraPadding: Boolean = false,
padding: PaddingValues = if (extraPadding)
PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING, top = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL, bottom = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL)
else
PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL),
content: (@Composable RowScope.() -> Unit)
) { ) {
val modifier = Modifier val modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -149,30 +162,11 @@ fun SectionItemViewLongClickable(
} }
} }
@Composable
fun SectionItemViewWithIcon(
click: (() -> Unit)? = null,
minHeight: Dp = 46.dp,
disabled: Boolean = false,
padding: PaddingValues = PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING),
content: (@Composable RowScope.() -> Unit)
) {
val modifier = Modifier
.fillMaxWidth()
.sizeIn(minHeight = minHeight)
Row(
if (click == null || disabled) modifier.padding(padding) else modifier.clickable(onClick = click).padding(padding),
verticalAlignment = Alignment.CenterVertically
) {
content()
}
}
@Composable @Composable
fun SectionItemViewSpaceBetween( fun SectionItemViewSpaceBetween(
click: (() -> Unit)? = null, click: (() -> Unit)? = null,
onLongClick: (() -> Unit)? = null, onLongClick: (() -> Unit)? = null,
minHeight: Dp = 46.dp, minHeight: Dp = DEFAULT_MIN_SECTION_ITEM_HEIGHT,
padding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING), padding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING),
disabled: Boolean = false, disabled: Boolean = false,
content: (@Composable RowScope.() -> Unit) content: (@Composable RowScope.() -> Unit)
@@ -181,7 +175,7 @@ fun SectionItemViewSpaceBetween(
.fillMaxWidth() .fillMaxWidth()
.sizeIn(minHeight = minHeight) .sizeIn(minHeight = minHeight)
Row( Row(
if (click == null || disabled) modifier.padding(padding) else modifier if (click == null || disabled) modifier.padding(padding).padding(vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL) else modifier
.combinedClickable(onClick = click, onLongClick = onLongClick).padding(padding) .combinedClickable(onClick = click, onLongClick = onLongClick).padding(padding)
.onRightClick { onLongClick?.invoke() }, .onRightClick { onLongClick?.invoke() },
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
@@ -254,9 +248,9 @@ fun SectionDividerSpaced(maxTopPadding: Boolean = false, maxBottomPadding: Boole
Divider( Divider(
Modifier.padding( Modifier.padding(
start = DEFAULT_PADDING_HALF, start = DEFAULT_PADDING_HALF,
top = if (maxTopPadding) 37.dp else 27.dp, top = if (maxTopPadding) DEFAULT_PADDING + 18.dp else DEFAULT_PADDING + 2.dp,
end = DEFAULT_PADDING_HALF, end = DEFAULT_PADDING_HALF,
bottom = if (maxBottomPadding) 37.dp else 27.dp) bottom = if (maxBottomPadding) DEFAULT_PADDING + 18.dp else DEFAULT_PADDING + 2.dp)
) )
} }
@@ -1,6 +1,7 @@
package chat.simplex.common.views.helpers package chat.simplex.common.views.helpers
import SectionBottomSpacer import SectionBottomSpacer
import SectionDividerSpaced
import SectionItemView import SectionItemView
import SectionSpacer import SectionSpacer
import SectionView import SectionView
@@ -203,11 +204,12 @@ fun ModalData.UserWallpaperEditor(
} }
) )
SectionSpacer() SectionDividerSpaced()
AppearanceScope.CustomizeThemeColorsSection(currentTheme, editColor = editColor) AppearanceScope.CustomizeThemeColorsSection(currentTheme, editColor = editColor)
SectionSpacer() SectionDividerSpaced(maxBottomPadding = false)
ImportExportThemeSection(null, remember { chatModel.currentUser }.value?.uiThemes) { ImportExportThemeSection(null, remember { chatModel.currentUser }.value?.uiThemes) {
withBGApi { withBGApi {
themeModeOverride.value = it themeModeOverride.value = it
@@ -440,11 +442,11 @@ fun ModalData.ChatWallpaperEditor(
} }
) )
SectionSpacer() SectionDividerSpaced()
AppearanceScope.CustomizeThemeColorsSection(currentTheme, editColor = editColor) AppearanceScope.CustomizeThemeColorsSection(currentTheme, editColor = editColor)
SectionSpacer() SectionDividerSpaced(maxBottomPadding = false)
ImportExportThemeSection(themeModeOverride.value, remember { chatModel.currentUser }.value?.uiThemes) { ImportExportThemeSection(themeModeOverride.value, remember { chatModel.currentUser }.value?.uiThemes) {
withBGApi { withBGApi {
themeModeOverride.value = it themeModeOverride.value = it
@@ -4,7 +4,7 @@ import SectionBottomSpacer
import SectionSpacer import SectionSpacer
import SectionTextFooter import SectionTextFooter
import SectionView import SectionView
import androidx.compose.foundation.* import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
@@ -17,7 +17,6 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import chat.simplex.common.model.* import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.model.ChatController.getNetCfg import chat.simplex.common.model.ChatController.getNetCfg
import chat.simplex.common.model.ChatController.startChat import chat.simplex.common.model.ChatController.startChat
import chat.simplex.common.model.ChatController.startChatWithTemporaryDatabase import chat.simplex.common.model.ChatController.startChatWithTemporaryDatabase
@@ -147,20 +146,13 @@ private fun MigrateFromDeviceLayout(
) { ) {
val tempDatabaseFile = rememberSaveable { mutableStateOf(fileForTemporaryDatabase()) } val tempDatabaseFile = rememberSaveable { mutableStateOf(fileForTemporaryDatabase()) }
val (scrollBarAlpha, scrollModifier, scrollJob) = platform.desktopScrollBarComponents() ColumnWithScrollBar(
val scrollState = rememberScrollState() Modifier.fillMaxSize(), maxIntrinsicSize = true
Column(
Modifier.fillMaxSize().verticalScroll(scrollState).then(if (appPlatform.isDesktop) scrollModifier else Modifier).height(IntrinsicSize.Max),
) { ) {
AppBarTitle(stringResource(MR.strings.migrate_from_device_title)) AppBarTitle(stringResource(MR.strings.migrate_from_device_title))
SectionByState(migrationState, tempDatabaseFile.value, chatReceiver) SectionByState(migrationState, tempDatabaseFile.value, chatReceiver)
SectionBottomSpacer() SectionBottomSpacer()
} }
if (appPlatform.isDesktop) {
Box(Modifier.fillMaxSize()) {
platform.desktopScrollBar(scrollState, Modifier.align(Alignment.CenterEnd).fillMaxHeight(), scrollBarAlpha, scrollJob, false)
}
}
platform.androidLockPortraitOrientation() platform.androidLockPortraitOrientation()
} }
@@ -155,20 +155,13 @@ private fun ModalData.MigrateToDeviceLayout(
close: () -> Unit, close: () -> Unit,
) { ) {
val tempDatabaseFile = rememberSaveable { mutableStateOf(fileForTemporaryDatabase()) } val tempDatabaseFile = rememberSaveable { mutableStateOf(fileForTemporaryDatabase()) }
val (scrollBarAlpha, scrollModifier, scrollJob) = platform.desktopScrollBarComponents() ColumnWithScrollBar(
val scrollState = rememberScrollState() Modifier.fillMaxSize(), maxIntrinsicSize = true
Column(
Modifier.fillMaxSize().verticalScroll(scrollState).then(if (appPlatform.isDesktop) scrollModifier else Modifier).height(IntrinsicSize.Max),
) { ) {
AppBarTitle(stringResource(MR.strings.migrate_to_device_title)) AppBarTitle(stringResource(MR.strings.migrate_to_device_title))
SectionByState(migrationState, tempDatabaseFile.value, chatReceiver, close) SectionByState(migrationState, tempDatabaseFile.value, chatReceiver, close)
SectionBottomSpacer() SectionBottomSpacer()
} }
if (appPlatform.isDesktop) {
Box(Modifier.fillMaxSize()) {
platform.desktopScrollBar(scrollState, Modifier.align(Alignment.CenterEnd).fillMaxHeight(), scrollBarAlpha, scrollJob, false)
}
}
platform.androidLockPortraitOrientation() platform.androidLockPortraitOrientation()
} }
@@ -142,7 +142,7 @@ private fun ContactConnectionInfoLayout(
} }
SectionTextFooter(sharedProfileInfo(chatModel, contactConnection.incognito)) SectionTextFooter(sharedProfileInfo(chatModel, contactConnection.incognito))
SectionDividerSpaced(maxBottomPadding = false) SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
DeleteButton(deleteConnection) DeleteButton(deleteConnection)
@@ -1,7 +1,9 @@
package chat.simplex.common.views.newchat package chat.simplex.common.views.newchat
import SectionDivider
import SectionDividerSpaced import SectionDividerSpaced
import SectionItemView import SectionItemView
import SectionSpacer
import SectionView import SectionView
import TextIconSpaced import TextIconSpaced
import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.desktop.ui.tooling.preview.Preview
@@ -68,10 +70,10 @@ fun NewChatSheet(rh: RemoteHostInfo?, close: () -> Unit) {
Column(modifier = Modifier.fillMaxSize()) { Column(modifier = Modifier.fillMaxSize()) {
NewChatSheetLayout( NewChatSheetLayout(
addContact = { addContact = {
ModalManager.start.showModalCloseable { _ -> NewChatView(chatModel.currentRemoteHost.value, NewChatOption.INVITE, close = closeAll ) } ModalManager.start.showModalCloseable(endButtons = { AddContactLearnMoreButton() }) { _ -> NewChatView(chatModel.currentRemoteHost.value, NewChatOption.INVITE, close = closeAll ) }
}, },
scanPaste = { scanPaste = {
ModalManager.start.showModalCloseable { _ -> NewChatView(chatModel.currentRemoteHost.value, NewChatOption.CONNECT, showQRCodeScanner = appPlatform.isAndroid, close = closeAll) } ModalManager.start.showModalCloseable(endButtons = { AddContactLearnMoreButton() }) { _ -> NewChatView(chatModel.currentRemoteHost.value, NewChatOption.CONNECT, showQRCodeScanner = appPlatform.isAndroid, close = closeAll) }
}, },
createGroup = { createGroup = {
ModalManager.start.showCustomModal { close -> AddGroupView(chatModel, chatModel.currentRemoteHost.value, close, closeAll) } ModalManager.start.showCustomModal { close -> AddGroupView(chatModel, chatModel.currentRemoteHost.value, close, closeAll) }
@@ -164,6 +166,10 @@ private fun NewChatSheetLayout(
) )
val sectionModifier = Modifier.fillMaxWidth() val sectionModifier = Modifier.fillMaxWidth()
val deletedContactTypes = listOf(ContactType.CHAT_DELETED)
val deletedChats by remember(chatModel.chats.value, deletedContactTypes) {
derivedStateOf { filterContactTypes(chatModel.chats.value, deletedContactTypes) }
}
LazyColumnWithScrollBar( LazyColumnWithScrollBar(
Modifier.fillMaxSize(), Modifier.fillMaxSize(),
@@ -194,7 +200,15 @@ private fun NewChatSheetLayout(
(appPlatform.isAndroid && keyboardState == KeyboardState.Opened) (appPlatform.isAndroid && keyboardState == KeyboardState.Opened)
) { ) {
0 0
} else if (listState.firstVisibleItemIndex == 0) offsetMultiplier * listState.firstVisibleItemScrollOffset else offsetMultiplier * 1000 } else if (oneHandUI.value && listState.firstVisibleItemIndex == 0) {
listState.firstVisibleItemScrollOffset
} else if (!oneHandUI.value && listState.firstVisibleItemIndex == 0) {
0
} else if (!oneHandUI.value && listState.firstVisibleItemIndex == 1) {
-listState.firstVisibleItemScrollOffset
} else {
offsetMultiplier * 1000
}
} else { } else {
0 0
} }
@@ -216,7 +230,9 @@ private fun NewChatSheetLayout(
} }
} }
item { item {
Spacer(Modifier.padding(bottom = 27.dp)) if (searchText.value.text.isEmpty()) {
Spacer(Modifier.padding(bottom = 27.dp))
}
val actionButtonsOriginal = listOf( val actionButtonsOriginal = listOf(
Triple( Triple(
@@ -254,50 +270,59 @@ private fun NewChatSheetLayout(
} }
} }
} }
SectionDividerSpaced(maxBottomPadding = false)
val deletedContactTypes = listOf(ContactType.CHAT_DELETED)
val deletedChats by remember(chatModel.chats.value, deletedContactTypes) {
derivedStateOf { filterContactTypes(chatModel.chats.value, deletedContactTypes) }
}
if (deletedChats.isNotEmpty()) { if (deletedChats.isNotEmpty()) {
Row(modifier = sectionModifier) { SectionDividerSpaced(maxBottomPadding = false)
SectionView { SectionView {
SectionItemView( SectionItemView(
click = { click = {
ModalManager.start.showCustomModal { closeDeletedChats -> ModalManager.start.showCustomModal { closeDeletedChats ->
ModalView( ModalView(
close = closeDeletedChats, close = closeDeletedChats,
closeOnTop = !oneHandUI.value, closeOnTop = !oneHandUI.value,
) { ) {
DeletedContactsView(rh = rh, closeDeletedChats = closeDeletedChats, close = { DeletedContactsView(rh = rh, closeDeletedChats = closeDeletedChats, close = {
ModalManager.start.closeModals() ModalManager.start.closeModals()
}) })
}
} }
} }
) {
Icon(
painterResource(MR.images.ic_inventory_2),
contentDescription = stringResource(MR.strings.deleted_chats),
tint = MaterialTheme.colors.secondary,
)
TextIconSpaced(false)
Text(text = stringResource(MR.strings.deleted_chats), color = MaterialTheme.colors.onBackground)
} }
) {
Icon(
painterResource(MR.images.ic_inventory_2),
contentDescription = stringResource(MR.strings.deleted_chats),
tint = MaterialTheme.colors.secondary,
)
TextIconSpaced(false)
Text(text = stringResource(MR.strings.deleted_chats), color = MaterialTheme.colors.onBackground)
} }
} }
SectionDividerSpaced(maxBottomPadding = false)
} }
} }
} }
item { item {
if (filteredContactChats.isNotEmpty() && !oneHandUI.value) { if (filteredContactChats.isNotEmpty() && searchText.value.text.isEmpty()) {
Text( if (!oneHandUI.value) {
stringResource(MR.strings.contact_list_header_title).uppercase(), color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2, SectionDividerSpaced()
modifier = sectionModifier.padding(start = DEFAULT_PADDING, top = DEFAULT_PADDING_HALF, bottom = DEFAULT_PADDING_HALF), fontSize = 12.sp SectionView(stringResource(MR.strings.contact_list_header_title).uppercase(), headerBottomPadding = DEFAULT_PADDING_HALF) {}
) } else {
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = false)
SectionView(stringResource(MR.strings.contact_list_header_title).uppercase(), headerBottomPadding = DEFAULT_PADDING_HALF) {}
Spacer(Modifier.height(DEFAULT_PADDING_HALF))
}
}
}
item {
if (filteredContactChats.isEmpty() && allChats.isNotEmpty()) {
Column(sectionModifier.fillMaxSize().padding(DEFAULT_PADDING)) {
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
Text(
generalGetString(MR.strings.no_filtered_contacts),
color = MaterialTheme.colors.secondary
)
}
}
} }
} }
@@ -310,17 +335,6 @@ private fun NewChatSheetLayout(
ContactListNavLinkView(chat, nextChatSelected, showDeletedChatIcon = true) ContactListNavLinkView(chat, nextChatSelected, showDeletedChatIcon = true)
} }
} }
if (filteredContactChats.isEmpty() && allChats.isNotEmpty()) {
Column(sectionModifier.fillMaxSize().padding(DEFAULT_PADDING)) {
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
Text(
generalGetString(MR.strings.no_filtered_contacts),
color = MaterialTheme.colors.secondary
)
}
}
}
} }
@Composable @Composable
@@ -356,7 +370,7 @@ private fun ContactsSearchBar(
Icon( Icon(
painterResource(MR.images.ic_search), painterResource(MR.images.ic_search),
contentDescription = null, contentDescription = null,
Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING_HALF).size(24.dp * fontSizeSqrtMultiplier), Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING_HALF).size(22.dp * fontSizeSqrtMultiplier),
tint = MaterialTheme.colors.secondary tint = MaterialTheme.colors.secondary
) )
SearchTextField( SearchTextField(
@@ -543,78 +557,75 @@ private fun DeletedContactsView(rh: RemoteHostInfo?, closeDeletedChats: () -> Un
} }
} }
} }
) { ) { contentPadding ->
Column( val listState = rememberLazyListState(lazyListState.first, lazyListState.second)
Modifier val searchText = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) }
.fillMaxSize() val searchShowingSimplexLink = remember { mutableStateOf(false) }
.padding(it) val searchChatFilteredBySimplexLink = remember { mutableStateOf<String?>(null) }
val showUnreadAndFavorites = remember { appPrefs.showUnreadAndFavorites.state }.value
val allChats by remember(chatModel.chats.value) {
derivedStateOf { filterContactTypes(chatModel.chats.value, listOf(ContactType.CHAT_DELETED)) }
}
val filteredContactChats = filteredContactChats(
showUnreadAndFavorites = showUnreadAndFavorites,
searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink,
searchShowingSimplexLink = searchShowingSimplexLink,
searchText = searchText.value.text,
contactChats = allChats
)
LazyColumnWithScrollBar(
Modifier.fillMaxSize(),
contentPadding = contentPadding,
reverseLayout = oneHandUI.value,
) { ) {
if (!oneHandUI.value) { item {
Box(contentAlignment = Alignment.Center) { if (!oneHandUI.value) {
val bottomPadding = DEFAULT_PADDING Box(contentAlignment = Alignment.Center) {
AppBarTitle( val bottomPadding = DEFAULT_PADDING
stringResource(MR.strings.deleted_chats), AppBarTitle(
hostDevice(rh?.remoteHostId), stringResource(MR.strings.deleted_chats),
bottomPadding = bottomPadding hostDevice(rh?.remoteHostId),
) bottomPadding = bottomPadding
}
}
val listState = rememberLazyListState(lazyListState.first, lazyListState.second)
val searchText = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) }
val searchShowingSimplexLink = remember { mutableStateOf(false) }
val searchChatFilteredBySimplexLink = remember { mutableStateOf<String?>(null) }
val showUnreadAndFavorites = remember { appPrefs.showUnreadAndFavorites.state }.value
val allChats by remember(chatModel.chats.value) {
derivedStateOf { filterContactTypes(chatModel.chats.value, listOf(ContactType.CHAT_DELETED)) }
}
val filteredContactChats = filteredContactChats(
showUnreadAndFavorites = showUnreadAndFavorites,
searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink,
searchShowingSimplexLink = searchShowingSimplexLink,
searchText = searchText.value.text,
contactChats = allChats
)
LazyColumnWithScrollBar(
Modifier.fillMaxSize(),
reverseLayout = oneHandUI.value,
) {
item {
if (!oneHandUI.value) {
Divider()
}
ContactsSearchBar(
listState = listState,
searchText = searchText,
searchShowingSimplexLink = searchShowingSimplexLink,
searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink,
close = close,
)
Divider()
Spacer(Modifier.padding(bottom = DEFAULT_PADDING))
}
itemsIndexed(filteredContactChats) { index, chat ->
val nextChatSelected = remember(chat.id, filteredContactChats) {
derivedStateOf {
chatModel.chatId.value != null && filteredContactChats.getOrNull(index + 1)?.id == chatModel.chatId.value
}
}
ContactListNavLinkView(chat, nextChatSelected, showDeletedChatIcon = false)
}
}
if (filteredContactChats.isEmpty() && allChats.isNotEmpty()) {
Column(Modifier.fillMaxSize().padding(DEFAULT_PADDING)) {
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
Text(
generalGetString(MR.strings.no_filtered_contacts),
color = MaterialTheme.colors.secondary,
) )
} }
} }
} }
item {
if (!oneHandUI.value) {
Divider()
}
ContactsSearchBar(
listState = listState,
searchText = searchText,
searchShowingSimplexLink = searchShowingSimplexLink,
searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink,
close = close,
)
Divider()
}
item {
if (filteredContactChats.isEmpty() && allChats.isNotEmpty()) {
Column(Modifier.fillMaxSize().padding(DEFAULT_PADDING)) {
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
Text(
generalGetString(MR.strings.no_filtered_contacts),
color = MaterialTheme.colors.secondary,
)
}
}
}
}
itemsIndexed(filteredContactChats) { index, chat ->
val nextChatSelected = remember(chat.id, filteredContactChats) {
derivedStateOf {
chatModel.chatId.value != null && filteredContactChats.getOrNull(index + 1)?.id == chatModel.chatId.value
}
}
ContactListNavLinkView(chat, nextChatSelected, showDeletedChatIcon = false)
}
} }
} }
} }
@@ -5,6 +5,7 @@ import SectionItemView
import SectionTextFooter import SectionTextFooter
import SectionView import SectionView
import androidx.compose.foundation.* import androidx.compose.foundation.*
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.HorizontalPager
@@ -96,66 +97,61 @@ fun ModalData.NewChatView(rh: RemoteHostInfo?, selection: NewChatOption, showQRC
} }
} }
Column( BoxWithConstraints {
Modifier.fillMaxSize(), ColumnWithScrollBar {
) { AppBarTitle(stringResource(MR.strings.new_chat), hostDevice(rh?.remoteHostId), bottomPadding = DEFAULT_PADDING)
Box(contentAlignment = Alignment.Center) { val scope = rememberCoroutineScope()
val bottomPadding = DEFAULT_PADDING val pagerState = rememberPagerState(
AppBarTitle(stringResource(MR.strings.new_chat), hostDevice(rh?.remoteHostId), bottomPadding = bottomPadding) initialPage = selection.value.ordinal,
Column(Modifier.align(Alignment.CenterEnd).padding(bottom = bottomPadding, end = DEFAULT_PADDING)) { initialPageOffsetFraction = 0f
AddContactLearnMoreButton() ) { NewChatOption.values().size }
KeyChangeEffect(pagerState.currentPage) {
selection.value = NewChatOption.values()[pagerState.currentPage]
} }
} TabRow(
val scope = rememberCoroutineScope() selectedTabIndex = pagerState.currentPage,
val pagerState = rememberPagerState( backgroundColor = Color.Transparent,
initialPage = selection.value.ordinal, contentColor = MaterialTheme.colors.primary,
initialPageOffsetFraction = 0f ) {
) { NewChatOption.values().size } tabTitles.forEachIndexed { index, it ->
KeyChangeEffect(pagerState.currentPage) { LeadingIconTab(
selection.value = NewChatOption.values()[pagerState.currentPage] selected = pagerState.currentPage == index,
} onClick = {
TabRow( scope.launch {
selectedTabIndex = pagerState.currentPage, pagerState.animateScrollToPage(index)
backgroundColor = Color.Transparent, }
contentColor = MaterialTheme.colors.primary, },
) { text = { Text(it, fontSize = 13.sp) },
tabTitles.forEachIndexed { index, it -> icon = {
LeadingIconTab( Icon(
selected = pagerState.currentPage == index, if (NewChatOption.INVITE.ordinal == index) painterResource(MR.images.ic_repeat_one) else painterResource(MR.images.ic_qr_code),
onClick = { it
scope.launch { )
pagerState.animateScrollToPage(index) },
} selectedContentColor = MaterialTheme.colors.primary,
}, unselectedContentColor = MaterialTheme.colors.secondary,
text = { Text(it, fontSize = 13.sp) }, )
icon = { }
Icon( }
if (NewChatOption.INVITE.ordinal == index) painterResource(MR.images.ic_repeat_one) else painterResource(MR.images.ic_qr_code),
it HorizontalPager(state = pagerState, Modifier, pageNestedScrollConnection = LocalAppBarHandler.current!!.connection, verticalAlignment = Alignment.Top, userScrollEnabled = appPlatform.isAndroid) { index ->
) Column(
}, Modifier
selectedContentColor = MaterialTheme.colors.primary, .fillMaxWidth()
unselectedContentColor = MaterialTheme.colors.secondary, .heightIn(min = this@BoxWithConstraints.maxHeight - 150.dp),
) verticalArrangement = if (index == NewChatOption.INVITE.ordinal && connReqInvitation.isEmpty()) Arrangement.Center else Arrangement.Top
} ) {
} Spacer(Modifier.height(DEFAULT_PADDING))
when (index) {
HorizontalPager(state = pagerState, Modifier.fillMaxSize(), verticalAlignment = Alignment.Top, userScrollEnabled = appPlatform.isAndroid) { index -> NewChatOption.INVITE.ordinal -> {
// LALAL SCROLLBAR DOESN'T WORK PrepareAndInviteView(rh?.remoteHostId, contactConnection, connReqInvitation, creatingConnReq)
ColumnWithScrollBar( }
Modifier NewChatOption.CONNECT.ordinal -> {
.fillMaxSize(), ConnectView(rh?.remoteHostId, showQRCodeScanner, pastedLink, close)
verticalArrangement = if (index == NewChatOption.INVITE.ordinal && connReqInvitation.isEmpty()) Arrangement.Center else Arrangement.Top) { }
Spacer(Modifier.height(DEFAULT_PADDING)) }
when (index) { SectionBottomSpacer()
NewChatOption.INVITE.ordinal -> {
PrepareAndInviteView(rh?.remoteHostId, contactConnection, connReqInvitation, creatingConnReq)
}
NewChatOption.CONNECT.ordinal -> {
ConnectView(rh?.remoteHostId, showQRCodeScanner, pastedLink, close)
}
} }
SectionBottomSpacer()
} }
} }
} }
@@ -198,13 +194,13 @@ private fun RetryButton(onClick: () -> Unit) {
@Composable @Composable
private fun InviteView(rhId: Long?, connReqInvitation: String, contactConnection: MutableState<PendingContactConnection?>) { private fun InviteView(rhId: Long?, connReqInvitation: String, contactConnection: MutableState<PendingContactConnection?>) {
SectionView(stringResource(MR.strings.share_this_1_time_link).uppercase()) { SectionView(stringResource(MR.strings.share_this_1_time_link).uppercase(), headerBottomPadding = 5.dp) {
LinkTextView(connReqInvitation, true) LinkTextView(connReqInvitation, true)
} }
Spacer(Modifier.height(10.dp)) Spacer(Modifier.height(10.dp))
SectionView(stringResource(MR.strings.or_show_this_qr_code).uppercase()) { SectionView(stringResource(MR.strings.or_show_this_qr_code).uppercase(), headerBottomPadding = 5.dp) {
SimpleXLinkQRCode(connReqInvitation, onShare = { chatModel.markShowingInvitationUsed() }) SimpleXLinkQRCode(connReqInvitation, onShare = { chatModel.markShowingInvitationUsed() })
} }
@@ -228,32 +224,32 @@ private fun InviteView(rhId: Long?, connReqInvitation: String, contactConnection
} }
@Composable @Composable
private fun AddContactLearnMoreButton() { fun AddContactLearnMoreButton() {
IconButton( IconButton(
{ {
ModalManager.start.showModalCloseable { close -> ModalManager.start.showModalCloseable { close ->
AddContactLearnMore(close) AddContactLearnMore(close)
} }
}, }
Modifier.size(18.dp * fontSizeSqrtMultiplier)
) { ) {
Icon( Icon(
painterResource(MR.images.ic_info), painterResource(MR.images.ic_info),
stringResource(MR.strings.learn_more), stringResource(MR.strings.learn_more),
tint = MaterialTheme.colors.primary
) )
} }
} }
@Composable @Composable
private fun ConnectView(rhId: Long?, showQRCodeScanner: MutableState<Boolean>, pastedLink: MutableState<String>, close: () -> Unit) { private fun ConnectView(rhId: Long?, showQRCodeScanner: MutableState<Boolean>, pastedLink: MutableState<String>, close: () -> Unit) {
SectionView(stringResource(MR.strings.paste_the_link_you_received).uppercase()) { SectionView(stringResource(MR.strings.paste_the_link_you_received).uppercase(), headerBottomPadding = 5.dp) {
PasteLinkView(rhId, pastedLink, showQRCodeScanner, close) PasteLinkView(rhId, pastedLink, showQRCodeScanner, close)
} }
if (appPlatform.isAndroid) { if (appPlatform.isAndroid) {
Spacer(Modifier.height(10.dp)) Spacer(Modifier.height(10.dp))
SectionView(stringResource(MR.strings.or_scan_qr_code).uppercase()) { SectionView(stringResource(MR.strings.or_scan_qr_code).uppercase(), headerBottomPadding = 5.dp) {
QRCodeScanner(showQRCodeScanner) { text -> QRCodeScanner(showQRCodeScanner) { text ->
withBGApi { withBGApi {
val res = verify(rhId, text, close) val res = verify(rhId, text, close)
@@ -297,7 +293,7 @@ private fun PasteLinkView(rhId: Long?, pastedLink: MutableState<String>, showQRC
@Composable @Composable
fun LinkTextView(link: String, share: Boolean) { fun LinkTextView(link: String, share: Boolean) {
val clipboard = LocalClipboardManager.current val clipboard = LocalClipboardManager.current
Row(Modifier.fillMaxWidth().heightIn(min = 46.dp).padding(horizontal = DEFAULT_PADDING), verticalAlignment = Alignment.CenterVertically) { Row(Modifier.fillMaxWidth().heightIn(min = DEFAULT_MIN_SECTION_ITEM_HEIGHT).padding(horizontal = DEFAULT_PADDING), verticalAlignment = Alignment.CenterVertically) {
Box(Modifier.weight(1f).clickable { Box(Modifier.weight(1f).clickable {
chatModel.markShowingInvitationUsed() chatModel.markShowingInvitationUsed()
clipboard.shareText(link) clipboard.shareText(link)
@@ -76,56 +76,62 @@ private fun CreateSimpleXAddressLayout(
createAddress: () -> Unit, createAddress: () -> Unit,
nextStep: () -> Unit, nextStep: () -> Unit,
) { ) {
ColumnWithScrollBar( val handler = remember { AppBarHandler() }
Modifier CompositionLocalProvider(
.fillMaxSize() LocalAppBarHandler provides handler
.themedBackground(),
horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
CloseSheetBar(showClose = false, close = {}) ModalView({}, showClose = false) {
AppBarTitle(stringResource(MR.strings.simplex_address)) ColumnWithScrollBar(
Modifier
.fillMaxSize()
.themedBackground(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
AppBarTitle(stringResource(MR.strings.simplex_address))
Spacer(Modifier.weight(1f)) Spacer(Modifier.weight(1f))
if (userAddress != null) { if (userAddress != null) {
SimpleXLinkQRCode(userAddress.connReqContact) SimpleXLinkQRCode(userAddress.connReqContact)
Spacer(Modifier.height(DEFAULT_PADDING_HALF)) Spacer(Modifier.height(DEFAULT_PADDING_HALF))
Row { Row {
ShareAddressButton { share(simplexChatLink(userAddress.connReqContact)) } ShareAddressButton { share(simplexChatLink(userAddress.connReqContact)) }
Spacer(Modifier.width(DEFAULT_PADDING * 2)) Spacer(Modifier.width(DEFAULT_PADDING * 2))
ShareViaEmailButton { sendEmail(userAddress) } ShareViaEmailButton { sendEmail(userAddress) }
} }
Spacer(Modifier.height(DEFAULT_PADDING)) Spacer(Modifier.height(DEFAULT_PADDING))
Spacer(Modifier.weight(1f)) Spacer(Modifier.weight(1f))
Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
OnboardingActionButton( OnboardingActionButton(
modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp), modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp),
labelId = MR.strings.continue_to_next_step, labelId = MR.strings.continue_to_next_step,
onboarding = null, onboarding = null,
onclick = nextStep onclick = nextStep
) )
// Reserve space // Reserve space
TextButtonBelowOnboardingButton("", null) TextButtonBelowOnboardingButton("", null)
} }
} else { } else {
Button(createAddress, Modifier, shape = CircleShape, contentPadding = PaddingValues()) { Button(createAddress, Modifier, shape = CircleShape, contentPadding = PaddingValues()) {
Icon(painterResource(MR.images.ic_mail_filled), null, Modifier.size(100.dp).background(MaterialTheme.colors.primary, CircleShape).padding(25.dp), tint = Color.White) Icon(painterResource(MR.images.ic_mail_filled), null, Modifier.size(100.dp).background(MaterialTheme.colors.primary, CircleShape).padding(25.dp), tint = Color.White)
} }
Spacer(Modifier.height(DEFAULT_PADDING)) Spacer(Modifier.height(DEFAULT_PADDING))
Spacer(Modifier.weight(1f)) Spacer(Modifier.weight(1f))
Text(stringResource(MR.strings.create_simplex_address), style = MaterialTheme.typography.h3, fontWeight = FontWeight.Bold) Text(stringResource(MR.strings.create_simplex_address), style = MaterialTheme.typography.h3, fontWeight = FontWeight.Bold)
TextBelowButton(stringResource(MR.strings.you_can_make_address_visible_via_settings)) TextBelowButton(stringResource(MR.strings.you_can_make_address_visible_via_settings))
Spacer(Modifier.height(DEFAULT_PADDING)) Spacer(Modifier.height(DEFAULT_PADDING))
Spacer(Modifier.weight(1f)) Spacer(Modifier.weight(1f))
Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
OnboardingActionButton( OnboardingActionButton(
modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp), modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp),
labelId = MR.strings.create_address_button, labelId = MR.strings.create_address_button,
onboarding = null, onboarding = null,
onclick = createAddress onclick = createAddress
) )
TextButtonBelowOnboardingButton(stringResource(MR.strings.dont_create_address), nextStep) TextButtonBelowOnboardingButton(stringResource(MR.strings.dont_create_address), nextStep)
}
}
} }
} }
} }
@@ -23,9 +23,10 @@ import dev.icerock.moko.resources.StringResource
@Composable @Composable
fun HowItWorks(user: User?, onboardingStage: SharedPreference<OnboardingStage>? = null) { fun HowItWorks(user: User?, onboardingStage: SharedPreference<OnboardingStage>? = null) {
ColumnWithScrollBar(Modifier ColumnWithScrollBar(
.fillMaxWidth() Modifier
.padding(DEFAULT_PADDING), .fillMaxWidth()
.padding(DEFAULT_PADDING),
) { ) {
AppBarTitle(stringResource(MR.strings.how_simplex_works), withPadding = false) AppBarTitle(stringResource(MR.strings.how_simplex_works), withPadding = false)
ReadableText(MR.strings.many_people_asked_how_can_it_deliver) ReadableText(MR.strings.many_people_asked_how_can_it_deliver)
@@ -25,39 +25,47 @@ import chat.simplex.res.MR
@Composable @Composable
fun SetNotificationsMode(m: ChatModel) { fun SetNotificationsMode(m: ChatModel) {
ColumnWithScrollBar( val handler = remember { AppBarHandler() }
modifier = Modifier CompositionLocalProvider(
.fillMaxSize() LocalAppBarHandler provides handler
.themedBackground()
) { ) {
CloseSheetBar(showClose = false, close = {}) ModalView({}, showClose = false) {
AppBarTitle(stringResource(MR.strings.onboarding_notifications_mode_title)) ColumnWithScrollBar(
val currentMode = rememberSaveable { mutableStateOf(NotificationsMode.default) } modifier = Modifier
Column(Modifier.padding(horizontal = DEFAULT_PADDING * 1f)) { .fillMaxSize()
Text(stringResource(MR.strings.onboarding_notifications_mode_subtitle), Modifier.fillMaxWidth(), textAlign = TextAlign.Center) .themedBackground()
Spacer(Modifier.height(DEFAULT_PADDING * 2f)) ) {
SelectableCard(currentMode, NotificationsMode.OFF, stringResource(MR.strings.onboarding_notifications_mode_off), annotatedStringResource(MR.strings.onboarding_notifications_mode_off_desc)) { Box(Modifier.align(Alignment.CenterHorizontally)) {
currentMode.value = NotificationsMode.OFF AppBarTitle(stringResource(MR.strings.onboarding_notifications_mode_title))
}
SelectableCard(currentMode, NotificationsMode.PERIODIC, stringResource(MR.strings.onboarding_notifications_mode_periodic), annotatedStringResource(MR.strings.onboarding_notifications_mode_periodic_desc)){
currentMode.value = NotificationsMode.PERIODIC
}
SelectableCard(currentMode, NotificationsMode.SERVICE, stringResource(MR.strings.onboarding_notifications_mode_service), annotatedStringResource(MR.strings.onboarding_notifications_mode_service_desc)){
currentMode.value = NotificationsMode.SERVICE
}
}
Spacer(Modifier.fillMaxHeight().weight(1f))
Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
OnboardingActionButton(
modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier,
labelId = MR.strings.use_chat,
onboarding = OnboardingStage.OnboardingComplete,
onclick = {
changeNotificationsMode(currentMode.value, m)
} }
) val currentMode = rememberSaveable { mutableStateOf(NotificationsMode.default) }
// Reserve space Column(Modifier.padding(horizontal = DEFAULT_PADDING * 1f)) {
TextButtonBelowOnboardingButton("", null) Text(stringResource(MR.strings.onboarding_notifications_mode_subtitle), Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
Spacer(Modifier.height(DEFAULT_PADDING * 2f))
SelectableCard(currentMode, NotificationsMode.OFF, stringResource(MR.strings.onboarding_notifications_mode_off), annotatedStringResource(MR.strings.onboarding_notifications_mode_off_desc)) {
currentMode.value = NotificationsMode.OFF
}
SelectableCard(currentMode, NotificationsMode.PERIODIC, stringResource(MR.strings.onboarding_notifications_mode_periodic), annotatedStringResource(MR.strings.onboarding_notifications_mode_periodic_desc)) {
currentMode.value = NotificationsMode.PERIODIC
}
SelectableCard(currentMode, NotificationsMode.SERVICE, stringResource(MR.strings.onboarding_notifications_mode_service), annotatedStringResource(MR.strings.onboarding_notifications_mode_service_desc)) {
currentMode.value = NotificationsMode.SERVICE
}
}
Spacer(Modifier.fillMaxHeight().weight(1f))
Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
OnboardingActionButton(
modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier,
labelId = MR.strings.use_chat,
onboarding = OnboardingStage.OnboardingComplete,
onclick = {
changeNotificationsMode(currentMode.value, m)
}
)
// Reserve space
TextButtonBelowOnboardingButton("", null)
}
}
} }
} }
SetNotificationsModeAdditions() SetNotificationsModeAdditions()
@@ -104,84 +104,90 @@ private fun SetupDatabasePassphraseLayout(
onConfirmEncrypt: () -> Unit, onConfirmEncrypt: () -> Unit,
nextStep: () -> Unit, nextStep: () -> Unit,
) { ) {
ColumnWithScrollBar( val handler = remember { AppBarHandler() }
Modifier.fillMaxSize().themedBackground().padding(bottom = DEFAULT_PADDING * 2), CompositionLocalProvider(
horizontalAlignment = Alignment.CenterHorizontally, LocalAppBarHandler provides handler
) { ) {
CloseSheetBar(showClose = false, close = {}) ModalView({}, showClose = false) {
AppBarTitle(stringResource(MR.strings.setup_database_passphrase)) ColumnWithScrollBar(
Modifier.fillMaxSize().themedBackground().padding(bottom = DEFAULT_PADDING * 2),
horizontalAlignment = Alignment.CenterHorizontally,
) {
AppBarTitle(stringResource(MR.strings.setup_database_passphrase))
Spacer(Modifier.weight(1f)) Spacer(Modifier.weight(1f))
Column(Modifier.width(600.dp)) { Column(Modifier.width(600.dp)) {
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
val focusManager = LocalFocusManager.current val focusManager = LocalFocusManager.current
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
delay(100L) delay(100L)
focusRequester.requestFocus() focusRequester.requestFocus()
} }
PassphraseField( PassphraseField(
newKey, newKey,
generalGetString(MR.strings.new_passphrase), generalGetString(MR.strings.new_passphrase),
modifier = Modifier modifier = Modifier
.padding(horizontal = DEFAULT_PADDING) .padding(horizontal = DEFAULT_PADDING)
.focusRequester(focusRequester) .focusRequester(focusRequester)
.onPreviewKeyEvent { .onPreviewKeyEvent {
if ((it.key == Key.Enter || it.key == Key.NumPadEnter) && it.type == KeyEventType.KeyUp) { if ((it.key == Key.Enter || it.key == Key.NumPadEnter) && it.type == KeyEventType.KeyUp) {
focusManager.moveFocus(FocusDirection.Down) focusManager.moveFocus(FocusDirection.Down)
true true
} else { } else {
false false
}
},
showStrength = true,
isValid = ::validKey,
keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }),
)
val onClickUpdate = {
// Don't do things concurrently. Shouldn't be here concurrently, just in case
if (!progressIndicator.value) {
encryptDatabaseAlert(onConfirmEncrypt)
} }
}, }
showStrength = true, val disabled = currentKey.value == newKey.value ||
isValid = ::validKey, newKey.value != confirmNewKey.value ||
keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), newKey.value.isEmpty() ||
) !validKey(currentKey.value) ||
val onClickUpdate = { !validKey(newKey.value) ||
// Don't do things concurrently. Shouldn't be here concurrently, just in case progressIndicator.value
if (!progressIndicator.value) {
encryptDatabaseAlert(onConfirmEncrypt) PassphraseField(
confirmNewKey,
generalGetString(MR.strings.confirm_new_passphrase),
modifier = Modifier
.padding(horizontal = DEFAULT_PADDING)
.onPreviewKeyEvent {
if (!disabled && (it.key == Key.Enter || it.key == Key.NumPadEnter) && it.type == KeyEventType.KeyUp) {
onClickUpdate()
true
} else {
false
}
},
isValid = { confirmNewKey.value == "" || newKey.value == confirmNewKey.value },
keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done) }),
)
Box(Modifier.align(Alignment.CenterHorizontally).padding(vertical = DEFAULT_PADDING)) {
SetPassphraseButton(disabled, onClickUpdate)
}
Column {
SectionTextFooter(generalGetString(MR.strings.you_have_to_enter_passphrase_every_time))
SectionTextFooter(annotatedStringResource(MR.strings.impossible_to_recover_passphrase))
}
}
Spacer(Modifier.weight(1f))
SkipButton(progressIndicator.value) {
chatModel.desktopOnboardingRandomPassword.value = true
nextStep()
} }
} }
val disabled = currentKey.value == newKey.value ||
newKey.value != confirmNewKey.value ||
newKey.value.isEmpty() ||
!validKey(currentKey.value) ||
!validKey(newKey.value) ||
progressIndicator.value
PassphraseField(
confirmNewKey,
generalGetString(MR.strings.confirm_new_passphrase),
modifier = Modifier
.padding(horizontal = DEFAULT_PADDING)
.onPreviewKeyEvent {
if (!disabled && (it.key == Key.Enter || it.key == Key.NumPadEnter) && it.type == KeyEventType.KeyUp) {
onClickUpdate()
true
} else {
false
}
},
isValid = { confirmNewKey.value == "" || newKey.value == confirmNewKey.value },
keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done) }),
)
Box(Modifier.align(Alignment.CenterHorizontally).padding(vertical = DEFAULT_PADDING)) {
SetPassphraseButton(disabled, onClickUpdate)
}
Column {
SectionTextFooter(generalGetString(MR.strings.you_have_to_enter_passphrase_every_time))
SectionTextFooter(annotatedStringResource(MR.strings.impossible_to_recover_passphrase))
}
}
Spacer(Modifier.weight(1f))
SkipButton(progressIndicator.value) {
chatModel.desktopOnboardingRandomPassword.value = true
nextStep()
} }
} }
} }
@@ -8,9 +8,7 @@ import SectionSpacer
import SectionView import SectionView
import TextIconSpaced import TextIconSpaced
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
@@ -149,7 +147,7 @@ private fun ConnectDesktop(deviceName: String, remoteCtrls: SnapshotStateList<Re
@Composable @Composable
private fun ConnectingDesktop(session: RemoteCtrlSession, rc: RemoteCtrlInfo?) { private fun ConnectingDesktop(session: RemoteCtrlSession, rc: RemoteCtrlInfo?) {
AppBarTitle(stringResource(MR.strings.connecting_to_desktop)) AppBarTitle(stringResource(MR.strings.connecting_to_desktop))
SectionView(stringResource(MR.strings.connecting_to_desktop).uppercase(), padding = PaddingValues(horizontal = DEFAULT_PADDING)) { SectionView(stringResource(MR.strings.connecting_to_desktop).uppercase(), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
CtrlDeviceNameText(session, rc) CtrlDeviceNameText(session, rc)
Spacer(Modifier.height(DEFAULT_PADDING_HALF)) Spacer(Modifier.height(DEFAULT_PADDING_HALF))
CtrlDeviceVersionText(session) CtrlDeviceVersionText(session)
@@ -199,7 +197,7 @@ private fun SearchingDesktop(deviceName: String, remoteCtrls: SnapshotStateList<
} }
} }
SectionDividerSpaced() SectionDividerSpaced()
SectionView(stringResource(MR.strings.found_desktop).uppercase(), padding = PaddingValues(horizontal = DEFAULT_PADDING)) { SectionView(stringResource(MR.strings.found_desktop).uppercase(), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
Text(stringResource(MR.strings.waiting_for_desktop), fontStyle = FontStyle.Italic) Text(stringResource(MR.strings.waiting_for_desktop), fontStyle = FontStyle.Italic)
} }
SectionSpacer() SectionSpacer()
@@ -226,7 +224,7 @@ private fun FoundDesktop(
} }
} }
SectionDividerSpaced() SectionDividerSpaced()
SectionView(stringResource(MR.strings.found_desktop).uppercase(), padding = PaddingValues(horizontal = DEFAULT_PADDING)) { SectionView(stringResource(MR.strings.found_desktop).uppercase(), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
CtrlDeviceNameText(session, rc) CtrlDeviceNameText(session, rc)
CtrlDeviceVersionText(session) CtrlDeviceVersionText(session)
if (!compatible) { if (!compatible) {
@@ -258,7 +256,7 @@ private fun FoundDesktop(
@Composable @Composable
private fun VerifySession(session: RemoteCtrlSession, rc: RemoteCtrlInfo?, sessCode: String, remoteCtrls: SnapshotStateList<RemoteCtrlInfo>) { private fun VerifySession(session: RemoteCtrlSession, rc: RemoteCtrlInfo?, sessCode: String, remoteCtrls: SnapshotStateList<RemoteCtrlInfo>) {
AppBarTitle(stringResource(MR.strings.verify_connection)) AppBarTitle(stringResource(MR.strings.verify_connection))
SectionView(stringResource(MR.strings.connected_to_desktop).uppercase(), padding = PaddingValues(horizontal = DEFAULT_PADDING)) { SectionView(stringResource(MR.strings.connected_to_desktop).uppercase(), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
CtrlDeviceNameText(session, rc) CtrlDeviceNameText(session, rc)
Spacer(Modifier.height(DEFAULT_PADDING_HALF)) Spacer(Modifier.height(DEFAULT_PADDING_HALF))
CtrlDeviceVersionText(session) CtrlDeviceVersionText(session)
@@ -313,7 +311,7 @@ private fun CtrlDeviceVersionText(session: RemoteCtrlSession) {
@Composable @Composable
private fun ActiveSession(session: RemoteCtrlSession, rc: RemoteCtrlInfo, close: () -> Unit) { private fun ActiveSession(session: RemoteCtrlSession, rc: RemoteCtrlInfo, close: () -> Unit) {
AppBarTitle(stringResource(MR.strings.connected_to_desktop)) AppBarTitle(stringResource(MR.strings.connected_to_desktop))
SectionView(stringResource(MR.strings.connected_desktop).uppercase(), padding = PaddingValues(horizontal = DEFAULT_PADDING)) { SectionView(stringResource(MR.strings.connected_desktop).uppercase(), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
Text(rc.deviceViewName) Text(rc.deviceViewName)
Spacer(Modifier.height(DEFAULT_PADDING_HALF)) Spacer(Modifier.height(DEFAULT_PADDING_HALF))
CtrlDeviceVersionText(session) CtrlDeviceVersionText(session)
@@ -446,7 +446,7 @@ fun IntSettingRow(title: String, selection: MutableState<Int>, values: List<Int>
) )
Spacer(Modifier.size(4.dp)) Spacer(Modifier.size(4.dp))
Icon( Icon(
if (!expanded.value) painterResource(MR.images.ic_expand_more) else painterResource(MR.images.ic_expand_less), if (!expanded.value) painterResource(MR.images.ic_arrow_drop_down) else painterResource(MR.images.ic_arrow_drop_up),
generalGetString(MR.strings.invite_to_group_button), generalGetString(MR.strings.invite_to_group_button),
modifier = Modifier.padding(start = 8.dp), modifier = Modifier.padding(start = 8.dp),
tint = MaterialTheme.colors.secondary tint = MaterialTheme.colors.secondary
@@ -506,7 +506,7 @@ fun TimeoutSettingRow(title: String, selection: MutableState<Long>, values: List
) )
Spacer(Modifier.size(4.dp)) Spacer(Modifier.size(4.dp))
Icon( Icon(
if (!expanded.value) painterResource(MR.images.ic_expand_more) else painterResource(MR.images.ic_expand_less), if (!expanded.value) painterResource(MR.images.ic_arrow_drop_down) else painterResource(MR.images.ic_arrow_drop_up),
generalGetString(MR.strings.invite_to_group_button), generalGetString(MR.strings.invite_to_group_button),
modifier = Modifier.padding(start = 8.dp), modifier = Modifier.padding(start = 8.dp),
tint = MaterialTheme.colors.secondary tint = MaterialTheme.colors.secondary
@@ -53,7 +53,7 @@ expect fun AppearanceView(m: ChatModel)
object AppearanceScope { object AppearanceScope {
@Composable @Composable
fun ProfileImageSection() { fun ProfileImageSection() {
SectionView(stringResource(MR.strings.settings_section_title_profile_images).uppercase(), padding = PaddingValues(horizontal = DEFAULT_PADDING)) { SectionView(stringResource(MR.strings.settings_section_title_profile_images).uppercase(), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
val image = remember { chatModel.currentUser }.value?.image val image = remember { chatModel.currentUser }.value?.image
Row(Modifier.padding(top = 10.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) { Row(Modifier.padding(top = 10.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
val size = 60 val size = 60
@@ -86,7 +86,7 @@ object AppearanceScope {
@Composable @Composable
fun FontScaleSection() { fun FontScaleSection() {
val localFontScale = remember { mutableStateOf(appPrefs.fontScale.get()) } val localFontScale = remember { mutableStateOf(appPrefs.fontScale.get()) }
SectionView(stringResource(MR.strings.appearance_font_size).uppercase(), padding = PaddingValues(horizontal = DEFAULT_PADDING)) { SectionView(stringResource(MR.strings.appearance_font_size).uppercase(), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
Row(Modifier.padding(top = 10.dp), verticalAlignment = Alignment.CenterVertically) { Row(Modifier.padding(top = 10.dp), verticalAlignment = Alignment.CenterVertically) {
Box(Modifier.size(60.dp) Box(Modifier.size(60.dp)
.background(MaterialTheme.colors.surface, RoundedCornerShape(percent = 22)) .background(MaterialTheme.colors.surface, RoundedCornerShape(percent = 22))
@@ -549,13 +549,13 @@ object AppearanceScope {
}, },
) )
} }
SectionDividerSpaced(maxTopPadding = true) SectionDividerSpaced()
CustomizeThemeColorsSection(currentTheme) { name -> CustomizeThemeColorsSection(currentTheme) { name ->
editColor(name) editColor(name)
} }
SectionSpacer() SectionDividerSpaced(maxBottomPadding = false)
val currentOverrides = remember(currentTheme) { ThemeManager.defaultActiveTheme(appPrefs.themeOverrides.get()) } val currentOverrides = remember(currentTheme) { ThemeManager.defaultActiveTheme(appPrefs.themeOverrides.get()) }
val canResetColors = currentTheme.base.hasChangedAnyColor(currentOverrides) val canResetColors = currentTheme.base.hasChangedAnyColor(currentOverrides)
@@ -889,7 +889,7 @@ object AppearanceScope {
val hexTrimmed = currentColor.toReadableHex().replaceFirst("#ff", "#") val hexTrimmed = currentColor.toReadableHex().replaceFirst("#ff", "#")
val savedColor by remember(wallpaperType) { mutableStateOf(initialColor) } val savedColor by remember(wallpaperType) { mutableStateOf(initialColor) }
Row(Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).height(46.dp)) { Row(Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).height(DEFAULT_MIN_SECTION_ITEM_HEIGHT)) {
Box(Modifier.weight(1f).fillMaxHeight().background(savedColor).clickable { Box(Modifier.weight(1f).fillMaxHeight().background(savedColor).clickable {
currentColor = savedColor currentColor = savedColor
onColorChange(currentColor) onColorChange(currentColor)

Some files were not shown because too many files have changed in this diff Show More