Compare commits

...

63 Commits

Author SHA1 Message Date
Levitating Pineapple 35bf3eee6b fix dynamic type 2024-08-06 22:22:13 +03:00
Evgeny Poberezkin 23ebef8fb2 Merge branch 'ios-one-hand-ui' into lp/ios-one-hand-ui/search-bar 2024-08-06 19:57:40 +01:00
Levitating Pineapple 624f36d686 show hide search field by observing scroll position 2024-08-06 20:29:13 +03:00
spaced4ndy 16aa3d0f18 Merge branch 'master' into ios-one-hand-ui 2024-08-06 20:42:23 +04:00
spaced4ndy d31dedf132 ui: revert to always show subscriptions indicator, but make it light blue instead of gray for new users (fresh installation with no chats) (#4604) 2024-08-06 20:31:45 +04:00
Levitating Pineapple 559093d0de Merge branch 'master' into ios-one-hand-ui 2024-08-06 19:07:45 +03:00
spaced4ndy 57e2f1a843 clean up 2024-08-06 19:38:38 +04:00
Evgeny 7441ed9892 core: choose random servers for the first user profile, use the same servers for other profiles (#4584)
* core: choose random servers for the first user profile, use the same servers for other profiles

* update ui clients
2024-08-06 16:13:36 +01:00
Stanislav Dmitrenko f6ee6338c4 android: status bar color fix in non-oneHandUI (#4603) 2024-08-06 16:12:30 +01:00
Evgeny df1d47e662 ios: reachable toolbar card on start (#4594)
* ios: reachable toolbar card on start

* rename toggle

* move to one-hand UI default to app group
2024-08-06 15:53:12 +01:00
Stanislav Dmitrenko 5a42c0c1d2 android, desktop: show database import/export errors (#4601)
* android, desktop: show database import/export errors

* no line
2024-08-06 15:37:55 +01:00
Stanislav Dmitrenko 240131a023 android, desktop: move some network settings to advanced network settings (#4583)
* android, desktop: move some network settings to advanced network settings

* strings

* icon

* string

* fix

* change

* icon and footer

* paddings

* revert debug lines
2024-08-06 15:08:47 +01:00
Arturs Krumins e3ed7f68a2 ios: invert swipe actions in oneHandUI mode (#4596)
* add swipe label

* minor

* adjust font

* dynamic type

* limit use to oneHandUI

* icon size

* fix offset

* change font style

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2024-08-06 17:50:33 +04:00
spaced4ndy 205cc835bf Merge branch 'master' into ios-one-hand-ui 2024-08-06 13:09:25 +04:00
Arturs Krumins 8d6c1fc87a fix sheet layout; move user picker (#4592) 2024-08-06 13:08:47 +04:00
spaced4ndy 37e275c3ca core: change simplex contact cards order (#4593) 2024-08-06 12:58:05 +04:00
spaced4ndy f7ee7c5a61 Merge branch 'master' into ios-one-hand-ui 2024-08-06 12:10:47 +04:00
spaced4ndy f893ad15de ui: only show subsription summary indicator after any chat is created; ios: fix servers summary sheet dismissal, screen protection (#4590) 2024-08-06 12:03:25 +04:00
Evgeny a0763b3a43 ios: same size of action buttons in chat info sheets (#4587) 2024-08-06 08:12:42 +01:00
Arturs Krumins 7e5aad371a ios: fix bottom toolbar for one hand ui (#4585)
* fix chat list toolbars forhandUI

* add TODO

* cleanup

* fix safe top safe area

* format

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2024-08-06 10:49:26 +04:00
Arturs Krumins e6ba82c8ff ios: improve performance, when receiving many messages in a single chat (#4586)
* use UnreadCollector for new messages

* less confusing

* collect removed items to reduce unread counts

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-08-05 23:36:33 +01:00
spaced4ndy a8bdf1555f ios: new chat sheet & info views action buttons improvements (#4582) 2024-08-05 21:22:09 +04:00
Evgeny d278b3dbcd core: resume message subscriptions as soon as client is moved to foreground (iOS) or network connection appears (iOS and Android) (#4577)
* core: resume subscriptions as soon as iOS client is moved to foreground

* simplexmq

* simplexmq
2024-08-05 16:42:29 +01:00
spaced4ndy ac4b1e9406 multiplatform: improve deleted chats design (#4581) 2024-08-05 19:39:16 +04:00
Stanislav Dmitrenko b04f159970 android, desktop: blur images of blocked group members (#4579)
* android, desktop: blur images of blocked group members

* simplify

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-08-05 15:01:50 +01:00
Diogo 55331289d3 ios: improve new and existing chat interactions - new chat sheet, one hand ui, info views action buttons; new modes of contact deletion (keep conversation, only delete conversation) (#4427)
* ios: added delete contacts, one hand ui, and contact action buttons

* remove unused, rework info buttons wip

* ios: moved existing buttons to new chat sheet

* ios: add basic list of contacts to new chat sheet

* ios: add deleted chats section to new chat sheet

* group chat info navigation

* fix spacing of group info buttons

* remove comment

* unify spacing logic across info views

* info button alerts wip

* calls alerts wip

* call buttons alerts

* fix call button to correctly update on preference change while in view

* refactor

* fix alert ids

* contact list wip

* more contact list actions

* open chat wip

* fix contact list elements clickability

* ios: search functionality on new chat sheet

* ios: white bg for search box on new chat sheet

* ios: don't show empty list when pasted contact is not known

* ios: add search and nav title to deleted chats

* navigation links wip

* fix refreshable

* ios: empty states for lists

* ios: hide contact cards from chat list

* ios: make search bar icon sizes consistent

* ios: fix deleted conversation dissapearing from chat list on back

* fix pending invitation cleanup in chat sheet

* rename search label from open to search

* make cleanup alert work on sheet and on dismiss

* dismiss all sheets after creation of groups

* fix double toolbar on group invite members

* fix double toolbar on group link invitation screen

* dismiss all on group creation error

* comment

* show alert in dismissAllSheets completion

* fix sheet dismissal on known group

* rework contact list with buttons (fixes dark mode)

* fix dark mode on new chat view

* fix search dark mode

* increase search padding

* improve new chat title and info button placing

* info view background

* improve create group title placement

* refactor

* fix delete dialogue in light mode

* change icon

* archivebox on contact list

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-08-05 15:58:24 +04:00
Stanislav Dmitrenko b2b1519aea android, desktop: multiple messages deletion (#4559)
* android, desktop: multiple messages deletion

* icons

* icon
2024-08-05 10:26:27 +01:00
Evgeny e769abf14a ui: translations (#4575)
* Deleted translation using Weblate (Hindi)

* Deleted translation using Weblate (Malayalam)

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1963 of 1963 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 97.1% (1695 of 1745 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1963 of 1963 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% (1963 of 1963 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 99.6% (1739 of 1745 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1963 of 1963 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1745 of 1745 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 99.0% (1728 of 1745 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 2.5% (44 of 1745 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 91.8% (1803 of 1963 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1963 of 1963 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% (1745 of 1745 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1745 of 1745 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 2.8% (50 of 1745 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 96.5% (1895 of 1963 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 38.9% (680 of 1745 strings)

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

* 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 (Arabic)

Currently translated at 3.4% (61 of 1745 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 3.8% (67 of 1745 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 99.5% (1987 of 1996 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 4.5% (80 of 1745 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1996 of 1996 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% (1996 of 1996 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1996 of 1996 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1996 of 1996 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 100.0% (1996 of 1996 strings)

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

* Translated using Weblate (Portuguese)

Currently translated at 46.8% (935 of 1996 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1996 of 1996 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% (1745 of 1745 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 5.5% (96 of 1745 strings)

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

* Translated using Weblate (Portuguese)

Currently translated at 46.9% (937 of 1996 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1996 of 1996 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1996 of 1996 strings)

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

* Translated using Weblate (German)

Currently translated at 99.9% (1744 of 1745 strings)

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

* Translated using Weblate (Vietnamese)

Currently translated at 28.0% (559 of 1996 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1996 of 1996 strings)

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

* import/export localizations

* update strings

---------

Co-authored-by: M1K4 <oomikaoo@gmail.com>
Co-authored-by: summoner001 <summoner@vivaldi.net>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: Muhammad AbdelMajeed <Muhammad.offic@proton.me>
Co-authored-by: shit face <shitface@users.noreply.hosted.weblate.org>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: הצל השחור וואהה!!!! <ty79k9+5pm8c50cjgpm4@sharklasers.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: Fábio Ferreira <ffcfpten@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Pixelcode <pixelcode@dismail.de>
Co-authored-by: tuananh-ng <158744840+tuananh-ng@users.noreply.github.com>
Co-authored-by: mlanp <github@lang.xyz>
2024-08-05 10:19:32 +01:00
Evgeny 9ca7031f82 Translated using Weblate (German) (#4574)
Currently translated at 100.0% (257 of 257 strings)

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

Co-authored-by: Pixelcode <pixelcode@dismail.de>
2024-08-05 09:36:49 +01:00
Evgeny 7a418918d6 ios: blur images of blocked group members (#4573)
* ios: blur images of blocked group members

* refactor
2024-08-04 22:24:08 +01:00
Stanislav Dmitrenko 8f1302e1c6 android, desktop: chat preview, compose message, new chat button (#4576)
* android, desktop: chat preview, compose message, new chat button

* padding on desktop

* multiplier

* no placeholder in console

* sheet elevation zero when hidden

* divider

* padding

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-08-04 21:57:23 +01:00
Evgeny ee7c5d2aac android: smaller new chat button in 1-hand UI mode, line on the correct side of the bar (#4569)
* android: smaller new chat button in 1-hand UI mode, line on the correct side of the bar (wrong color in 1-hand UI mode)

* remove comment

* desktop: open new chat information buttons on start modal

* remove no longer applicable comment

* desktop, android: move to chat when accepting from chat list and snsReady

* android: keep search visible if keyboard is open on new chat sheet

* android: keep search visibile if keyboard is open on chat list

* android: scroll modal header on new chat sheet

* android: added divider between search and toolbar in one hand ui

* make one hand ui toolbar more extensible by using scafold

* android: remove tiny paddings around one hand ui toolbars

* android: hide toolbar when searching on one hand ui

* avoid passing one hand ui as param everywhere

* make paddings match in new chat sheet action buttons

* flip animation

* refactor and divider fix

* fix padding

* bigger padding

* appPrefs

---------

Co-authored-by: Diogo Cunha <diogofncunha@gmail.com>
Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2024-08-04 21:37:20 +01:00
Evgeny bf697c722a ios: update messages in share extension alert when message sending is slow (#4578) 2024-08-04 20:48:54 +01:00
Evgeny f30dfa0be7 ios: move onion and private routing to advanced network settings, enable private routing by default (#4571)
* ios: move onion and private routing to advanced network settings, enable private routing by default

* update

* update labels

* update localizations
2024-08-04 12:01:09 +01:00
Evgeny Poberezkin 9c73ef9769 android, desktop: search placeholder color and size 2024-08-03 10:21:54 +01:00
Stanislav Dmitrenko e38db7fb44 android: status bar and navigation bar colors from theme (#4568)
* android: status bar and navigation bar colors from theme

* padding

* background on desktop

* useless code

* colors

* removed unused param

* one more place
2024-08-02 23:48:41 +01:00
Evgeny Poberezkin cb76c8079c core: fix archive export when some filename is not compatible with zip (#4561)
* core: fix archive export when some filename is not compatible with zip

* update

* core, ios

* update kotlin apis, ios: add alert to migrate from device
2024-08-02 20:23:54 +01:00
Stanislav Dmitrenko 8fbba16f53 android, desktop: enhancements to chat switching (#4567) 2024-08-02 16:01:21 +01:00
Arturs Krumins 5384e2826d ios: throttle items moving around in chat list too often (#4564)
* ios: add throttling for incoming messages

* cleanup

* throttle, update unread

* dont pop the first chat

* move chats every 3 seconds

* fix

* optimize

* better updateChats

* remove file

* diff

* restore special case for the current chat

* ios: simpler item throttler

* minor

* minor

* refactor

* sort by key

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-08-02 14:08:10 +01:00
Diogo 0975079a93 multiplatform: improve new and existing chat interactions - new chat sheet, one hand ui, info views action buttons; new modes of contact deletion (keep conversation, only delete conversation) (#4435)
* android, desktop: added action buttons and delete to contact card, added toolbar

* android, desktop: added setting for one hand ui

* android: implemented one hand ui for chat list screen (#4448)

* android: implemented one hand ui for chat list screen

* android, desktop: remove extra toolbar

* android: fixed user picker positioning

* android, desktop: new chat sheet (#4479)

* (early draft) android, desktop: new chat sheet

* first draft

* android, desktop: new chat UI improvements

* android, desktop: removed group connections

not needed, missunderstanding in requirements

* android, desktop: deleted contacts and requests

* android, desktop: showing only actionable contacts

* android, desktop: made full new chat sheet scrollable

* android, desktop: handled empty lists

* refactor: fixed fn access scopes

* android, desktop: made sure contacts list refreshes on changes

* android: removed one hand ui for new chat sheet

* android, desktop: removed no longer used code

* android: moved new chat button to toolbar for one hand ui

* removed unused imports

* android, desktop: remove favorite contact set functionality from new chat sheet

* android, desktop: improved chat redirect

* android, desktop: removed padding from contact rows

* android, desktop: improved paddings

* android, desktop: started to use accent color for contact cards and requests

* android, desktop: fixed modals and improved contact stage tracking

* android, desktop: made deleted contacts contactable

* android, desktop: allowed for simplex links to be pasted in new chat sheet

* android, desktop: added interaction for contact cards

* close modal

* android, desktop: started to hide cards from chat list

* android, desktop: translations cleanup

* android, desktop: started to mark deleted chat as non deleted when open from new chat sheet

* android, desktop: fixed link pastes for existing connections

* android, desktop: redirect to groups when group links are pasted in new chat sheet

* move one hand ui toggle

* refactor

* on contact card interaction only close new chat sheet on connect

* android, desktop: removed usages of connection stage enum

* android, desktop: stopped preloading active chats on new chat sheet

* android: fixed invitation cleanup

* desktop: fixed invitation cleanup

* desktop: improved consistency on modals to close

* desktop: added small delay to focus re-position logic to avoid focus change cancelling click events

* android, desktop: made add contact learn more smaller to avoid header becoming bigger than expected

* android, desktop: redirect to chat on accept if send is ready

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>

* android, desktop: hide new chat sheet action buttons when search text is not empty instead of when search is focused (#4529)

* android, desktop: contacts, groups and group member action buttons (#4523)

* android, desktop: made action buttons round

* android, desktop: updated action buttons for contacts

* android, desktop: added action buttons for groups

* android, desktop: removed context menu items

* android, desktop: cleaned up visuals and paddings for contact and group card action buttons

* android, desktop: improved modal close logic

* android, desktop: improved search

* adjust color, fix paddings

* android, desktop: avoided async calls to open chats and simplified search as result

* android, desktop: moved mute button to the end on group view to match chat view

* android, desktop: made filling of icons consistent

* android, desktop: fixed contacts sheet close and dismiss actions on contact connection

* order

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>

* android, desktop: streamlined delete actions based on contact type (#4538)

* android, desktop: streamlined delete actions based on contact type

* removed unused translations

* refactor, adjust texts

* move toggle closer to buttons

* fix text

* fix accept request

* android, desktop: made sure deleted contacts update on deletes

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>

* restore deleted file

* desktop: stop closing modal on message search

* android, desktop: remove scroll preservation on new chat sheet unmount

* android, desktop: add functionality to search inside deleted conversation on main new chat sheet screen

* android, desktop: fixed attachment bug when clicking contact with keyboard open inside new chat sheet

* desktop, android: set incognito contacts color to Indigo in contact list

* remove unused code

* remove openedFromChatView

* android, desktop: change icon for contact requests and added icon for contact cards

* refactor

* fix paddings

* fix padding

* refactor

* android, desktop: fix attachment issue for deleted contacts

* remove unused

* android: invert new chat sheet on one hand ui

* info buttons alerts

* info buttons paddings

* android: one hand ui for new chat sheet and deleted chats

* fix build after latest master changes on chat model and mutations in chat

* android,desktop: add menu items back

* add scrollbars to new chat sheet

* desktop: inactivate and rephrase scan since it is not supported

* android: one hand ui for forward chat list

* android, desktop: fix for no chats in one hand ui

* desktop: use left side of screen for new chat actions

* desktop: close end modal when new chat sheet is clicked

* android: fix no filtered contacts on delete contacts view

* fix scrollbar not showing

* android: few adjustmnets in one hand ui

* change icon

* increase icon size

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2024-08-02 14:09:57 +04:00
Stanislav Dmitrenko 74e2b7582e scripts (linux): fix building appimage with missing runtime file (#4565) 2024-08-02 09:34:04 +01:00
spaced4ndy 413a8f6b76 core: correctly update chat item deletable state on deletions (#4558) 2024-08-01 18:56:41 +04:00
Evgeny Poberezkin 619cff91b2 ios: export localized strings for translation 2024-08-01 14:58:39 +01:00
Evgeny Poberezkin 2ee16f9398 website: translations (#4556)
* Translated using Weblate (Czech)

Currently translated at 100.0% (257 of 257 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (257 of 257 strings)

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

---------

Co-authored-by: zenobit <zen@osowoso.xyz>
2024-08-01 14:14:50 +01:00
Arturs Krumins bfab2d9fb6 ios: fix alert reseting list position (#4554)
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-08-01 10:21:31 +01:00
Evgeny Poberezkin 229ea80499 ios: fix sharing links with previews (#4552)
* desktop: fix app without user crashing when trying to get subscription data (#4551)

* use newline

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2024-08-01 09:51:22 +01:00
spaced4ndy 95de471754 desktop: fix app without user crashing when trying to get subscription data (#4551) 2024-08-01 08:05:35 +01:00
Arturs Krumins cbc86cd81e ios: disable chats in share-sheet based on preferences (#4549)
* claenup

* cleanup

* remove groupFeatureEnabled from Chat

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-08-01 07:48:17 +01:00
Stanislav Dmitrenko 19cab39ee8 android, desktop: refactoring to use mutex when updating chats (#4541)
* moving to mutablestate + snapshotstatelist from snapshotstatelist

* android, desktop: refactoring to use mutex when updating chats

* wrapped into class instead of object

* fix

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-07-31 18:43:31 +01:00
Arturs Krumins 6fa3695ad6 ios: add database password prompt to share-sheet, fix sharing screenshot (#4546)
* ios: add password prompt to share-sheet

* fix sharing screenshots

* s/Password/Passphrase/

* alert title

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-07-31 18:07:11 +01:00
Stanislav Dmitrenko 6e6afdbd25 ios: multiple messages deletion (#4535)
* ios: multiple messages deletion

* changes

* layout

* fix

* changes in design and UX

* fixes

* padding

* paddings

* refactor

* changes

* gray circles, separator, optimize

* titles

* disable moderation for own single message

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-07-31 15:00:14 +01:00
Evgeny Poberezkin 93e88c3953 ios: optionally lock share extension when SimpleX Lock is enabled (default is to lock), allow link previews if enabled in the app, use the same shape of avatars (#4547) 2024-07-31 13:54:47 +01:00
Evgeny Poberezkin 676b533393 ui: translations (#4540)
* Translated using Weblate (Hungarian)

Currently translated at 99.7% (1948 of 1953 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 99.7% (1948 of 1953 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% (1953 of 1953 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% (1687 of 1687 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1953 of 1953 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% (1687 of 1687 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1953 of 1953 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1953 of 1953 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% (1687 of 1687 strings)

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

* Translated using Weblate (Indonesian)

Currently translated at 3.8% (75 of 1953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 99.6% (1947 of 1953 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 100.0% (1953 of 1953 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1953 of 1953 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1687 of 1687 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1953 of 1953 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 94.2% (1590 of 1687 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 83.5% (1631 of 1953 strings)

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

* Translated using Weblate (Indonesian)

Currently translated at 4.4% (86 of 1953 strings)

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

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

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

* Translated using Weblate (French)

Currently translated at 99.7% (1948 of 1953 strings)

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

* Translated using Weblate (French)

Currently translated at 99.5% (1679 of 1687 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 96.0% (1875 of 1953 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 94.1% (1589 of 1687 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1953 of 1953 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% (1687 of 1687 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1953 of 1953 strings)

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

* Translated using Weblate (Vietnamese)

Currently translated at 25.4% (498 of 1953 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1687 of 1687 strings)

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

* Translated using Weblate (French)

Currently translated at 99.5% (1680 of 1687 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 99.5% (1945 of 1953 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 98.3% (1659 of 1687 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 98.3% (1659 of 1687 strings)

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

* Translated using Weblate (Indonesian)

Currently translated at 4.6% (90 of 1953 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 99.5% (1944 of 1952 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 99.8% (1685 of 1687 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1958 of 1958 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1952 of 1952 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% (1687 of 1687 strings)

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

* Translated using Weblate (Vietnamese)

Currently translated at 26.0% (509 of 1952 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1958 of 1958 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1958 of 1958 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1952 of 1952 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% (1687 of 1687 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 100.0% (1958 of 1958 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1952 of 1952 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% (1687 of 1687 strings)

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

* Translated using Weblate (Vietnamese)

Currently translated at 26.6% (521 of 1952 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% (1952 of 1952 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% (1687 of 1687 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1952 of 1952 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1687 of 1687 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1952 of 1952 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% (1687 of 1687 strings)

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

* Translated using Weblate (Indonesian)

Currently translated at 5.2% (103 of 1952 strings)

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

* Translated using Weblate (Polish)

Currently translated at 96.6% (1893 of 1958 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1952 of 1952 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% (1687 of 1687 strings)

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

* Translated using Weblate (Vietnamese)

Currently translated at 27.8% (543 of 1952 strings)

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

* Translated using Weblate (Indonesian)

Currently translated at 5.2% (103 of 1952 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1963 of 1963 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 100.0% (1963 of 1963 strings)

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

* Translated using Weblate (Polish)

Currently translated at 99.9% (1957 of 1958 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% (1687 of 1687 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% (104 of 1952 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1963 of 1963 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1963 of 1963 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 100.0% (1963 of 1963 strings)

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

* update multiplatform strings

* ios: import localizations

* export localizations, add localized strings to extensions

---------

Co-authored-by: summoner001 <summoner@vivaldi.net>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: billy appetie <billy_appetie@users.noreply.hosted.weblate.org>
Co-authored-by: M1K4 <oomikaoo@gmail.com>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: Diego Luiz <diegoluizfps@gmail.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Mqlx <vncrlecgubhp@rel.eliott.cc>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: khalidbelk <khalid.belkassmi-el-hafi@epitech.eu>
Co-authored-by: tuananh-ng <158744840+tuananh-ng@users.noreply.github.com>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
2024-07-31 08:54:03 +01:00
Stanislav Dmitrenko e1fb0ac0b1 ui: delete multiple messages (#4532)
* ui: delete multiple messages

* ios

* simplify, rename

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-07-30 23:56:56 +01:00
Evgeny Poberezkin 8bda64a5c1 core: api to delete multiple messages (#4452)
* core: api proposal (not implemeted) to delete multiple messages

* core: batch delete multiple messages; allow to moderate self (#4513)

* allow to moderate self, remove saving item-message record on mark delete

* direct batched

* local batched

* group batched

* moderate batched

* refactor

* fix

* fix test

* remove unused event

* direct message batching wip

* direct test

* more tests

* trunk

* batch compressed

* remove unused function

* new agent api

* sendGroupMessages

* forward batched

* refactor

* remove comment

* rename, comment

* refactor

* many chat batches test (doesn't pass)

* refactor

* comment

* rename

* comment

* linearize

* fix

* fix

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* core: check item deletable with margin (#4533)

* simplexmq

* remove L.singleton (ghc 8.10.7)

* test delay

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2024-07-30 22:59:47 +01:00
spaced4ndy ab058d7222 core: add SimpleX-Status preset contact card; create contact cards for each new user (#4544) 2024-07-30 20:00:51 +04:00
Evgeny Poberezkin 4f9c53f561 6.0-beta.2: ios 229, android 227, desktop 58 2024-07-30 08:14:22 +01:00
Stanislav Dmitrenko 5257c6f9ca android, desktop: fix crash on fast clicking every chat in list (#4536)
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-07-30 07:33:15 +01:00
Evgeny Poberezkin 6729b1fb4f ios: update core library 2024-07-29 23:00:54 +01:00
Evgeny Poberezkin ac5351a752 core: 6.0.0.3 (simplexmq 6.0.0.5) 2024-07-29 22:14:23 +01:00
Evgeny Poberezkin 2ff4619ca4 ios: improve chat list layout (#4537) 2024-07-29 22:11:02 +01:00
spaced4ndy 493ad14b39 core: make user db actions high priority, faster chat start with async db operations (#4531)
* core: move db actions out of synchronous execution on chat start

* revert some

* multiplatform: load chat data before starting chat

* use priority database access

* simplexmq

* fix race in the tests

* check chat is running

* core: allow getting call invitations and notificationn token when chat is stopped

* ios: load chats and refresh call invitations before chat is started

* simplexmq

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-07-29 21:52:45 +01:00
Arturs Krumins 7f08f87ee4 ios: improve conversation scrolling (fixes hangs when messages are updated). (#4534)
* ios: fix hang while updating chat item state

* throttle item update

* fix

* remove buttons, switch back to Debug

* remove items getter/setter from ChatModel

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-07-29 21:17:14 +01:00
337 changed files with 20327 additions and 14100 deletions
+123 -75
View File
@@ -43,6 +43,21 @@ private func addTermItem(_ items: inout [TerminalItem], _ item: TerminalItem) {
items.append(item)
}
class ItemsModel: ObservableObject {
static let shared = ItemsModel()
private let publisher = ObservableObjectPublisher()
private var bag = Set<AnyCancellable>()
var reversedChatItems: [ChatItem] = [] {
willSet { publisher.send() }
}
init() {
publisher
.throttle(for: 0.25, scheduler: DispatchQueue.main, latest: true)
.sink { self.objectWillChange.send() }
.store(in: &bag)
}
}
final class ChatModel: ObservableObject {
@Published var onboardingStage: OnboardingStage?
@Published var setDeliveryReceipts = false
@@ -69,7 +84,6 @@ final class ChatModel: ObservableObject {
@Published var networkStatuses: Dictionary<String, NetworkStatus> = [:]
// current chat
@Published var chatId: String?
@Published var reversedChatItems: [ChatItem] = []
var chatItemStatuses: Dictionary<Int64, CIStatus> = [:]
@Published var chatToTop: String?
@Published var groupMembers: [GMember] = []
@@ -117,6 +131,8 @@ final class ChatModel: ObservableObject {
static let shared = ChatModel()
let im = ItemsModel.shared
static var ok: Bool { ChatModel.shared.chatDbStatus == .ok }
let ntfEnableLocal = true
@@ -317,18 +333,9 @@ final class ChatModel: ObservableObject {
[cItem]
}
if case .rcvNew = cItem.meta.itemStatus {
chats[i].chatStats.unreadCount = chats[i].chatStats.unreadCount + 1
increaseUnreadCounter(user: currentUser!)
}
if i > 0 {
if chatId == nil {
withAnimation { popChat_(i) }
} else if chatId == cInfo.id {
chatToTop = cInfo.id
} else {
popChat_(i)
}
unreadCollector.changeUnreadCounter(cInfo.id, by: 1)
}
popChatCollector.addChat(cInfo.id)
} else {
addChat(Chat(chatInfo: cInfo, chatItems: [cItem]))
}
@@ -343,7 +350,7 @@ final class ChatModel: ObservableObject {
var res: Bool
if let chat = getChat(cInfo.id) {
if let pItem = chat.chatItems.last {
if pItem.id == cItem.id || (chatId == cInfo.id && reversedChatItems.first(where: { $0.id == cItem.id }) == nil) {
if pItem.id == cItem.id || (chatId == cInfo.id && im.reversedChatItems.first(where: { $0.id == cItem.id }) == nil) {
chat.chatItems = [cItem]
}
} else {
@@ -373,7 +380,7 @@ final class ChatModel: ObservableObject {
if let status = chatItemStatuses.removeValue(forKey: ci.id), case .sndNew = ci.meta.itemStatus {
ci.meta.itemStatus = status
}
reversedChatItems.insert(ci, at: hasLiveDummy ? 1 : 0)
im.reversedChatItems.insert(ci, at: hasLiveDummy ? 1 : 0)
}
return true
}
@@ -397,17 +404,17 @@ final class ChatModel: ObservableObject {
}
private func _updateChatItem(at i: Int, with cItem: ChatItem) {
reversedChatItems[i] = cItem
reversedChatItems[i].viewTimestamp = .now
im.reversedChatItems[i] = cItem
im.reversedChatItems[i].viewTimestamp = .now
}
func getChatItemIndex(_ cItem: ChatItem) -> Int? {
reversedChatItems.firstIndex(where: { $0.id == cItem.id })
im.reversedChatItems.firstIndex(where: { $0.id == cItem.id })
}
func removeChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) {
if cItem.isRcvNew, let chatIndex = getChatIndex(cInfo.id) {
decreaseUnreadCounter(chatIndex)
if cItem.isRcvNew {
unreadCollector.changeUnreadCounter(cInfo.id, by: -1)
}
// update previews
if let chat = getChat(cInfo.id) {
@@ -419,7 +426,7 @@ final class ChatModel: ObservableObject {
if chatId == cInfo.id {
if let i = getChatItemIndex(cItem) {
_ = withAnimation {
self.reversedChatItems.remove(at: i)
im.reversedChatItems.remove(at: i)
}
}
}
@@ -427,16 +434,16 @@ final class ChatModel: ObservableObject {
}
func nextChatItemData<T>(_ chatItemId: Int64, previous: Bool, map: @escaping (ChatItem) -> T?) -> T? {
guard var i = reversedChatItems.firstIndex(where: { $0.id == chatItemId }) else { return nil }
guard var i = im.reversedChatItems.firstIndex(where: { $0.id == chatItemId }) else { return nil }
if previous {
while i < reversedChatItems.count - 1 {
while i < im.reversedChatItems.count - 1 {
i += 1
if let res = map(reversedChatItems[i]) { return res }
if let res = map(im.reversedChatItems[i]) { return res }
}
} else {
while i > 0 {
i -= 1
if let res = map(reversedChatItems[i]) { return res }
if let res = map(im.reversedChatItems[i]) { return res }
}
}
return nil
@@ -467,7 +474,7 @@ final class ChatModel: ObservableObject {
func addLiveDummy(_ chatInfo: ChatInfo) -> ChatItem {
let cItem = ChatItem.liveDummy(chatInfo.chatType)
withAnimation {
reversedChatItems.insert(cItem, at: 0)
im.reversedChatItems.insert(cItem, at: 0)
}
return cItem
}
@@ -475,15 +482,15 @@ final class ChatModel: ObservableObject {
func removeLiveDummy(animated: Bool = true) {
if hasLiveDummy {
if animated {
withAnimation { _ = reversedChatItems.removeFirst() }
withAnimation { _ = im.reversedChatItems.removeFirst() }
} else {
_ = reversedChatItems.removeFirst()
_ = im.reversedChatItems.removeFirst()
}
}
}
private var hasLiveDummy: Bool {
reversedChatItems.first?.isLiveDummy == true
im.reversedChatItems.first?.isLiveDummy == true
}
func markChatItemsRead(_ cInfo: ChatInfo) {
@@ -500,7 +507,7 @@ final class ChatModel: ObservableObject {
private func markCurrentChatRead(fromIndex i: Int = 0) {
var j = i
while j < reversedChatItems.count {
while j < im.reversedChatItems.count {
markChatItemRead_(j)
j += 1
}
@@ -514,7 +521,7 @@ final class ChatModel: ObservableObject {
var unreadBelow = 0
var j = i - 1
while j >= 0 {
if case .rcvNew = self.reversedChatItems[j].meta.itemStatus {
if case .rcvNew = self.im.reversedChatItems[j].meta.itemStatus {
unreadBelow += 1
}
j -= 1
@@ -549,56 +556,114 @@ final class ChatModel: ObservableObject {
// clear current chat
if chatId == cInfo.id {
chatItemStatuses = [:]
reversedChatItems = []
im.reversedChatItems = []
}
}
func markChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) async {
if chatId == cInfo.id,
let itemIndex = getChatItemIndex(cItem),
let chatIndex = getChatIndex(cInfo.id),
reversedChatItems[itemIndex].isRcvNew {
im.reversedChatItems[itemIndex].isRcvNew {
await MainActor.run {
withTransaction(Transaction()) {
// update current chat
markChatItemRead_(itemIndex)
// update preview
decreaseUnreadCounter(chatIndex)
unreadCollector.changeUnreadCounter(cInfo.id, by: -1)
}
}
}
}
private func markChatItemRead_(_ i: Int) {
let meta = reversedChatItems[i].meta
if case .rcvNew = meta.itemStatus {
reversedChatItems[i].meta.itemStatus = .rcvRead
reversedChatItems[i].viewTimestamp = .now
if meta.itemLive != true, let ttl = meta.itemTimed?.ttl {
reversedChatItems[i].meta.itemTimed?.deleteAt = .now + TimeInterval(ttl)
private let unreadCollector = UnreadCollector()
class UnreadCollector {
private let subject = PassthroughSubject<Void, Never>()
private var bag = Set<AnyCancellable>()
private var unreadCounts: [ChatId: Int] = [:]
init() {
subject
.debounce(for: 1, scheduler: DispatchQueue.main)
.sink {
let m = ChatModel.shared
for (chatId, count) in self.unreadCounts {
if let i = m.getChatIndex(chatId) {
m.changeUnreadCounter(i, by: count)
}
}
self.unreadCounts = [:]
}
.store(in: &bag)
}
func changeUnreadCounter(_ chatId: ChatId, by count: Int) {
DispatchQueue.main.async {
self.unreadCounts[chatId] = (self.unreadCounts[chatId] ?? 0) + count
}
subject.send()
}
}
let popChatCollector = PopChatCollector()
class PopChatCollector {
private let subject = PassthroughSubject<Void, Never>()
private var bag = Set<AnyCancellable>()
init() {
subject
.throttle(for: 2, scheduler: DispatchQueue.main, latest: true)
.sink {
let m = ChatModel.shared
if m.chatId == nil {
withAnimation {
m.chats = m.chats.sorted(using: KeyPathComparator(\.popTs, order: .reverse))
}
} else {
m.chats = m.chats.sorted(using: KeyPathComparator(\.popTs, order: .reverse))
}
}
.store(in: &bag)
}
func addChat(_ chatId: ChatId) {
if let index = ChatModel.shared.getChatIndex(chatId) {
ChatModel.shared.chats[index].popTs = CFAbsoluteTimeGetCurrent()
subject.send()
}
}
}
func decreaseUnreadCounter(_ chatIndex: Int) {
chats[chatIndex].chatStats.unreadCount = chats[chatIndex].chatStats.unreadCount - 1
decreaseUnreadCounter(user: currentUser!)
private func markChatItemRead_(_ i: Int) {
let meta = im.reversedChatItems[i].meta
if case .rcvNew = meta.itemStatus {
im.reversedChatItems[i].meta.itemStatus = .rcvRead
im.reversedChatItems[i].viewTimestamp = .now
if meta.itemLive != true, let ttl = meta.itemTimed?.ttl {
im.reversedChatItems[i].meta.itemTimed?.deleteAt = .now + TimeInterval(ttl)
}
}
}
func changeUnreadCounter(_ chatIndex: Int, by count: Int) {
chats[chatIndex].chatStats.unreadCount = chats[chatIndex].chatStats.unreadCount + count
changeUnreadCounter(user: currentUser!, by: count)
}
func increaseUnreadCounter(user: any UserLike) {
changeUnreadCounter(user: user, by: 1)
NtfManager.shared.incNtfBadgeCount()
}
func decreaseUnreadCounter(user: any UserLike, by: Int = 1) {
changeUnreadCounter(user: user, by: -by)
NtfManager.shared.decNtfBadgeCount(by: by)
}
private func changeUnreadCounter(user: any UserLike, by: Int) {
if let i = users.firstIndex(where: { $0.user.userId == user.userId }) {
users[i].unreadCount += by
}
NtfManager.shared.changeNtfBadgeCount(by: by)
}
func totalUnreadCountForAllUsers() -> Int {
@@ -612,8 +677,8 @@ final class ChatModel: ObservableObject {
var ns: [String] = []
if let ciCategory = chatItem.mergeCategory,
var i = getChatItemIndex(chatItem) {
while i < reversedChatItems.count {
let ci = reversedChatItems[i]
while i < im.reversedChatItems.count {
let ci = im.reversedChatItems[i]
if ci.mergeCategory != ciCategory { break }
if let m = ci.memberConnected {
ns.append(m.displayName)
@@ -628,7 +693,7 @@ final class ChatModel: ObservableObject {
// returns the index of the passed item and the next item (it has smaller index)
func getNextChatItem(_ ci: ChatItem) -> (Int?, ChatItem?) {
if let i = getChatItemIndex(ci) {
(i, i > 0 ? reversedChatItems[i - 1] : nil)
(i, i > 0 ? im.reversedChatItems[i - 1] : nil)
} else {
(nil, nil)
}
@@ -638,10 +703,10 @@ final class ChatModel: ObservableObject {
// and the previous visible item with another merge category
func getPrevShownChatItem(_ ciIndex: Int?, _ ciCategory: CIMergeCategory?) -> (Int?, ChatItem?) {
guard var i = ciIndex else { return (nil, nil) }
let fst = reversedChatItems.count - 1
let fst = im.reversedChatItems.count - 1
while i < fst {
i = i + 1
let ci = reversedChatItems[i]
let ci = im.reversedChatItems[i]
if ciCategory == nil || ciCategory != ci.mergeCategory {
return (i - 1, ci)
}
@@ -654,7 +719,7 @@ final class ChatModel: ObservableObject {
var prevMember: GroupMember? = nil
var memberIds: Set<Int64> = []
for i in range {
if case let .groupRcv(m) = reversedChatItems[i].chatDir {
if case let .groupRcv(m) = im.reversedChatItems[i].chatDir {
if prevMember == nil && m.groupMemberId != member.groupMemberId { prevMember = m }
memberIds.insert(m.groupMemberId)
}
@@ -729,9 +794,9 @@ final class ChatModel: ObservableObject {
var i = 0
var totalBelow = 0
var unreadBelow = 0
while i < reversedChatItems.count - 1 && !itemsInView.contains(reversedChatItems[i].viewId) {
while i < im.reversedChatItems.count - 1 && !itemsInView.contains(im.reversedChatItems[i].viewId) {
totalBelow += 1
if reversedChatItems[i].isRcvNew {
if im.reversedChatItems[i].isRcvNew {
unreadBelow += 1
}
i += 1
@@ -740,12 +805,12 @@ final class ChatModel: ObservableObject {
}
func topItemInView(itemsInView: Set<String>) -> ChatItem? {
let maxIx = reversedChatItems.count - 1
let maxIx = im.reversedChatItems.count - 1
var i = 0
let inView = { itemsInView.contains(self.reversedChatItems[$0].viewId) }
let inView = { itemsInView.contains(self.im.reversedChatItems[$0].viewId) }
while i < maxIx && !inView(i) { i += 1 }
while i < maxIx && inView(i) { i += 1 }
return reversedChatItems[min(i - 1, maxIx)]
return im.reversedChatItems[min(i - 1, maxIx)]
}
func setContactNetworkStatus(_ contact: Contact, _ status: NetworkStatus) {
@@ -783,6 +848,7 @@ final class Chat: ObservableObject, Identifiable, ChatLike {
@Published var chatItems: [ChatItem]
@Published var chatStats: ChatStats
var created = Date.now
var popTs: CFAbsoluteTime?
init(_ cData: ChatData) {
self.chatInfo = cData.chatInfo
@@ -829,24 +895,6 @@ final class Chat: ObservableObject, Identifiable, ChatLike {
var viewId: String { get { "\(chatInfo.id) \(created.timeIntervalSince1970)" } }
func groupFeatureEnabled(_ feature: GroupFeature) -> Bool {
if case let .group(groupInfo) = self.chatInfo {
let p = groupInfo.fullGroupPreferences
return switch feature {
case .timedMessages: p.timedMessages.on
case .directMessages: p.directMessages.on(for: groupInfo.membership)
case .fullDelete: p.fullDelete.on
case .reactions: p.reactions.on
case .voice: p.voice.on(for: groupInfo.membership)
case .files: p.files.on(for: groupInfo.membership)
case .simplexLinks: p.simplexLinks.on(for: groupInfo.membership)
case .history: p.history.on
}
} else {
return true
}
}
public static var sampleData: Chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: [])
}
+2 -6
View File
@@ -238,12 +238,8 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
ntfBadgeCountGroupDefault.set(count)
}
func decNtfBadgeCount(by count: Int = 1) {
setNtfBadgeCount(max(0, UIApplication.shared.applicationIconBadgeNumber - count))
}
func incNtfBadgeCount(by count: Int = 1) {
setNtfBadgeCount(UIApplication.shared.applicationIconBadgeNumber + count)
func changeNtfBadgeCount(by count: Int = 1) {
setNtfBadgeCount(max(0, UIApplication.shared.applicationIconBadgeNumber + count))
}
private func addNotification(_ content: UNMutableNotificationContent) {
+113 -29
View File
@@ -137,8 +137,8 @@ func apiGetActiveUser(ctrl: chat_ctrl? = nil) throws -> User? {
}
}
func apiCreateActiveUser(_ p: Profile?, sameServers: Bool = false, pastTimestamp: Bool = false, ctrl: chat_ctrl? = nil) throws -> User {
let r = chatSendCmdSync(.createActiveUser(profile: p, sameServers: sameServers, pastTimestamp: pastTimestamp), ctrl)
func apiCreateActiveUser(_ p: Profile?, pastTimestamp: Bool = false, ctrl: chat_ctrl? = nil) throws -> User {
let r = chatSendCmdSync(.createActiveUser(profile: p, pastTimestamp: pastTimestamp), ctrl)
if case let .activeUser(user) = r { return user }
throw r
}
@@ -225,6 +225,15 @@ func apiStartChat(ctrl: chat_ctrl? = nil) throws -> Bool {
}
}
func apiCheckChatRunning() throws -> Bool {
let r = chatSendCmdSync(.checkChatRunning)
switch r {
case .chatRunning: return true
case .chatStopped: return false
default: throw r
}
}
func apiStopChat() async throws {
let r = await chatSendCmd(.apiStopChat)
switch r {
@@ -270,8 +279,10 @@ func apiGetAppSettings(settings: AppSettings) throws -> AppSettings {
throw r
}
func apiExportArchive(config: ArchiveConfig) async throws {
try await sendCommandOkResp(.apiExportArchive(config: config))
func apiExportArchive(config: ArchiveConfig) async throws -> [ArchiveError] {
let r = await chatSendCmd(.apiExportArchive(config: config))
if case let .archiveExported(archiveErrors) = r { return archiveErrors }
throw r
}
func apiImportArchive(config: ArchiveConfig) async throws -> [ArchiveError] {
@@ -325,11 +336,18 @@ func loadChat(chat: Chat, search: String = "") {
do {
let cInfo = chat.chatInfo
let m = ChatModel.shared
let im = ItemsModel.shared
m.chatItemStatuses = [:]
m.reversedChatItems = []
im.reversedChatItems = []
let chat = try apiGetChat(type: cInfo.chatType, id: cInfo.apiId, search: search)
m.updateChatInfo(chat.chatInfo)
m.reversedChatItems = chat.chatItems.reversed()
if case let .direct(contact) = chat.chatInfo, !cInfo.chatDeleted, chat.chatInfo.chatDeleted {
var updatedContact = contact
updatedContact.chatDeleted = false
m.updateContact(updatedContact)
} else {
m.updateChatInfo(chat.chatInfo)
}
im.reversedChatItems = chat.chatItems.reversed()
} catch let error {
logger.error("loadChat error: \(responseError(error))")
}
@@ -421,15 +439,15 @@ func apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, re
throw r
}
func apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteMode) async throws -> (ChatItem, ChatItem?) {
let r = await chatSendCmd(.apiDeleteChatItem(type: type, id: id, itemId: itemId, mode: mode), bgDelay: msgDelay)
if case let .chatItemDeleted(_, deletedChatItem, toChatItem, _) = r { return (deletedChatItem.chatItem, toChatItem?.chatItem) }
func apiDeleteChatItems(type: ChatType, id: Int64, itemIds: [Int64], mode: CIDeleteMode) async throws -> [ChatItemDeletion] {
let r = await chatSendCmd(.apiDeleteChatItem(type: type, id: id, itemIds: itemIds, mode: mode), bgDelay: msgDelay)
if case let .chatItemsDeleted(_, items, _) = r { return items }
throw r
}
func apiDeleteMemberChatItem(groupId: Int64, groupMemberId: Int64, itemId: Int64) async throws -> (ChatItem, ChatItem?) {
let r = await chatSendCmd(.apiDeleteMemberChatItem(groupId: groupId, groupMemberId: groupMemberId, itemId: itemId), bgDelay: msgDelay)
if case let .chatItemDeleted(_, deletedChatItem, toChatItem, _) = r { return (deletedChatItem.chatItem, toChatItem?.chatItem) }
func apiDeleteMemberChatItems(groupId: Int64, itemIds: [Int64]) async throws -> [ChatItemDeletion] {
let r = await chatSendCmd(.apiDeleteMemberChatItem(groupId: groupId, itemIds: itemIds), bgDelay: msgDelay)
if case let .chatItemsDeleted(_, items, _) = r { return items }
throw r
}
@@ -749,22 +767,38 @@ func apiConnectContactViaAddress(incognito: Bool, contactId: Int64) async -> (Co
return (nil, alert)
}
func apiDeleteChat(type: ChatType, id: Int64, notify: Bool? = nil) async throws {
func apiDeleteChat(type: ChatType, id: Int64, chatDeleteMode: ChatDeleteMode = .full(notify: true)) async throws {
let chatId = type.rawValue + id.description
DispatchQueue.main.async { ChatModel.shared.deletedChats.insert(chatId) }
defer { DispatchQueue.main.async { ChatModel.shared.deletedChats.remove(chatId) } }
let r = await chatSendCmd(.apiDeleteChat(type: type, id: id, notify: notify), bgTask: false)
let r = await chatSendCmd(.apiDeleteChat(type: type, id: id, chatDeleteMode: chatDeleteMode), bgTask: false)
if case .direct = type, case .contactDeleted = r { return }
if case .contactConnection = type, case .contactConnectionDeleted = r { return }
if case .group = type, case .groupDeletedUser = r { return }
throw r
}
func deleteChat(_ chat: Chat, notify: Bool? = nil) async {
func apiDeleteContact(id: Int64, chatDeleteMode: ChatDeleteMode = .full(notify: true)) async throws -> Contact {
let type: ChatType = .direct
let chatId = type.rawValue + id.description
if case .full = chatDeleteMode {
DispatchQueue.main.async { ChatModel.shared.deletedChats.insert(chatId) }
}
defer {
if case .full = chatDeleteMode {
DispatchQueue.main.async { ChatModel.shared.deletedChats.remove(chatId) }
}
}
let r = await chatSendCmd(.apiDeleteChat(type: type, id: id, chatDeleteMode: chatDeleteMode), bgTask: false)
if case let .contactDeleted(_, contact) = r { return contact }
throw r
}
func deleteChat(_ chat: Chat, chatDeleteMode: ChatDeleteMode = .full(notify: true)) async {
do {
let cInfo = chat.chatInfo
try await apiDeleteChat(type: cInfo.chatType, id: cInfo.apiId, notify: notify)
DispatchQueue.main.async { ChatModel.shared.removeChat(cInfo.id) }
try await apiDeleteChat(type: cInfo.chatType, id: cInfo.apiId, chatDeleteMode: chatDeleteMode)
await MainActor.run { ChatModel.shared.removeChat(cInfo.id) }
} catch let error {
logger.error("deleteChat apiDeleteChat error: \(responseError(error))")
AlertManager.shared.showAlertMsg(
@@ -774,6 +808,39 @@ func deleteChat(_ chat: Chat, notify: Bool? = nil) async {
}
}
func deleteContactChat(_ chat: Chat, chatDeleteMode: ChatDeleteMode = .full(notify: true)) async -> Alert? {
do {
let cInfo = chat.chatInfo
let ct = try await apiDeleteContact(id: cInfo.apiId, chatDeleteMode: chatDeleteMode)
await MainActor.run {
switch chatDeleteMode {
case .full:
ChatModel.shared.removeChat(cInfo.id)
case .entity:
ChatModel.shared.removeChat(cInfo.id)
ChatModel.shared.addChat(Chat(
chatInfo: .direct(contact: ct),
chatItems: chat.chatItems
))
case .messages:
ChatModel.shared.removeChat(cInfo.id)
ChatModel.shared.addChat(Chat(
chatInfo: .direct(contact: ct),
chatItems: []
))
}
}
} catch let error {
logger.error("deleteContactChat apiDeleteContact error: \(responseError(error))")
return mkAlert(
title: "Error deleting chat!",
message: "Error: \(responseError(error))"
)
}
return nil
}
func apiClearChat(type: ChatType, id: Int64) async throws -> ChatInfo {
let r = await chatSendCmd(.apiClearChat(type: type, id: id), bgTask: false)
if case let .chatCleared(_, updatedChatInfo) = r { return updatedChatInfo }
@@ -1102,10 +1169,17 @@ func networkErrorAlert(_ r: ChatResponse) -> Alert? {
func acceptContactRequest(incognito: Bool, contactRequest: UserContactRequest) async {
if let contact = await apiAcceptContactRequest(incognito: incognito, contactReqId: contactRequest.apiId) {
let chat = Chat(chatInfo: ChatInfo.direct(contact: contact), chatItems: [])
DispatchQueue.main.async {
await MainActor.run {
ChatModel.shared.replaceChat(contactRequest.id, chat)
ChatModel.shared.setContactNetworkStatus(contact, .connected)
}
if contact.sndReady {
DispatchQueue.main.async {
dismissAllSheets(animated: true) {
ChatModel.shared.chatId = chat.id
}
}
}
}
}
@@ -1439,15 +1513,16 @@ func startChat(refreshInvitations: Bool = true) throws {
logger.debug("startChat")
let m = ChatModel.shared
try setNetworkConfig(getNetCfg())
let justStarted = try apiStartChat()
let chatRunning = try apiCheckChatRunning()
m.users = try listUsers()
if justStarted {
if !chatRunning {
try getUserChatData()
NtfManager.shared.setNtfBadgeCount(m.totalUnreadCountForAllUsers())
if (refreshInvitations) {
try refreshCallInvitations()
}
(m.savedToken, m.tokenStatus, m.notificationMode, m.notificationServer) = apiGetNtfToken()
_ = try apiStartChat()
// deviceToken is set when AppDelegate.application(didRegisterForRemoteNotificationsWithDeviceToken:) is called,
// when it is called before startChat
if let token = m.deviceToken {
@@ -1700,6 +1775,11 @@ func processReceivedMsg(_ res: ChatResponse) async {
let cItem = aChatItem.chatItem
await MainActor.run {
if active(user) {
if case let .direct(contact) = cInfo, contact.chatDeleted {
var updatedContact = contact
updatedContact.chatDeleted = false
m.updateContact(updatedContact)
}
m.addChatItem(cInfo, cItem)
} else if cItem.isRcvNew && cInfo.ntfsEnabled {
m.increaseUnreadCounter(user: user)
@@ -1735,21 +1815,25 @@ func processReceivedMsg(_ res: ChatResponse) async {
m.updateChatItem(r.chatInfo, r.chatReaction.chatItem)
}
}
case let .chatItemDeleted(user, deletedChatItem, toChatItem, _):
case let .chatItemsDeleted(user, items, _):
if !active(user) {
if toChatItem == nil && deletedChatItem.chatItem.isRcvNew && deletedChatItem.chatInfo.ntfsEnabled {
await MainActor.run {
m.decreaseUnreadCounter(user: user)
for item in items {
if item.toChatItem == nil && item.deletedChatItem.chatItem.isRcvNew && item.deletedChatItem.chatInfo.ntfsEnabled {
await MainActor.run {
m.decreaseUnreadCounter(user: user)
}
}
}
return
}
await MainActor.run {
if let toChatItem = toChatItem {
_ = m.upsertChatItem(toChatItem.chatInfo, toChatItem.chatItem)
} else {
m.removeChatItem(deletedChatItem.chatInfo, deletedChatItem.chatItem)
for item in items {
if let toChatItem = item.toChatItem {
_ = m.upsertChatItem(toChatItem.chatInfo, toChatItem.chatItem)
} else {
m.removeChatItem(item.deletedChatItem.chatInfo, item.deletedChatItem.chatItem)
}
}
}
case let .receivedGroupInvitation(user, groupInfo, _, _):
+468 -74
View File
@@ -94,17 +94,20 @@ struct ChatInfoView: View {
@Environment(\.dismiss) var dismiss: DismissAction
@ObservedObject var chat: Chat
@State var contact: Contact
@Binding var connectionStats: ConnectionStats?
@Binding var customUserProfile: Profile?
@State var localAlias: String
@Binding var connectionCode: String?
var onSearch: () -> Void
@State private var connectionStats: ConnectionStats? = nil
@State private var customUserProfile: Profile? = nil
@State private var connectionCode: String? = nil
@FocusState private var aliasTextFieldFocused: Bool
@State private var alert: ChatInfoViewAlert? = nil
@State private var showDeleteContactActionSheet = false
@State private var actionSheet: SomeActionSheet? = nil
@State private var sheet: SomeSheet<AnyView>? = nil
@State private var showConnectContactViaAddressDialog = false
@State private var sendReceipts = SendReceipts.userDefault(true)
@State private var sendReceiptsUserDefault = true
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
enum ChatInfoViewAlert: Identifiable {
case clearChatAlert
case networkStatusAlert
@@ -112,6 +115,7 @@ struct ChatInfoView: View {
case abortSwitchAddressAlert
case syncConnectionForceAlert
case queueInfo(info: String)
case someAlert(alert: SomeAlert)
case error(title: LocalizedStringKey, error: LocalizedStringKey?)
var id: String {
@@ -122,11 +126,12 @@ struct ChatInfoView: View {
case .abortSwitchAddressAlert: return "abortSwitchAddressAlert"
case .syncConnectionForceAlert: return "syncConnectionForceAlert"
case let .queueInfo(info): return "queueInfo \(info)"
case let .someAlert(alert): return "chatInfoSomeAlert \(alert.id)"
case let .error(title, _): return "error \(title)"
}
}
}
var body: some View {
NavigationView {
List {
@@ -136,12 +141,29 @@ struct ChatInfoView: View {
.onTapGesture {
aliasTextFieldFocused = false
}
Group {
localAliasTextEdit()
}
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
.padding(.bottom, 18)
GeometryReader { g in
HStack(alignment: .center, spacing: 8) {
let buttonWidth = g.size.width / 4
searchButton(width: buttonWidth)
AudioCallButton(chat: chat, contact: contact, width: buttonWidth) { alert = .someAlert(alert: $0) }
VideoButton(chat: chat, contact: contact, width: buttonWidth) { alert = .someAlert(alert: $0) }
muteButton(width: buttonWidth)
}
}
.padding(.trailing)
.frame(maxWidth: .infinity)
.frame(height: infoViewActionButtonHeight)
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 8))
if let customUserProfile = customUserProfile {
Section(header: Text("Incognito").foregroundColor(theme.colors.secondary)) {
@@ -153,7 +175,7 @@ struct ChatInfoView: View {
}
}
}
Section {
Group {
if let code = connectionCode { verifyCodeButton(code) }
@@ -173,14 +195,18 @@ struct ChatInfoView: View {
} label: {
Label("Chat theme", systemImage: "photo")
}
// } else if developerTools {
// synchronizeConnectionButtonForce()
// }
}
.disabled(!contact.ready || !contact.active)
if let conn = contact.activeConn {
Section {
infoRow(Text(String("E2E encryption")), conn.connPQEnabled ? "Quantum resistant" : "Standard")
}
}
if let contactLink = contact.contactLink {
Section {
SimpleXLinkQRCode(uri: contactLink)
@@ -197,7 +223,7 @@ struct ChatInfoView: View {
.foregroundColor(theme.colors.secondary)
}
}
if contact.ready && contact.active {
Section(header: Text("Servers").foregroundColor(theme.colors.secondary)) {
networkStatusRow()
@@ -226,12 +252,12 @@ struct ChatInfoView: View {
}
}
}
Section {
clearChatButton()
deleteContactButton()
}
if developerTools {
Section(header: Text("For console").foregroundColor(theme.colors.secondary)) {
infoRow("Local name", chat.chatInfo.localDisplayName)
@@ -260,6 +286,24 @@ struct ChatInfoView: View {
sendReceiptsUserDefault = currentUser.sendRcptsContacts
}
sendReceipts = SendReceipts.fromBool(contact.chatSettings.sendRcpts, userDefault: sendReceiptsUserDefault)
Task {
do {
let (stats, profile) = try await apiContactInfo(chat.chatInfo.apiId)
let (ct, code) = try await apiGetContactCode(chat.chatInfo.apiId)
await MainActor.run {
connectionStats = stats
customUserProfile = profile
connectionCode = code
if contact.activeConn?.connectionCode != ct.activeConn?.connectionCode {
chat.chatInfo = .direct(contact: ct)
}
}
} catch let error {
logger.error("apiContactInfo or apiGetContactCode error: \(responseError(error))")
}
}
}
.alert(item: $alert) { alertItem in
switch(alertItem) {
@@ -269,37 +313,26 @@ struct ChatInfoView: View {
case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchContactAddress)
case .syncConnectionForceAlert: return syncConnectionForceAlert({ syncContactConnection(force: true) })
case let .queueInfo(info): return queueInfoAlert(info)
case let .someAlert(a): return a.alert
case let .error(title, error): return mkAlert(title: title, message: error)
}
}
.actionSheet(isPresented: $showDeleteContactActionSheet) {
if contact.sndReady && contact.active {
return ActionSheet(
title: Text("Delete contact?\nThis cannot be undone!"),
buttons: [
.destructive(Text("Delete and notify contact")) { deleteContact(notify: true) },
.destructive(Text("Delete")) { deleteContact(notify: false) },
.cancel()
]
)
.actionSheet(item: $actionSheet) { $0.actionSheet }
.sheet(item: $sheet) {
if #available(iOS 16.0, *) {
$0.content
.presentationDetents([.fraction(0.4)])
} else {
return ActionSheet(
title: Text("Delete contact?\nThis cannot be undone!"),
buttons: [
.destructive(Text("Delete")) { deleteContact() },
.cancel()
]
)
$0.content
}
}
}
private func contactInfoHeader() -> some View {
VStack {
VStack(spacing: 8) {
let cInfo = chat.chatInfo
ChatInfoImage(chat: chat, size: 192, color: Color(uiColor: .tertiarySystemFill))
.padding(.top, 12)
.padding()
.padding(.vertical, 12)
if contact.verified {
(
Text(Image(systemName: "checkmark.shield"))
@@ -328,7 +361,7 @@ struct ChatInfoView: View {
}
.frame(maxWidth: .infinity, alignment: .center)
}
private func localAliasTextEdit() -> some View {
TextField("Set contact name…", text: $localAlias)
.disableAutocorrection(true)
@@ -345,7 +378,7 @@ struct ChatInfoView: View {
.multilineTextAlignment(.center)
.foregroundColor(theme.colors.secondary)
}
private func setContactAlias() {
Task {
do {
@@ -360,6 +393,25 @@ struct ChatInfoView: View {
}
}
private func searchButton(width: CGFloat) -> some View {
InfoViewButton(image: "magnifyingglass", title: "search", width: width) {
dismiss()
onSearch()
}
.disabled(!contact.ready || chat.chatItems.isEmpty)
}
private func muteButton(width: CGFloat) -> some View {
InfoViewButton(
image: chat.chatInfo.ntfsEnabled ? "speaker.slash.fill" : "speaker.wave.2.fill",
title: chat.chatInfo.ntfsEnabled ? "mute" : "unmute",
width: width
) {
toggleNotifications(chat, enableNtfs: !chat.chatInfo.ntfsEnabled)
}
.disabled(!contact.ready || !contact.active)
}
private func verifyCodeButton(_ code: String) -> some View {
NavigationLink {
VerifyCodeView(
@@ -389,7 +441,7 @@ struct ChatInfoView: View {
)
}
}
private func contactPreferencesButton() -> some View {
NavigationLink {
ContactPreferencesView(
@@ -404,7 +456,7 @@ struct ChatInfoView: View {
Label("Contact preferences", systemImage: "switch.2")
}
}
private func sendReceiptsOption() -> some View {
Picker(selection: $sendReceipts) {
ForEach([.yes, .no, .userDefault(sendReceiptsUserDefault)]) { (opt: SendReceipts) in
@@ -418,13 +470,13 @@ struct ChatInfoView: View {
setSendReceipts()
}
}
private func setSendReceipts() {
var chatSettings = chat.chatInfo.chatSettings ?? ChatSettings.defaults
chatSettings.sendRcpts = sendReceipts.bool()
updateChatSettings(chat, chatSettings: chatSettings)
}
private func synchronizeConnectionButton() -> some View {
Button {
syncContactConnection(force: false)
@@ -433,7 +485,7 @@ struct ChatInfoView: View {
.foregroundColor(.orange)
}
}
private func synchronizeConnectionButtonForce() -> some View {
Button {
alert = .syncConnectionForceAlert
@@ -442,7 +494,7 @@ struct ChatInfoView: View {
.foregroundColor(.red)
}
}
private func networkStatusRow() -> some View {
HStack {
Text("Network status")
@@ -455,23 +507,30 @@ struct ChatInfoView: View {
serverImage()
}
}
private func serverImage() -> some View {
let status = chatModel.contactNetworkStatus(contact)
return Image(systemName: status.imageName)
.foregroundColor(status == .connected ? .green : theme.colors.secondary)
.font(.system(size: 12))
}
private func deleteContactButton() -> some View {
Button(role: .destructive) {
showDeleteContactActionSheet = true
deleteContactDialog(
chat,
contact,
dismissToChatList: true,
showAlert: { alert = .someAlert(alert: $0) },
showActionSheet: { actionSheet = $0 },
showSheetContent: { sheet = $0 }
)
} label: {
Label("Delete contact", systemImage: "trash")
Label("Delete contact", systemImage: "person.badge.minus")
.foregroundColor(Color.red)
}
}
private func clearChatButton() -> some View {
Button() {
alert = .clearChatAlert
@@ -480,26 +539,7 @@ struct ChatInfoView: View {
.foregroundColor(Color.orange)
}
}
private func deleteContact(notify: Bool? = nil) {
Task {
do {
try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, notify: notify)
await MainActor.run {
dismiss()
chatModel.chatId = nil
chatModel.removeChat(chat.chatInfo.id)
}
} catch let error {
logger.error("deleteContactAlert apiDeleteChat error: \(responseError(error))")
let a = getErrorAlert(error, "Error deleting contact")
await MainActor.run {
alert = .error(title: a.title, error: a.message)
}
}
}
}
private func clearChatAlert() -> Alert {
Alert(
title: Text("Clear conversation?"),
@@ -513,14 +553,14 @@ struct ChatInfoView: View {
secondaryButton: .cancel()
)
}
private func networkStatusAlert() -> Alert {
Alert(
title: Text("Network status"),
message: Text(chatModel.contactNetworkStatus(contact).statusExplanation)
)
}
private func switchContactAddress() {
Task {
do {
@@ -539,7 +579,7 @@ struct ChatInfoView: View {
}
}
}
private func abortSwitchContactAddress() {
Task {
do {
@@ -557,7 +597,7 @@ struct ChatInfoView: View {
}
}
}
private func syncContactConnection(force: Bool) {
Task {
do {
@@ -578,6 +618,153 @@ struct ChatInfoView: View {
}
}
struct AudioCallButton: View {
var chat: Chat
var contact: Contact
var width: CGFloat
var showAlert: (SomeAlert) -> Void
var body: some View {
CallButton(
chat: chat,
contact: contact,
image: "phone.fill",
title: "call",
mediaType: .audio,
width: width,
showAlert: showAlert
)
}
}
struct VideoButton: View {
var chat: Chat
var contact: Contact
var width: CGFloat
var showAlert: (SomeAlert) -> Void
var body: some View {
CallButton(
chat: chat,
contact: contact,
image: "video.fill",
title: "video",
mediaType: .video,
width: width,
showAlert: showAlert
)
}
}
private struct CallButton: View {
var chat: Chat
var contact: Contact
var image: String
var title: LocalizedStringKey
var mediaType: CallMediaType
var width: CGFloat
var showAlert: (SomeAlert) -> Void
var body: some View {
let canCall = contact.ready && contact.active && chat.chatInfo.featureEnabled(.calls) && ChatModel.shared.activeCall == nil
InfoViewButton(image: image, title: title, disabledLook: !canCall, width: width) {
if canCall {
CallController.shared.startCall(contact, mediaType)
} else if contact.nextSendGrpInv {
showAlert(SomeAlert(
alert: mkAlert(
title: "Can't call contact",
message: "Send message to enable calls."
),
id: "can't call contact, send message"
))
} else if !contact.active {
showAlert(SomeAlert(
alert: mkAlert(
title: "Can't call contact",
message: "Contact is deleted."
),
id: "can't call contact, contact deleted"
))
} else if !contact.ready {
showAlert(SomeAlert(
alert: mkAlert(
title: "Can't call contact",
message: "Connecting to contact, please wait or check later!"
),
id: "can't call contact, contact not ready"
))
} else if !chat.chatInfo.featureEnabled(.calls) {
switch chat.chatInfo.showEnableCallsAlert {
case .userEnable:
showAlert(SomeAlert(
alert: Alert(
title: Text("Allow calls?"),
message: Text("You need to allow your contact to call to be able to call them."),
primaryButton: .default(Text("Allow")) {
allowFeatureToContact(contact, .calls)
},
secondaryButton: .cancel()
),
id: "allow calls"
))
case .askContact:
showAlert(SomeAlert(
alert: mkAlert(
title: "Calls prohibited!",
message: "Please ask your contact to enable calls."
),
id: "calls prohibited, ask contact"
))
case .other:
showAlert(SomeAlert(
alert: mkAlert(
title: "Calls prohibited!",
message: "Please check yours and your contact preferences."
)
, id: "calls prohibited, other"
))
}
} else {
showAlert(SomeAlert(
alert: mkAlert(title: "Can't call contact"),
id: "can't call contact"
))
}
}
.disabled(ChatModel.shared.activeCall != nil)
}
}
let infoViewActionButtonHeight: CGFloat = 60
struct InfoViewButton: View {
var image: String
var title: LocalizedStringKey
var disabledLook: Bool = false
var width: CGFloat
var action: () -> Void
var body: some View {
VStack(spacing: 4) {
Image(systemName: image)
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
Text(title)
.font(.caption)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.foregroundColor(.accentColor)
.background(Color(.secondarySystemGroupedBackground))
.cornerRadius(10.0)
.frame(width: width, height: infoViewActionButtonHeight)
.disabled(disabledLook)
.onTapGesture(perform: action)
}
}
struct ChatWallpaperEditorSheet: View {
@Environment(\.dismiss) var dismiss
@EnvironmentObject var theme: AppTheme
@@ -763,15 +950,222 @@ func queueInfoAlert(_ info: String) -> Alert {
)
}
func deleteContactDialog(
_ chat: Chat,
_ contact: Contact,
dismissToChatList: Bool,
showAlert: @escaping (SomeAlert) -> Void,
showActionSheet: @escaping (SomeActionSheet) -> Void,
showSheetContent: @escaping (SomeSheet<AnyView>) -> Void
) {
if contact.sndReady && contact.active && !contact.chatDeleted {
deleteContactOrConversationDialog(chat, contact, dismissToChatList, showAlert, showActionSheet, showSheetContent)
} else if contact.sndReady && contact.active && contact.chatDeleted {
deleteContactWithoutConversation(chat, contact, dismissToChatList, showAlert, showActionSheet)
} else { // !(contact.sndReady && contact.active)
deleteNotReadyContact(chat, contact, dismissToChatList, showAlert, showActionSheet)
}
}
private func deleteContactOrConversationDialog(
_ chat: Chat,
_ contact: Contact,
_ dismissToChatList: Bool,
_ showAlert: @escaping (SomeAlert) -> Void,
_ showActionSheet: @escaping (SomeActionSheet) -> Void,
_ showSheetContent: @escaping (SomeSheet<AnyView>) -> Void
) {
showActionSheet(SomeActionSheet(
actionSheet: ActionSheet(
title: Text("Delete contact?"),
buttons: [
.destructive(Text("Only delete conversation")) {
deleteContactMaybeErrorAlert(chat, contact, chatDeleteMode: .messages, dismissToChatList, showAlert)
},
.destructive(Text("Delete contact")) {
showSheetContent(SomeSheet(
content: { AnyView(
DeleteActiveContactDialog(
chat: chat,
contact: contact,
dismissToChatList: dismissToChatList,
showAlert: showAlert
)
) },
id: "DeleteActiveContactDialog"
))
},
.cancel()
]
),
id: "deleteContactOrConversationDialog"
))
}
private func deleteContactMaybeErrorAlert(
_ chat: Chat,
_ contact: Contact,
chatDeleteMode: ChatDeleteMode,
_ dismissToChatList: Bool,
_ showAlert: @escaping (SomeAlert) -> Void
) {
Task {
let alert_ = await deleteContactChat(chat, chatDeleteMode: chatDeleteMode)
if let alert = alert_ {
showAlert(SomeAlert(alert: alert, id: "deleteContactMaybeErrorAlert, error"))
} else {
if dismissToChatList {
await MainActor.run {
ChatModel.shared.chatId = nil
}
DispatchQueue.main.async {
dismissAllSheets(animated: true) {
if case .messages = chatDeleteMode, showDeleteConversationNoticeDefault.get() {
AlertManager.shared.showAlert(deleteConversationNotice(contact))
} else if chatDeleteMode.isEntity, showDeleteContactNoticeDefault.get() {
AlertManager.shared.showAlert(deleteContactNotice(contact))
}
}
}
} else {
if case .messages = chatDeleteMode, showDeleteConversationNoticeDefault.get() {
showAlert(SomeAlert(alert: deleteConversationNotice(contact), id: "deleteContactMaybeErrorAlert, deleteConversationNotice"))
} else if chatDeleteMode.isEntity, showDeleteContactNoticeDefault.get() {
showAlert(SomeAlert(alert: deleteContactNotice(contact), id: "deleteContactMaybeErrorAlert, deleteContactNotice"))
}
}
}
}
}
private func deleteConversationNotice(_ contact: Contact) -> Alert {
return Alert(
title: Text("Conversation deleted!"),
message: Text("You can still send messages to \(contact.displayName) from the Deleted chats."),
primaryButton: .default(Text("Don't show again")) {
showDeleteConversationNoticeDefault.set(false)
},
secondaryButton: .default(Text("Ok"))
)
}
private func deleteContactNotice(_ contact: Contact) -> Alert {
return Alert(
title: Text("Contact deleted!"),
message: Text("You can still view conversation with \(contact.displayName) in the list of chats."),
primaryButton: .default(Text("Don't show again")) {
showDeleteContactNoticeDefault.set(false)
},
secondaryButton: .default(Text("Ok"))
)
}
enum ContactDeleteMode {
case full
case entity
public func toChatDeleteMode(notify: Bool) -> ChatDeleteMode {
switch self {
case .full: .full(notify: notify)
case .entity: .entity(notify: notify)
}
}
}
struct DeleteActiveContactDialog: View {
@Environment(\.dismiss) var dismiss
@EnvironmentObject var theme: AppTheme
var chat: Chat
var contact: Contact
var dismissToChatList: Bool
var showAlert: (SomeAlert) -> Void
@State private var keepConversation = false
var body: some View {
NavigationView {
List {
Section {
Toggle("Keep conversation", isOn: $keepConversation)
Button(role: .destructive) {
dismiss()
deleteContactMaybeErrorAlert(chat, contact, chatDeleteMode: contactDeleteMode.toChatDeleteMode(notify: false), dismissToChatList, showAlert)
} label: {
Text("Delete without notification")
}
Button(role: .destructive) {
dismiss()
deleteContactMaybeErrorAlert(chat, contact, chatDeleteMode: contactDeleteMode.toChatDeleteMode(notify: true), dismissToChatList, showAlert)
} label: {
Text("Delete and notify contact")
}
} footer: {
Text("Contact will be deleted - this cannot be undone!")
.foregroundColor(theme.colors.secondary)
}
}
.modifier(ThemedBackground(grouped: true))
}
}
var contactDeleteMode: ContactDeleteMode {
keepConversation ? .entity : .full
}
}
private func deleteContactWithoutConversation(
_ chat: Chat,
_ contact: Contact,
_ dismissToChatList: Bool,
_ showAlert: @escaping (SomeAlert) -> Void,
_ showActionSheet: @escaping (SomeActionSheet) -> Void
) {
showActionSheet(SomeActionSheet(
actionSheet: ActionSheet(
title: Text("Confirm contact deletion?"),
buttons: [
.destructive(Text("Delete and notify contact")) {
deleteContactMaybeErrorAlert(chat, contact, chatDeleteMode: .full(notify: true), dismissToChatList, showAlert)
},
.destructive(Text("Delete without notification")) {
deleteContactMaybeErrorAlert(chat, contact, chatDeleteMode: .full(notify: false), dismissToChatList, showAlert)
},
.cancel()
]
),
id: "deleteContactWithoutConversation"
))
}
private func deleteNotReadyContact(
_ chat: Chat,
_ contact: Contact,
_ dismissToChatList: Bool,
_ showAlert: @escaping (SomeAlert) -> Void,
_ showActionSheet: @escaping (SomeActionSheet) -> Void
) {
showActionSheet(SomeActionSheet(
actionSheet: ActionSheet(
title: Text("Confirm contact deletion?"),
buttons: [
.destructive(Text("Confirm")) {
deleteContactMaybeErrorAlert(chat, contact, chatDeleteMode: .full(notify: false), dismissToChatList, showAlert)
},
.cancel()
]
),
id: "deleteNotReadyContact"
))
}
struct ChatInfoView_Previews: PreviewProvider {
static var previews: some View {
ChatInfoView(
chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []),
contact: Contact.sampleData,
connectionStats: Binding.constant(nil),
customUserProfile: Binding.constant(nil),
localAlias: "",
connectionCode: Binding.constant(nil)
onSearch: {}
)
}
}
@@ -11,6 +11,7 @@ import SimpleXChat
struct CIChatFeatureView: View {
@EnvironmentObject var m: ChatModel
@ObservedObject var im = ItemsModel.shared
@ObservedObject var chat: Chat
@EnvironmentObject var theme: AppTheme
var chatItem: ChatItem
@@ -53,8 +54,8 @@ struct CIChatFeatureView: View {
var fs: [FeatureInfo] = []
var icons: Set<String> = []
if var i = m.getChatItemIndex(chatItem) {
while i < m.reversedChatItems.count,
let f = featureInfo(m.reversedChatItems[i]) {
while i < im.reversedChatItems.count,
let f = featureInfo(im.reversedChatItems[i]) {
if !icons.contains(f.icon) {
fs.insert(f, at: 0)
icons.insert(f.icon)
@@ -49,7 +49,7 @@ struct FramedItemView: View {
if let qi = chatItem.quotedItem {
ciQuoteView(qi)
.onTapGesture {
if let ci = m.reversedChatItems.first(where: { $0.id == qi.itemId }) {
if let ci = ItemsModel.shared.reversedChatItems.first(where: { $0.id == qi.itemId }) {
withAnimation {
scrollModel.scrollToItem(id: ci.id)
}
@@ -35,8 +35,8 @@ struct MarkedDeletedItemView: View {
var blockedByAdmin = 0
var deleted = 0
var moderatedBy: Set<String> = []
while i < m.reversedChatItems.count,
let ci = .some(m.reversedChatItems[i]),
while i < ItemsModel.shared.reversedChatItems.count,
let ci = .some(ItemsModel.shared.reversedChatItems[i]),
ci.mergeCategory == ciCategory,
let itemDeleted = ci.meta.itemDeleted {
switch itemDeleted {
@@ -21,7 +21,6 @@ struct ChatItemForwardingView: View {
@State private var searchText: String = ""
@FocusState private var searchFocused
@State private var alert: SomeAlert?
@State private var hasSimplexLink_: Bool?
private let chatsToForwardTo = filterChatsToForwardTo(chats: ChatModel.shared.chats)
var body: some View {
@@ -67,34 +66,6 @@ struct ChatItemForwardingView: View {
}
}
private func prohibitedByPref(_ chat: Chat) -> Bool {
// preference checks should match checks in compose view
let simplexLinkProhibited = hasSimplexLink && !chat.groupFeatureEnabled(.simplexLinks)
let fileProhibited = (ci.content.msgContent?.isMediaOrFileAttachment ?? false) && !chat.groupFeatureEnabled(.files)
let voiceProhibited = (ci.content.msgContent?.isVoice ?? false) && !chat.chatInfo.featureEnabled(.voice)
return switch chat.chatInfo {
case .direct: voiceProhibited
case .group: simplexLinkProhibited || fileProhibited || voiceProhibited
case .local: false
case .contactRequest: false
case .contactConnection: false
case .invalidJSON: false
}
}
private var hasSimplexLink: Bool {
if let hasSimplexLink_ { return hasSimplexLink_ }
let r =
if let mcText = ci.content.msgContent?.text,
let parsedMsg = parseSimpleXMarkdown(mcText) {
parsedMsgHasSimplexLink(parsedMsg)
} else {
false
}
hasSimplexLink_ = r
return r
}
private func emptyList() -> some View {
Text("No filtered chats")
.foregroundColor(theme.colors.secondary)
@@ -102,7 +73,11 @@ struct ChatItemForwardingView: View {
}
@ViewBuilder private func forwardListChatView(_ chat: Chat) -> some View {
let prohibited = prohibitedByPref(chat)
let prohibited = chat.prohibitedByPref(
hasSimplexLink: hasSimplexLink(ci.content.msgContent?.text),
isMediaOrFileAttachment: ci.content.msgContent?.isMediaOrFileAttachment ?? false,
isVoice: ci.content.msgContent?.isVoice ?? false
)
Button {
if prohibited {
alert = SomeAlert(
@@ -440,7 +440,7 @@ struct ChatItemInfoView: View {
private func memberDeliveryStatusView(_ member: GroupMember, _ status: GroupSndStatus, _ sentViaProxy: Bool?) -> some View {
HStack{
ProfileImage(imageStr: member.image, size: 30)
MemberProfileImage(member, size: 30)
.padding(.trailing, 2)
Text(member.chatViewName)
.lineLimit(1)
+367 -177
View File
@@ -15,6 +15,7 @@ private let memberImageSize: CGFloat = 34
struct ChatView: View {
@EnvironmentObject var chatModel: ChatModel
@ObservedObject var im = ItemsModel.shared
@State var theme: AppTheme = buildTheme()
@Environment(\.dismiss) var dismiss
@Environment(\.colorScheme) var colorScheme
@@ -42,6 +43,9 @@ struct ChatView: View {
@State private var showGroupLinkSheet: Bool = false
@State private var groupLink: String?
@State private var groupLinkMemberRole: GroupMemberRole = .member
@State private var selectedChatItems: Set<Int64>? = nil
@State private var showDeleteSelectedMessages: Bool = false
@State private var allowToDeleteSelectedMessagesForAll: Bool = false
var body: some View {
if #available(iOS 16.0, *) {
@@ -79,25 +83,58 @@ struct ChatView: View {
floatingButtons(counts: floatingButtonModel.unreadChatItemCounts)
}
connectingText()
ComposeView(
chat: chat,
composeState: $composeState,
keyboardVisible: $keyboardVisible
)
.disabled(!cInfo.sendMsgEnabled)
if selectedChatItems == nil {
ComposeView(
chat: chat,
composeState: $composeState,
keyboardVisible: $keyboardVisible
)
.disabled(!cInfo.sendMsgEnabled)
} else {
SelectedItemsBottomToolbar(
chatItems: ItemsModel.shared.reversedChatItems,
selectedChatItems: $selectedChatItems,
chatInfo: chat.chatInfo,
deleteItems: { forAll in
allowToDeleteSelectedMessagesForAll = forAll
showDeleteSelectedMessages = true
},
moderateItems: {
if case let .group(groupInfo) = chat.chatInfo {
showModerateSelectedMessagesAlert(groupInfo)
}
}
)
}
}
.navigationTitle(cInfo.chatViewName)
.background(theme.colors.background)
.navigationBarTitleDisplayMode(.inline)
.environmentObject(theme)
.confirmationDialog(selectedChatItems?.count == 1 ? "Delete message?" : "Delete \((selectedChatItems?.count ?? 0)) messages?", isPresented: $showDeleteSelectedMessages, titleVisibility: .visible) {
Button("Delete for me", role: .destructive) {
if let selected = selectedChatItems {
deleteMessages(chat, selected.sorted(), .cidmInternal, moderate: false, deletedSelectedMessages) }
}
if allowToDeleteSelectedMessagesForAll {
Button(broadcastDeleteButtonText(chat), role: .destructive) {
if let selected = selectedChatItems {
allowToDeleteSelectedMessagesForAll = false
deleteMessages(chat, selected.sorted(), .cidmBroadcast, moderate: false, deletedSelectedMessages)
}
}
}
}
.onAppear {
loadChat(chat: chat)
initChatView()
selectedChatItems = nil
}
.onChange(of: chatModel.chatId) { cId in
showChatInfoSheet = false
stopAudioPlayer()
if let cId {
selectedChatItems = nil
if let c = chatModel.getChat(cId) {
chat = c
}
@@ -110,7 +147,7 @@ struct ChatView: View {
.onChange(of: revealedChatItem) { _ in
NotificationCenter.postReverseListNeedsLayout()
}
.onChange(of: chatModel.reversedChatItems) { reversedChatItems in
.onChange(of: im.reversedChatItems) { reversedChatItems in
if reversedChatItems.count <= loadItemsPerPage && filtered(reversedChatItems).count < 10 {
loadChatItems(chat.chatInfo)
}
@@ -124,7 +161,7 @@ struct ChatView: View {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
if chatModel.chatId == nil {
chatModel.chatItemStatuses = [:]
chatModel.reversedChatItems = []
ItemsModel.shared.reversedChatItems = []
chatModel.groupMembers = []
chatModel.groupMembersIndexes.removeAll()
chatModel.membersLoaded = false
@@ -137,35 +174,23 @@ struct ChatView: View {
}
.toolbar {
ToolbarItem(placement: .principal) {
if case let .direct(contact) = cInfo {
if selectedChatItems != nil {
SelectedItemsTopToolbar(selectedChatItems: $selectedChatItems)
} else if case let .direct(contact) = cInfo {
Button {
Task {
do {
let (stats, profile) = try await apiContactInfo(chat.chatInfo.apiId)
let (ct, code) = try await apiGetContactCode(chat.chatInfo.apiId)
await MainActor.run {
connectionStats = stats
customUserProfile = profile
connectionCode = code
if contact.activeConn?.connectionCode != ct.activeConn?.connectionCode {
chat.chatInfo = .direct(contact: ct)
}
}
} catch let error {
logger.error("apiContactInfo or apiGetContactCode error: \(responseError(error))")
}
await MainActor.run { showChatInfoSheet = true }
showChatInfoSheet = true
}
} label: {
ChatInfoToolbar(chat: chat)
}
.appSheet(isPresented: $showChatInfoSheet, onDismiss: {
connectionStats = nil
customUserProfile = nil
connectionCode = nil
theme = buildTheme()
}) {
ChatInfoView(chat: chat, contact: contact, connectionStats: $connectionStats, customUserProfile: $customUserProfile, localAlias: chat.chatInfo.localAlias, connectionCode: $connectionCode)
.appSheet(isPresented: $showChatInfoSheet) {
ChatInfoView(
chat: chat,
contact: contact,
localAlias: chat.chatInfo.localAlias,
onSearch: { focusSearch() }
)
}
} else if case let .group(groupInfo) = cInfo {
Button {
@@ -183,7 +208,8 @@ struct ChatView: View {
chat.chatInfo = .group(groupInfo: gInfo)
chat.created = Date.now
}
)
),
onSearch: { focusSearch() }
)
}
} else if case .local = cInfo {
@@ -191,66 +217,76 @@ struct ChatView: View {
}
}
ToolbarItem(placement: .navigationBarTrailing) {
switch cInfo {
case let .direct(contact):
HStack {
let callsPrefEnabled = contact.mergedPreferences.calls.enabled.forUser
if callsPrefEnabled {
if chatModel.activeCall == nil {
callButton(contact, .audio, imageName: "phone")
.disabled(!contact.ready || !contact.active)
} else if let call = chatModel.activeCall, call.contact.id == cInfo.id {
endCallButton(call)
}
if selectedChatItems != nil {
Button {
withAnimation {
selectedChatItems = nil
}
Menu {
if callsPrefEnabled && chatModel.activeCall == nil {
Button {
CallController.shared.startCall(contact, .video)
} label: {
Label("Video call", systemImage: "video")
} label: {
Text("Cancel")
}
} else {
switch cInfo {
case let .direct(contact):
HStack {
let callsPrefEnabled = contact.mergedPreferences.calls.enabled.forUser
if callsPrefEnabled {
if chatModel.activeCall == nil {
callButton(contact, .audio, imageName: "phone")
.disabled(!contact.ready || !contact.active)
} else if let call = chatModel.activeCall, call.contact.id == cInfo.id {
endCallButton(call)
}
.disabled(!contact.ready || !contact.active)
}
searchButton()
ToggleNtfsButton(chat: chat)
.disabled(!contact.ready || !contact.active)
} label: {
Image(systemName: "ellipsis")
}
}
case let .group(groupInfo):
HStack {
if groupInfo.canAddMembers {
if (chat.chatInfo.incognito) {
groupLinkButton()
.appSheet(isPresented: $showGroupLinkSheet) {
GroupLinkView(
groupId: groupInfo.groupId,
groupLink: $groupLink,
groupLinkMemberRole: $groupLinkMemberRole,
showTitle: true,
creatingGroup: false
)
}
} else {
addMembersButton()
.appSheet(isPresented: $showAddMembersSheet) {
AddGroupMembersView(chat: chat, groupInfo: groupInfo)
Menu {
if callsPrefEnabled && chatModel.activeCall == nil {
Button {
CallController.shared.startCall(contact, .video)
} label: {
Label("Video call", systemImage: "video")
}
.disabled(!contact.ready || !contact.active)
}
searchButton()
ToggleNtfsButton(chat: chat)
.disabled(!contact.ready || !contact.active)
} label: {
Image(systemName: "ellipsis")
}
}
Menu {
searchButton()
ToggleNtfsButton(chat: chat)
} label: {
Image(systemName: "ellipsis")
case let .group(groupInfo):
HStack {
if groupInfo.canAddMembers {
if (chat.chatInfo.incognito) {
groupLinkButton()
.appSheet(isPresented: $showGroupLinkSheet) {
GroupLinkView(
groupId: groupInfo.groupId,
groupLink: $groupLink,
groupLinkMemberRole: $groupLinkMemberRole,
showTitle: true,
creatingGroup: false
)
}
} else {
addMembersButton()
.appSheet(isPresented: $showAddMembersSheet) {
AddGroupMembersView(chat: chat, groupInfo: groupInfo)
}
}
}
Menu {
searchButton()
ToggleNtfsButton(chat: chat)
} label: {
Image(systemName: "ellipsis")
}
}
case .local:
searchButton()
default:
EmptyView()
}
case .local:
searchButton()
default:
EmptyView()
}
}
}
@@ -327,7 +363,7 @@ struct ChatView: View {
reversedChatItems
.enumerated()
.filter { (index, chatItem) in
if let mergeCategory = chatItem.mergeCategory, index > .zero {
if let mergeCategory = chatItem.mergeCategory, index > 0 {
mergeCategory != reversedChatItems[index - 1].mergeCategory
} else {
true
@@ -339,7 +375,7 @@ struct ChatView: View {
private func chatItemsList() -> some View {
let cInfo = chat.chatInfo
let mergedItems = filtered(chatModel.reversedChatItems)
let mergedItems = filtered(im.reversedChatItems)
return GeometryReader { g in
ReverseList(items: mergedItems, scrollState: $scrollModel.state) { ci in
let voiceNoFrame = voiceWithoutFrame(ci)
@@ -372,7 +408,7 @@ struct ChatView: View {
loadChat(chat: c)
}
}
.onChange(of: chatModel.reversedChatItems) { _ in
.onChange(of: im.reversedChatItems) { _ in
floatingButtonModel.chatItemsChanged()
}
}
@@ -407,7 +443,7 @@ struct ChatView: View {
init() {
unreadChatItemCounts = UnreadChatItemCounts(
isNearBottom: true,
unreadBelow: .zero
unreadBelow: 0
)
events
.receive(on: DispatchQueue.global(qos: .background))
@@ -515,14 +551,18 @@ struct ChatView: View {
private func searchButton() -> some View {
Button {
searchMode = true
searchFocussed = true
searchText = ""
focusSearch()
} label: {
Label("Search", systemImage: "magnifyingglass")
}
}
private func focusSearch() {
searchMode = true
searchFocussed = true
searchText = ""
}
private func addMembersButton() -> some View {
Button {
if case let .group(gInfo) = chat.chatInfo {
@@ -552,6 +592,33 @@ struct ChatView: View {
}
}
private func showModerateSelectedMessagesAlert(_ groupInfo: GroupInfo) {
guard let count = selectedChatItems?.count, count > 0 else { return }
AlertManager.shared.showAlert(Alert(
title: Text(count == 1 ? "Delete member message?" : "Delete \(count) messages of members?"),
message: Text(
groupInfo.fullGroupPreferences.fullDelete.on
? (count == 1 ? "The message will be deleted for all members." : "The messages will be deleted for all members.")
: (count == 1 ? "The message will be marked as moderated for all members." : "The messages will be marked as moderated for all members.")
),
primaryButton: .destructive(Text("Delete")) {
if let selected = selectedChatItems {
deleteMessages(chat, selected.sorted(), .cidmBroadcast, moderate: true, deletedSelectedMessages)
}
},
secondaryButton: .cancel()
))
}
private func deletedSelectedMessages() async {
await MainActor.run {
withAnimation {
selectedChatItems = nil
}
}
}
private func loadChatItems(_ cInfo: ChatInfo) {
Task {
if loadingItems || firstPage { return }
@@ -562,7 +629,7 @@ struct ChatView: View {
// Load additional items until the page is +50 large after merging
while chatItemsAvailable && filtered(reversedPage).count < loadItemsPerPage {
let pagination: ChatPagination =
if let lastItem = reversedPage.last ?? chatModel.reversedChatItems.last {
if let lastItem = reversedPage.last ?? im.reversedChatItems.last {
.before(chatItemId: lastItem.id, count: loadItemsPerPage)
} else {
.last(count: loadItemsPerPage)
@@ -580,7 +647,7 @@ struct ChatView: View {
if reversedPage.count == 0 {
firstPage = true
} else {
chatModel.reversedChatItems.append(contentsOf: reversedPage)
im.reversedChatItems.append(contentsOf: reversedPage)
}
loadingItems = false
}
@@ -603,7 +670,8 @@ struct ChatView: View {
maxWidth: maxWidth,
composeState: $composeState,
selectedMember: $selectedMember,
revealedChatItem: $revealedChatItem
revealedChatItem: $revealedChatItem,
selectedChatItems: $selectedChatItems
)
}
@@ -625,6 +693,8 @@ struct ChatView: View {
@State private var chatItemInfo: ChatItemInfo?
@State private var showForwardingSheet: Bool = false
@Binding var selectedChatItems: Set<Int64>?
@State private var allowMenu: Bool = true
var revealed: Bool { chatItem == revealedChatItem }
@@ -634,15 +704,36 @@ struct ChatView: View {
let ciCategory = chatItem.mergeCategory
let (prevHidden, prevItem) = m.getPrevShownChatItem(currIndex, ciCategory)
let range = itemsRange(currIndex, prevHidden)
let im = ItemsModel.shared
Group {
if revealed, let range = range {
let items = Array(zip(Array(range), m.reversedChatItems[range]))
let items = Array(zip(Array(range), im.reversedChatItems[range]))
ForEach(items, id: \.1.viewId) { (i, ci) in
let prev = i == prevHidden ? prevItem : m.reversedChatItems[i + 1]
let prev = i == prevHidden ? prevItem : im.reversedChatItems[i + 1]
chatItemView(ci, nil, prev)
.overlay {
if let selected = selectedChatItems, ci.canBeDeletedForSelf {
Color.clear
.contentShape(Rectangle())
.onTapGesture {
let checked = selected.contains(ci.id)
selectUnselectChatItem(select: !checked, ci)
}
}
}
}
} else {
chatItemView(chatItem, range, prevItem)
.overlay {
if let selected = selectedChatItems, chatItem.canBeDeletedForSelf {
Color.clear
.contentShape(Rectangle())
.onTapGesture {
let checked = selected.contains(chatItem.id)
selectUnselectChatItem(select: !checked, chatItem)
}
}
}
}
}
.onAppear {
@@ -663,9 +754,10 @@ struct ChatView: View {
}
private func unreadItems(_ range: ClosedRange<Int>) -> [ChatItem]? {
let im = ItemsModel.shared
let items = range.compactMap { i in
if i >= 0 && i < m.reversedChatItems.count {
let ci = m.reversedChatItems[i]
if i >= 0 && i < im.reversedChatItems.count {
let ci = im.reversedChatItems[i]
return if ci.isRcvNew { ci } else { nil }
} else {
return nil
@@ -686,11 +778,11 @@ struct ChatView: View {
if case let .groupRcv(member) = ci.chatDir,
case let .group(groupInfo) = chat.chatInfo {
let (prevMember, memCount): (GroupMember?, Int) =
if let range = range {
m.getPrevHiddenMember(member, range)
} else {
(nil, 1)
}
if let range = range {
m.getPrevHiddenMember(member, range)
} else {
(nil, 1)
}
if prevItem == nil || showMemberImage(member, prevItem) || prevMember != nil {
VStack(alignment: .leading, spacing: 4) {
if ci.content.showMemberName {
@@ -703,41 +795,64 @@ struct ChatView: View {
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
.padding(.leading, memberImageSize + 14)
.padding(.leading, memberImageSize + 14 + (selectedChatItems != nil && ci.canBeDeletedForSelf ? 12 + 24 : 0))
.padding(.top, 7)
}
HStack(alignment: .top, spacing: 8) {
ProfileImage(imageStr: member.memberProfile.image, size: memberImageSize, backgroundColor: theme.colors.background)
.onTapGesture {
if m.membersLoaded {
selectedMember = m.getGroupMember(member.groupMemberId)
} else {
Task {
await m.loadGroupMembers(groupInfo) {
selectedMember = m.getGroupMember(member.groupMemberId)
HStack(alignment: .center, spacing: 0) {
if selectedChatItems != nil && ci.canBeDeletedForSelf {
SelectedChatItem(ciId: ci.id, selectedChatItems: $selectedChatItems)
.padding(.trailing, 12)
}
HStack(alignment: .top, spacing: 8) {
MemberProfileImage(member, size: memberImageSize, backgroundColor: theme.colors.background)
.onTapGesture {
if m.membersLoaded {
selectedMember = m.getGroupMember(member.groupMemberId)
} else {
Task {
await m.loadGroupMembers(groupInfo) {
selectedMember = m.getGroupMember(member.groupMemberId)
}
}
}
}
}
.appSheet(item: $selectedMember) { member in
GroupMemberInfoView(groupInfo: groupInfo, groupMember: member, navigation: true)
}
chatItemWithMenu(ci, range, maxWidth)
.appSheet(item: $selectedMember) { member in
GroupMemberInfoView(groupInfo: groupInfo, groupMember: member, navigation: true)
}
chatItemWithMenu(ci, range, maxWidth)
}
}
}
.padding(.bottom, 5)
.padding(.trailing)
.padding(.leading, 12)
} else {
chatItemWithMenu(ci, range, maxWidth)
.padding(.bottom, 5)
.padding(.trailing)
.padding(.leading, memberImageSize + 8 + 12)
HStack(alignment: .center, spacing: 0) {
if selectedChatItems != nil && ci.canBeDeletedForSelf {
SelectedChatItem(ciId: ci.id, selectedChatItems: $selectedChatItems)
.padding(.leading, 12)
}
chatItemWithMenu(ci, range, maxWidth)
.padding(.trailing)
.padding(.leading, memberImageSize + 8 + 12)
}
.padding(.bottom, 5)
}
} else {
chatItemWithMenu(ci, range, maxWidth)
.padding(.horizontal)
.padding(.bottom, 5)
HStack(alignment: .center, spacing: 0) {
if selectedChatItems != nil && ci.canBeDeletedForSelf {
if chat.chatInfo.chatType == .group {
SelectedChatItem(ciId: ci.id, selectedChatItems: $selectedChatItems)
.padding(.leading, 12)
} else {
SelectedChatItem(ciId: ci.id, selectedChatItems: $selectedChatItems)
.padding(.leading)
}
}
chatItemWithMenu(ci, range, maxWidth)
.padding(.horizontal)
}
.padding(.bottom, 5)
}
}
@@ -772,17 +887,17 @@ struct ChatView: View {
}
.confirmationDialog("Delete message?", isPresented: $showDeleteMessage, titleVisibility: .visible) {
Button("Delete for me", role: .destructive) {
deleteMessage(.cidmInternal)
deleteMessage(.cidmInternal, moderate: false)
}
if let di = deletingItem, di.meta.deletable && !di.localNote {
Button(broadcastDeleteButtonText, role: .destructive) {
deleteMessage(.cidmBroadcast)
Button(broadcastDeleteButtonText(chat), role: .destructive) {
deleteMessage(.cidmBroadcast, moderate: false)
}
}
}
.confirmationDialog(deleteMessagesTitle, isPresented: $showDeleteMessages, titleVisibility: .visible) {
Button("Delete for me", role: .destructive) {
deleteMessages()
deleteMessages(chat, deletingItems, moderate: false)
}
}
.frame(maxWidth: maxWidth, maxHeight: .infinity, alignment: alignment)
@@ -891,7 +1006,7 @@ struct ChatView: View {
if !live || !ci.meta.isLive {
deleteButton(ci)
}
if let (groupInfo, _) = ci.memberToModerate(chat.chatInfo) {
if let (groupInfo, _) = ci.memberToModerate(chat.chatInfo), ci.chatDir != .groupSnd {
moderateButton(ci, groupInfo)
}
} else if ci.meta.itemDeleted != nil {
@@ -915,6 +1030,10 @@ struct ChatView: View {
} else {
EmptyView()
}
if selectedChatItems == nil && ci.canBeDeletedForSelf {
Divider()
selectButton(ci)
}
}
var replyButton: Button<some View> {
@@ -970,7 +1089,7 @@ struct ChatView: View {
}
func reactions(from: Int? = nil, till: Int? = nil) -> some View {
ForEach(availableReactions[(from ?? .zero)..<(till ?? availableReactions.count)]) { reaction in
ForEach(availableReactions[(from ?? 0)..<(till ?? availableReactions.count)]) { reaction in
Button(reaction.text) {
setReaction(chatItem, add: true, reaction: reaction)
}
@@ -1087,6 +1206,21 @@ struct ChatView: View {
}
}
private func selectButton(_ ci: ChatItem) -> Button<some View> {
Button {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
withAnimation {
selectUnselectChatItem(select: true, ci)
}
}
} label: {
Label(
NSLocalizedString("Select", comment: "chat item action"),
systemImage: "checkmark.circle"
)
}
}
private func viewInfoButton(_ ci: ChatItem) -> Button<some View> {
Button {
Task {
@@ -1156,7 +1290,7 @@ struct ChatView: View {
if let range = itemsRange(currIndex, prevHidden) {
var itemIds: [Int64] = []
for i in range {
itemIds.append(m.reversedChatItems[i].id)
itemIds.append(ItemsModel.shared.reversedChatItems[i].id)
}
showDeleteMessages = true
deletingItems = itemIds
@@ -1197,7 +1331,7 @@ struct ChatView: View {
),
primaryButton: .destructive(Text("Delete")) {
deletingItem = ci
deleteMessage(.cidmBroadcast)
deleteMessage(.cidmBroadcast, moderate: true)
},
secondaryButton: .cancel()
))
@@ -1248,72 +1382,66 @@ struct ChatView: View {
}
}
private var broadcastDeleteButtonText: LocalizedStringKey {
chat.chatInfo.featureEnabled(.fullDelete) ? "Delete for everyone" : "Mark deleted for everyone"
}
var deleteMessagesTitle: LocalizedStringKey {
let n = deletingItems.count
return n == 1 ? "Delete message?" : "Delete \(n) messages?"
}
private func deleteMessages() {
let itemIds = deletingItems
if itemIds.count > 0 {
let chatInfo = chat.chatInfo
Task {
var deletedItems: [ChatItem] = []
for itemId in itemIds {
do {
let (di, _) = try await apiDeleteChatItem(
type: chatInfo.chatType,
id: chatInfo.apiId,
itemId: itemId,
mode: .cidmInternal
)
deletedItems.append(di)
} catch {
logger.error("ChatView.deleteMessage error: \(error.localizedDescription)")
}
}
await MainActor.run {
for di in deletedItems {
m.removeChatItem(chatInfo, di)
}
private func selectUnselectChatItem(select: Bool, _ ci: ChatItem) {
selectedChatItems = selectedChatItems ?? []
var itemIds: [Int64] = []
if !revealed,
let currIndex = m.getChatItemIndex(ci),
let ciCategory = ci.mergeCategory {
let (prevHidden, _) = m.getPrevShownChatItem(currIndex, ciCategory)
if let range = itemsRange(currIndex, prevHidden) {
for i in range {
itemIds.append(ItemsModel.shared.reversedChatItems[i].id)
}
} else {
itemIds.append(ci.id)
}
} else {
itemIds.append(ci.id)
}
if select {
if let sel = selectedChatItems {
selectedChatItems = sel.union(itemIds)
}
} else {
itemIds.forEach { selectedChatItems?.remove($0) }
}
}
private func deleteMessage(_ mode: CIDeleteMode) {
private func deleteMessage(_ mode: CIDeleteMode, moderate: Bool) {
logger.debug("ChatView deleteMessage")
Task {
logger.debug("ChatView deleteMessage: in Task")
do {
if let di = deletingItem {
var deletedItem: ChatItem
var toItem: ChatItem?
if case .cidmBroadcast = mode,
let (groupInfo, groupMember) = di.memberToModerate(chat.chatInfo) {
(deletedItem, toItem) = try await apiDeleteMemberChatItem(
let r = if case .cidmBroadcast = mode,
moderate,
let (groupInfo, _) = di.memberToModerate(chat.chatInfo) {
try await apiDeleteMemberChatItems(
groupId: groupInfo.apiId,
groupMemberId: groupMember.groupMemberId,
itemId: di.id
itemIds: [di.id]
)
} else {
(deletedItem, toItem) = try await apiDeleteChatItem(
try await apiDeleteChatItems(
type: chat.chatInfo.chatType,
id: chat.chatInfo.apiId,
itemId: di.id,
itemIds: [di.id],
mode: mode
)
}
DispatchQueue.main.async {
deletingItem = nil
if let toItem = toItem {
_ = m.upsertChatItem(chat.chatInfo, toItem)
} else {
m.removeChatItem(chat.chatInfo, deletedItem)
if let itemDeletion = r.first {
await MainActor.run {
deletingItem = nil
if let toItem = itemDeletion.toChatItem {
_ = m.upsertChatItem(chat.chatInfo, toItem.chatItem)
} else {
m.removeChatItem(chat.chatInfo, itemDeletion.deletedChatItem.chatItem)
}
}
}
}
@@ -1322,6 +1450,68 @@ struct ChatView: View {
}
}
}
private struct SelectedChatItem: View {
@EnvironmentObject var theme: AppTheme
var ciId: Int64
@Binding var selectedChatItems: Set<Int64>?
@State var checked: Bool = false
var body: some View {
Image(systemName: checked ? "checkmark.circle.fill" : "circle")
.resizable()
.foregroundColor(checked ? theme.colors.primary : Color(uiColor: .tertiaryLabel))
.frame(width: 24, height: 24)
.onAppear {
checked = selectedChatItems?.contains(ciId) == true
}
.onChange(of: selectedChatItems) { selected in
checked = selected?.contains(ciId) == true
}
}
}
}
}
private func broadcastDeleteButtonText(_ chat: Chat) -> LocalizedStringKey {
chat.chatInfo.featureEnabled(.fullDelete) ? "Delete for everyone" : "Mark deleted for everyone"
}
private func deleteMessages(_ chat: Chat, _ deletingItems: [Int64], _ mode: CIDeleteMode = .cidmInternal, moderate: Bool, _ onSuccess: @escaping () async -> Void = {}) {
let itemIds = deletingItems
if itemIds.count > 0 {
let chatInfo = chat.chatInfo
Task {
do {
let deletedItems = if case .cidmBroadcast = mode,
moderate,
case .group = chat.chatInfo {
try await apiDeleteMemberChatItems(
groupId: chatInfo.apiId,
itemIds: itemIds
)
} else {
try await apiDeleteChatItems(
type: chatInfo.chatType,
id: chatInfo.apiId,
itemIds: itemIds,
mode: mode
)
}
await MainActor.run {
for di in deletedItems {
if let toItem = di.toChatItem {
_ = ChatModel.shared.upsertChatItem(chat.chatInfo, toItem.chatItem)
} else {
ChatModel.shared.removeChatItem(chatInfo, di.deletedChatItem.chatItem)
}
}
}
await onSuccess()
} catch {
logger.error("ChatView.deleteMessages error: \(error.localizedDescription)")
}
}
}
}
@@ -1399,7 +1589,7 @@ struct ChatView_Previews: PreviewProvider {
static var previews: some View {
let chatModel = ChatModel()
chatModel.chatId = "@1"
chatModel.reversedChatItems = [
ItemsModel.shared.reversedChatItems = [
ChatItem.getSample(1, .directSnd, .now, "hello"),
ChatItem.getSample(2, .directRcv, .now, "hi"),
ChatItem.getSample(3, .directRcv, .now, "hi there"),
@@ -1120,10 +1120,6 @@ struct ComposeView: View {
}
}
func parsedMsgHasSimplexLink(_ parsedMsg: [FormattedText]) -> Bool {
parsedMsg.contains(where: { ft in ft.format?.isSimplexLink ?? false })
}
struct ComposeView_Previews: PreviewProvider {
static var previews: some View {
let chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: [])
@@ -257,6 +257,9 @@ struct SendMessageView: View {
var body: some View {
Button(action: {}) {
Image(systemName: "mic.fill")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.foregroundColor(theme.colors.primary)
}
.disabled(disabled)
@@ -310,6 +313,9 @@ struct SendMessageView: View {
}
} label: {
Image(systemName: "mic")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.foregroundColor(theme.colors.secondary)
}
.disabled(composeState.inProgress)
@@ -47,14 +47,13 @@ struct AddGroupMembersViewCommon: View {
var body: some View {
if creatingGroup {
NavigationView {
addGroupMembersView()
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button ("Skip") { addedMembersCb(selectedContacts) }
}
addGroupMembersView()
.navigationBarBackButtonHidden()
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button ("Skip") { addedMembersCb(selectedContacts) }
}
}
}
} else {
addGroupMembersView()
}
@@ -17,10 +17,12 @@ struct GroupChatInfoView: View {
@Environment(\.dismiss) var dismiss: DismissAction
@ObservedObject var chat: Chat
@Binding var groupInfo: GroupInfo
var onSearch: () -> Void
@State private var alert: GroupChatInfoViewAlert? = nil
@State private var groupLink: String?
@State private var groupLinkMemberRole: GroupMemberRole = .member
@State private var showAddMembersSheet: Bool = false
@State private var groupLinkNavLinkActive: Bool = false
@State private var addMembersNavLinkActive: Bool = false
@State private var connectionStats: ConnectionStats?
@State private var connectionCode: String?
@State private var sendReceipts = SendReceipts.userDefault(true)
@@ -68,6 +70,15 @@ struct GroupChatInfoView: View {
List {
groupInfoHeader()
.listRowBackground(Color.clear)
.padding(.bottom, 18)
infoActionButtons()
.padding(.horizontal)
.frame(maxWidth: .infinity)
.frame(height: infoViewActionButtonHeight)
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
Section {
if groupInfo.canEdit {
@@ -198,24 +209,95 @@ struct GroupChatInfoView: View {
.frame(maxWidth: .infinity, alignment: .center)
}
func infoActionButtons() -> some View {
GeometryReader { g in
let buttonWidth = g.size.width / 4
HStack(alignment: .center, spacing: 8) {
searchButton(width: buttonWidth)
if groupInfo.canAddMembers {
addMembersActionButton(width: buttonWidth)
}
muteButton(width: buttonWidth)
}
.frame(maxWidth: .infinity, alignment: .center)
}
}
private func searchButton(width: CGFloat) -> some View {
InfoViewButton(image: "magnifyingglass", title: "search", width: width) {
dismiss()
onSearch()
}
.disabled(!groupInfo.ready || chat.chatItems.isEmpty)
}
@ViewBuilder private func addMembersActionButton(width: CGFloat) -> some View {
if chat.chatInfo.incognito {
ZStack {
InfoViewButton(image: "link.badge.plus", title: "invite", width: width) {
groupLinkNavLinkActive = true
}
NavigationLink(isActive: $groupLinkNavLinkActive) {
groupLinkDestinationView()
} label: {
EmptyView()
}
.frame(width: 1, height: 1)
.hidden()
}
.disabled(!groupInfo.ready)
} else {
ZStack {
InfoViewButton(image: "person.fill.badge.plus", title: "invite", width: width) {
addMembersNavLinkActive = true
}
NavigationLink(isActive: $addMembersNavLinkActive) {
addMembersDestinationView()
} label: {
EmptyView()
}
.frame(width: 1, height: 1)
.hidden()
}
.disabled(!groupInfo.ready)
}
}
private func muteButton(width: CGFloat) -> some View {
InfoViewButton(
image: chat.chatInfo.ntfsEnabled ? "speaker.slash.fill" : "speaker.wave.2.fill",
title: chat.chatInfo.ntfsEnabled ? "mute" : "unmute",
width: width
) {
toggleNotifications(chat, enableNtfs: !chat.chatInfo.ntfsEnabled)
}
.disabled(!groupInfo.ready)
}
private func addMembersButton() -> some View {
NavigationLink {
AddGroupMembersView(chat: chat, groupInfo: groupInfo)
.onAppear {
searchFocussed = false
Task {
let groupMembers = await apiListMembers(groupInfo.groupId)
await MainActor.run {
chatModel.groupMembers = groupMembers.map { GMember.init($0) }
chatModel.populateGroupMembersIndexes()
}
}
}
addMembersDestinationView()
} label: {
Label("Invite members", systemImage: "plus")
}
}
private func addMembersDestinationView() -> some View {
AddGroupMembersView(chat: chat, groupInfo: groupInfo)
.onAppear {
searchFocussed = false
Task {
let groupMembers = await apiListMembers(groupInfo.groupId)
await MainActor.run {
chatModel.groupMembers = groupMembers.map { GMember.init($0) }
chatModel.populateGroupMembersIndexes()
}
}
}
}
private struct MemberRowView: View {
var groupInfo: GroupInfo
@ObservedObject var groupMember: GMember
@@ -226,7 +308,7 @@ struct GroupChatInfoView: View {
var body: some View {
let member = groupMember.wrapped
let v = HStack{
ProfileImage(imageStr: member.image, size: 38)
MemberProfileImage(member, size: 38)
.padding(.trailing, 2)
// TODO server connection status
VStack(alignment: .leading) {
@@ -352,16 +434,7 @@ struct GroupChatInfoView: View {
private func groupLinkButton() -> some View {
NavigationLink {
GroupLinkView(
groupId: groupInfo.groupId,
groupLink: $groupLink,
groupLinkMemberRole: $groupLinkMemberRole,
showTitle: false,
creatingGroup: false
)
.navigationBarTitle("Group link")
.modifier(ThemedBackground(grouped: true))
.navigationBarTitleDisplayMode(.large)
groupLinkDestinationView()
} label: {
if groupLink == nil {
Label("Create group link", systemImage: "link.badge.plus")
@@ -371,6 +444,19 @@ struct GroupChatInfoView: View {
}
}
private func groupLinkDestinationView() -> some View {
GroupLinkView(
groupId: groupInfo.groupId,
groupLink: $groupLink,
groupLinkMemberRole: $groupLinkMemberRole,
showTitle: false,
creatingGroup: false
)
.navigationBarTitle("Group link")
.modifier(ThemedBackground(grouped: true))
.navigationBarTitleDisplayMode(.large)
}
private func editGroupButton() -> some View {
NavigationLink {
GroupProfileView(
@@ -577,7 +663,8 @@ struct GroupChatInfoView_Previews: PreviewProvider {
static var previews: some View {
GroupChatInfoView(
chat: Chat(chatInfo: ChatInfo.sampleData.group, chatItems: []),
groupInfo: Binding.constant(GroupInfo.sampleData)
groupInfo: Binding.constant(GroupInfo.sampleData),
onSearch: {}
)
}
}
@@ -34,14 +34,13 @@ struct GroupLinkView: View {
var body: some View {
if creatingGroup {
NavigationView {
groupLinkView()
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button ("Continue") { linkCreatedCb?() }
}
groupLinkView()
.navigationBarBackButtonHidden()
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button ("Continue") { linkCreatedCb?() }
}
}
}
} else {
groupLinkView()
}
@@ -37,6 +37,7 @@ struct GroupMemberInfoView: View {
case syncConnectionForceAlert
case planAndConnectAlert(alert: PlanAndConnectAlert)
case queueInfo(info: String)
case someAlert(alert: SomeAlert)
case error(title: LocalizedStringKey, error: LocalizedStringKey?)
var id: String {
@@ -52,6 +53,7 @@ struct GroupMemberInfoView: View {
case .syncConnectionForceAlert: return "syncConnectionForceAlert"
case let .planAndConnectAlert(alert): return "planAndConnectAlert \(alert.id)"
case let .queueInfo(info): return "queueInfo \(info)"
case let .someAlert(alert): return "someAlert \(alert.id)"
case let .error(title, _): return "error \(title)"
}
}
@@ -65,10 +67,11 @@ struct GroupMemberInfoView: View {
}
}
private func knownDirectChat(_ contactId: Int64) -> Chat? {
private func knownDirectChat(_ contactId: Int64) -> (Chat, Contact)? {
if let chat = chatModel.getContactChat(contactId),
chat.chatInfo.contact?.directOrUsed == true {
return chat
let contact = chat.chatInfo.contact,
contact.directOrUsed == true {
return (chat, contact)
} else {
return nil
}
@@ -80,21 +83,22 @@ struct GroupMemberInfoView: View {
List {
groupMemberInfoHeader(member)
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
.padding(.bottom, 18)
infoActionButtons(member)
.padding(.horizontal)
.frame(maxWidth: .infinity)
.frame(height: infoViewActionButtonHeight)
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
if member.memberActive {
Section {
if let contactId = member.memberContactId, let chat = knownDirectChat(contactId) {
knownDirectChatButton(chat)
} else if groupInfo.fullGroupPreferences.directMessages.on(for: groupInfo.membership) {
if let contactId = member.memberContactId {
newDirectChatButton(contactId)
} else if member.activeConn?.peerChatVRange.isCompatibleRange(CREATE_MEMBER_CONTACT_VRANGE) ?? false {
createMemberContactButton()
}
}
if let code = connectionCode { verifyCodeButton(code) }
if let connStats = connectionStats,
connStats.ratchetSyncAllowed {
connStats.ratchetSyncAllowed {
synchronizeConnectionButton()
}
// } else if developerTools {
@@ -237,6 +241,7 @@ struct GroupMemberInfoView: View {
case .syncConnectionForceAlert: return syncConnectionForceAlert({ syncMemberConnection(force: true) })
case let .planAndConnectAlert(alert): return planAndConnectAlert(alert, dismiss: true)
case let .queueInfo(info): return queueInfoAlert(info)
case let .someAlert(a): return a.alert
case let .error(title, error): return mkAlert(title: title, message: error)
}
}
@@ -249,6 +254,57 @@ struct GroupMemberInfoView: View {
.modifier(ThemedBackground(grouped: true))
}
func infoActionButtons(_ member: GroupMember) -> some View {
GeometryReader { g in
let buttonWidth = g.size.width / 4
HStack(alignment: .center, spacing: 8) {
if let contactId = member.memberContactId, let (chat, contact) = knownDirectChat(contactId) {
knownDirectChatButton(chat, width: buttonWidth)
AudioCallButton(chat: chat, contact: contact, width: buttonWidth) { alert = .someAlert(alert: $0) }
VideoButton(chat: chat, contact: contact, width: buttonWidth) { alert = .someAlert(alert: $0) }
} else if groupInfo.fullGroupPreferences.directMessages.on(for: groupInfo.membership) {
if let contactId = member.memberContactId {
newDirectChatButton(contactId, width: buttonWidth)
} else if member.activeConn?.peerChatVRange.isCompatibleRange(CREATE_MEMBER_CONTACT_VRANGE) ?? false {
createMemberContactButton(width: buttonWidth)
}
InfoViewButton(image: "phone.fill", title: "call", disabledLook: true, width: buttonWidth) { showSendMessageToEnableCallsAlert()
}
InfoViewButton(image: "video.fill", title: "video", disabledLook: true, width: buttonWidth) { showSendMessageToEnableCallsAlert()
}
} else { // no known contact chat && directMessages are off
InfoViewButton(image: "message.fill", title: "message", disabledLook: true, width: buttonWidth) { showDirectMessagesProhibitedAlert("Can't message member")
}
InfoViewButton(image: "phone.fill", title: "call", disabledLook: true, width: buttonWidth) { showDirectMessagesProhibitedAlert("Can't call member")
}
InfoViewButton(image: "video.fill", title: "video", disabledLook: true, width: buttonWidth) { showDirectMessagesProhibitedAlert("Can't call member")
}
}
}
.frame(maxWidth: .infinity, alignment: .center)
}
}
func showSendMessageToEnableCallsAlert() {
alert = .someAlert(alert: SomeAlert(
alert: mkAlert(
title: "Can't call member",
message: "Send message to enable calls."
),
id: "can't call member, send message"
))
}
func showDirectMessagesProhibitedAlert(_ title: LocalizedStringKey) {
alert = .someAlert(alert: SomeAlert(
alert: mkAlert(
title: title,
message: "Direct messages between members are prohibited in this group."
),
id: "can't message member, direct messages prohibited"
))
}
func connectViaAddressButton(_ contactLink: String) -> some View {
Button {
planAndConnect(
@@ -263,19 +319,17 @@ struct GroupMemberInfoView: View {
}
}
func knownDirectChatButton(_ chat: Chat) -> some View {
Button {
func knownDirectChatButton(_ chat: Chat, width: CGFloat) -> some View {
InfoViewButton(image: "message.fill", title: "message", width: width) {
dismissAllSheets(animated: true)
DispatchQueue.main.async {
chatModel.chatId = chat.id
}
} label: {
Label("Send direct message", systemImage: "message")
}
}
func newDirectChatButton(_ contactId: Int64) -> some View {
Button {
func newDirectChatButton(_ contactId: Int64, width: CGFloat) -> some View {
InfoViewButton(image: "message.fill", title: "message", width: width) {
do {
let chat = try apiGetChat(type: .direct, id: contactId)
chatModel.addChat(chat)
@@ -286,13 +340,11 @@ struct GroupMemberInfoView: View {
} catch let error {
logger.error("openDirectChatButton apiGetChat error: \(responseError(error))")
}
} label: {
Label("Send direct message", systemImage: "message")
}
}
func createMemberContactButton() -> some View {
Button {
func createMemberContactButton(width: CGFloat) -> some View {
InfoViewButton(image: "message.fill", title: "message", width: width) {
progressIndicator = true
Task {
do {
@@ -313,14 +365,12 @@ struct GroupMemberInfoView: View {
}
}
}
} label: {
Label("Send direct message", systemImage: "message")
}
}
private func groupMemberInfoHeader(_ mem: GroupMember) -> some View {
VStack {
ProfileImage(imageStr: mem.image, size: 192, color: Color(uiColor: .tertiarySystemFill))
MemberProfileImage(mem, size: 192, color: Color(uiColor: .tertiarySystemFill))
.padding(.top, 12)
.padding()
if mem.verified {
@@ -582,6 +632,21 @@ struct GroupMemberInfoView: View {
}
}
func MemberProfileImage(
_ mem: GroupMember,
size: CGFloat,
color: Color = Color(uiColor: .tertiarySystemGroupedBackground),
backgroundColor: Color? = nil
) -> some View {
ProfileImage(
imageStr: mem.image,
size: size,
color: color,
backgroundColor: backgroundColor,
blurred: mem.blocked
)
}
func blockMemberAlert(_ gInfo: GroupInfo, _ mem: GroupMember) -> Alert {
Alert(
title: Text("Block member?"),
+5 -5
View File
@@ -33,7 +33,7 @@ struct ReverseList<Item: Identifiable & Hashable & Sendable, Content: View>: UIV
case let .item(id):
controller.scroll(to: items.firstIndex(where: { $0.id == id }), position: .bottom)
case .bottom:
controller.scroll(to: .zero, position: .top)
controller.scroll(to: 0, position: .top)
}
} else {
controller.update(items: items)
@@ -45,7 +45,7 @@ struct ReverseList<Item: Identifiable & Hashable & Sendable, Content: View>: UIV
private enum Section { case main }
private let representer: ReverseList
private var dataSource: UITableViewDiffableDataSource<Section, Item>!
private var itemCount: Int = .zero
private var itemCount: Int = 0
private var bag = Set<AnyCancellable>()
init(representer: ReverseList) {
@@ -80,7 +80,7 @@ struct ReverseList<Item: Identifiable & Hashable & Sendable, Content: View>: UIV
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath)
if #available(iOS 16.0, *) {
cell.contentConfiguration = UIHostingConfiguration { self.representer.content(item) }
.margins(.all, .zero)
.margins(.all, 0)
.minSize(height: 1) // Passing zero will result in system default of 44 points being used
} else {
if let cell = cell as? HostingCell<Content> {
@@ -153,7 +153,7 @@ struct ReverseList<Item: Identifiable & Hashable & Sendable, Content: View>: UIV
animated = true
}
tableView.scrollToRow(
at: IndexPath(row: index, section: .zero),
at: IndexPath(row: index, section: 0),
at: position,
animated: animated
)
@@ -168,7 +168,7 @@ struct ReverseList<Item: Identifiable & Hashable & Sendable, Content: View>: UIV
dataSource.defaultRowAnimation = .none
dataSource.apply(
snapshot,
animatingDifferences: itemCount != .zero && abs(items.count - itemCount) == 1
animatingDifferences: itemCount != 0 && abs(items.count - itemCount) == 1
)
itemCount = items.count
}
@@ -0,0 +1,130 @@
//
// SelectableChatItemToolbars.swift
// SimpleX (iOS)
//
// Created by Stanislav Dmitrenko on 30.07.2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
import SwiftUI
import SimpleXChat
struct SelectedItemsTopToolbar: View {
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
@Binding var selectedChatItems: Set<Int64>?
var body: some View {
let count = selectedChatItems?.count ?? 0
return Text(count == 0 ? "Nothing selected" : "Selected \(count)").font(.headline)
.foregroundColor(theme.colors.onBackground)
.frame(width: 220)
}
}
struct SelectedItemsBottomToolbar: View {
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
let chatItems: [ChatItem]
@Binding var selectedChatItems: Set<Int64>?
var chatInfo: ChatInfo
// Bool - delete for everyone is possible
var deleteItems: (Bool) -> Void
var moderateItems: () -> Void
//var shareItems: () -> Void
@State var deleteEnabled: Bool = false
@State var deleteForEveryoneEnabled: Bool = false
@State var canModerate: Bool = false
@State var moderateEnabled: Bool = false
@State var allButtonsDisabled = false
var body: some View {
VStack(spacing: 0) {
Divider()
HStack(alignment: .center) {
Button {
deleteItems(deleteForEveryoneEnabled)
} label: {
Image(systemName: "trash")
.resizable()
.frame(width: 20, height: 20, alignment: .center)
.foregroundColor(!deleteEnabled || allButtonsDisabled ? theme.colors.secondary: .red)
}
.disabled(!deleteEnabled || allButtonsDisabled)
Spacer()
Button {
moderateItems()
} label: {
Image(systemName: "flag")
.resizable()
.frame(width: 20, height: 20, alignment: .center)
.foregroundColor(!moderateEnabled || allButtonsDisabled ? theme.colors.secondary : .red)
}
.disabled(!moderateEnabled || allButtonsDisabled)
.opacity(canModerate ? 1 : 0)
Spacer()
Button {
//shareItems()
} label: {
Image(systemName: "square.and.arrow.up")
.resizable()
.frame(width: 20, height: 20, alignment: .center)
.foregroundColor(allButtonsDisabled ? theme.colors.secondary : theme.colors.primary)
}
.disabled(allButtonsDisabled)
.opacity(0)
}
.frame(maxHeight: .infinity)
.padding([.leading, .trailing], 12)
}
.onAppear {
recheckItems(chatInfo, chatItems, selectedChatItems)
}
.onChange(of: chatInfo) { info in
recheckItems(info, chatItems, selectedChatItems)
}
.onChange(of: chatItems) { items in
recheckItems(chatInfo, items, selectedChatItems)
}
.onChange(of: selectedChatItems) { selected in
recheckItems(chatInfo, chatItems, selected)
}
.frame(height: 55.5)
.background(.thinMaterial)
}
private func recheckItems(_ chatInfo: ChatInfo, _ chatItems: [ChatItem], _ selectedItems: Set<Int64>?) {
let count = selectedItems?.count ?? 0
allButtonsDisabled = count == 0 || count > 20
canModerate = possibleToModerate(chatInfo)
if let selected = selectedItems {
(deleteEnabled, deleteForEveryoneEnabled, moderateEnabled, _, selectedChatItems) = chatItems.reduce((true, true, true, true, [])) { (r, ci) in
if selected.contains(ci.id) {
var (de, dee, me, onlyOwnGroupItems, sel) = r
de = de && ci.canBeDeletedForSelf
dee = dee && ci.meta.deletable && !ci.localNote
onlyOwnGroupItems = onlyOwnGroupItems && ci.chatDir == .groupSnd
me = me && !onlyOwnGroupItems && ci.content.msgContent != nil && ci.memberToModerate(chatInfo) != nil
sel.insert(ci.id) // we are collecting new selected items here to account for any changes in chat items list
return (de, dee, me, onlyOwnGroupItems, sel)
} else {
return r
}
}
}
}
private func possibleToModerate(_ chatInfo: ChatInfo) -> Bool {
return switch chatInfo {
case let .group(groupInfo):
groupInfo.membership.memberRole >= .admin
default: false
}
}
}
@@ -11,7 +11,6 @@ import SwiftUI
struct ChatHelp: View {
@EnvironmentObject var chatModel: ChatModel
@Binding var showSettings: Bool
@State private var newChatMenuOption: NewChatMenuOption? = nil
var body: some View {
ScrollView { chatHelp() }
@@ -39,7 +38,7 @@ struct ChatHelp: View {
HStack(spacing: 8) {
Text("Tap button ")
NewChatMenuButton(newChatMenuOption: $newChatMenuOption)
NewChatMenuButton()
Text("above, then choose:")
}
@@ -9,45 +9,55 @@
import SwiftUI
import SimpleXChat
let dynamicSizes: [
DynamicTypeSize: (
rowHeight: CGFloat,
profileImageSize: CGFloat,
mediaSize: CGFloat,
chatInfoSize: CGFloat,
unreadCorner: CGFloat,
unreadPadding: CGFloat
)
] = [
.xSmall: (68, 55, 33, 18, 9, 3),
.small: (72, 57, 34, 18, 9, 3),
.medium: (76, 60, 36, 18, 10, 4),
.large: (80, 63, 38, 20, 10, 4),
.xLarge: (88, 67, 41, 20, 10, 4),
.xxLarge: (94, 71, 44, 22, 11, 4),
.xxxLarge: (104, 75, 48, 24, 12, 5),
.accessibility1: (104, 75, 48, 24, 12, 5),
.accessibility2: (114, 75, 48, 24, 12, 5),
.accessibility3: (124, 75, 48, 24, 12, 5),
.accessibility4: (134, 75, 48, 24, 12, 5),
.accessibility5: (144, 75, 48, 24, 12, 5)
typealias DynamicSizes = (
rowHeight: CGFloat,
profileImageSize: CGFloat,
mediaSize: CGFloat,
incognitoSize: CGFloat,
chatInfoSize: CGFloat,
unreadCorner: CGFloat,
unreadPadding: CGFloat
)
private let dynamicSizes: [DynamicTypeSize: DynamicSizes] = [
.xSmall: (68, 55, 33, 22, 18, 9, 3),
.small: (72, 57, 34, 22, 18, 9, 3),
.medium: (76, 60, 36, 22, 18, 10, 4),
.large: (80, 63, 38, 24, 20, 10, 4),
.xLarge: (88, 67, 41, 24, 20, 10, 4),
.xxLarge: (100, 71, 44, 27, 22, 11, 4),
.xxxLarge: (110, 75, 48, 30, 24, 12, 5),
.accessibility1: (110, 75, 48, 30, 24, 12, 5),
.accessibility2: (114, 75, 48, 30, 24, 12, 5),
.accessibility3: (124, 75, 48, 30, 24, 12, 5),
.accessibility4: (134, 75, 48, 30, 24, 12, 5),
.accessibility5: (144, 75, 48, 30, 24, 12, 5)
]
private let defaultDynamicSizes: DynamicSizes = dynamicSizes[.large]!
func dynamicSize(_ font: DynamicTypeSize) -> DynamicSizes {
dynamicSizes[font] ?? defaultDynamicSizes
}
struct ChatListNavLink: View {
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme
@Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize
@AppStorage(GROUP_DEFAULT_ONE_HAND_UI, store: groupDefaults) private var oneHandUI = false
@ObservedObject var chat: Chat
@State private var showContactRequestDialog = false
@State private var showJoinGroupDialog = false
@State private var showContactConnectionInfo = false
@State private var showInvalidJSON = false
@State private var showDeleteContactActionSheet = false
@State private var alert: SomeAlert? = nil
@State private var actionSheet: SomeActionSheet? = nil
@State private var sheet: SomeSheet<AnyView>? = nil
@State private var showConnectContactViaAddressDialog = false
@State private var inProgress = false
@State private var progressByTimeout = false
var dynamicRowHeight: CGFloat { dynamicSizes[userFont]?.rowHeight ?? 80 }
var dynamicRowHeight: CGFloat { dynamicSize(userFont).rowHeight }
var body: some View {
Group {
@@ -76,17 +86,24 @@ struct ChatListNavLink: View {
}
}
}
@ViewBuilder private func contactNavLink(_ contact: Contact) -> some View {
Group {
if contact.activeConn == nil && contact.profile.contactLink != nil {
if contact.activeConn == nil && contact.profile.contactLink != nil && contact.active {
ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false))
.frame(height: dynamicRowHeight)
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button {
showDeleteContactActionSheet = true
deleteContactDialog(
chat,
contact,
dismissToChatList: false,
showAlert: { alert = $0 },
showActionSheet: { actionSheet = $0 },
showSheetContent: { sheet = $0 }
)
} label: {
Label("Delete", systemImage: "trash")
deleteLabel
}
.tint(.red)
}
@@ -104,44 +121,37 @@ struct ChatListNavLink: View {
.swipeActions(edge: .leading, allowsFullSwipe: true) {
markReadButton()
toggleFavoriteButton()
ToggleNtfsButton(chat: chat)
toggleNtfsButton(chat: chat)
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
if !chat.chatItems.isEmpty {
clearChatButton()
}
Button {
if contact.sndReady || !contact.active {
showDeleteContactActionSheet = true
} else {
AlertManager.shared.showAlert(deletePendingContactAlert(chat, contact))
}
deleteContactDialog(
chat,
contact,
dismissToChatList: false,
showAlert: { alert = $0 },
showActionSheet: { actionSheet = $0 },
showSheetContent: { sheet = $0 }
)
} label: {
Label("Delete", systemImage: "trash")
deleteLabel
}
.tint(.red)
}
.frame(height: dynamicRowHeight)
}
}
.actionSheet(isPresented: $showDeleteContactActionSheet) {
if contact.sndReady && contact.active {
return ActionSheet(
title: Text("Delete contact?\nThis cannot be undone!"),
buttons: [
.destructive(Text("Delete and notify contact")) { Task { await deleteChat(chat, notify: true) } },
.destructive(Text("Delete")) { Task { await deleteChat(chat, notify: false) } },
.cancel()
]
)
.alert(item: $alert) { $0.alert }
.actionSheet(item: $actionSheet) { $0.actionSheet }
.sheet(item: $sheet) {
if #available(iOS 16.0, *) {
$0.content
.presentationDetents([.fraction(0.4)])
} else {
return ActionSheet(
title: Text("Delete contact?\nThis cannot be undone!"),
buttons: [
.destructive(Text("Delete")) { Task { await deleteChat(chat) } },
.cancel()
]
)
$0.content
}
}
}
@@ -193,7 +203,7 @@ struct ChatListNavLink: View {
.swipeActions(edge: .leading, allowsFullSwipe: true) {
markReadButton()
toggleFavoriteButton()
ToggleNtfsButton(chat: chat)
toggleNtfsButton(chat: chat)
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
if !chat.chatItems.isEmpty {
@@ -234,7 +244,7 @@ struct ChatListNavLink: View {
await MainActor.run { inProgress = false }
}
} label: {
Label("Join", systemImage: chat.chatInfo.incognito ? "theatermasks" : "ipad.and.arrow.forward")
SwipeLabel("Join", systemImage: chat.chatInfo.incognito ? "theatermasks" : "ipad.and.arrow.forward", inverted: oneHandUI)
}
.tint(chat.chatInfo.incognito ? .indigo : theme.colors.primary)
}
@@ -244,14 +254,14 @@ struct ChatListNavLink: View {
Button {
Task { await markChatRead(chat) }
} label: {
Label("Read", systemImage: "checkmark")
SwipeLabel("Read", systemImage: "checkmark", inverted: oneHandUI)
}
.tint(theme.colors.primary)
} else {
Button {
Task { await markChatUnread(chat) }
} label: {
Label("Unread", systemImage: "circlebadge.fill")
SwipeLabel("Unread", systemImage: "circlebadge.fill", inverted: oneHandUI)
}
.tint(theme.colors.primary)
}
@@ -263,24 +273,36 @@ struct ChatListNavLink: View {
Button {
toggleChatFavorite(chat, favorite: false)
} label: {
Label("Unfav.", systemImage: "star.slash")
SwipeLabel("Unfav.", systemImage: "star.slash.fill", inverted: oneHandUI)
}
.tint(.green)
} else {
Button {
toggleChatFavorite(chat, favorite: true)
} label: {
Label("Favorite", systemImage: "star.fill")
SwipeLabel("Favorite", systemImage: "star.fill", inverted: oneHandUI)
}
.tint(.green)
}
}
@ViewBuilder private func toggleNtfsButton(chat: Chat) -> some View {
Button {
toggleNotifications(chat, enableNtfs: !chat.chatInfo.ntfsEnabled)
} label: {
if chat.chatInfo.ntfsEnabled {
SwipeLabel("Mute", systemImage: "speaker.slash.fill", inverted: oneHandUI)
} else {
SwipeLabel("Unmute", systemImage: "speaker.wave.2.fill", inverted: oneHandUI)
}
}
}
private func clearChatButton() -> some View {
Button {
AlertManager.shared.showAlert(clearChatAlert())
} label: {
Label("Clear", systemImage: "gobackward")
SwipeLabel("Clear", systemImage: "gobackward", inverted: oneHandUI)
}
.tint(Color.orange)
}
@@ -289,7 +311,7 @@ struct ChatListNavLink: View {
Button {
AlertManager.shared.showAlert(clearNoteFolderAlert())
} label: {
Label("Clear", systemImage: "gobackward")
SwipeLabel("Clear", systemImage: "gobackward", inverted: oneHandUI)
}
.tint(Color.orange)
}
@@ -298,7 +320,7 @@ struct ChatListNavLink: View {
Button {
AlertManager.shared.showAlert(leaveGroupAlert(groupInfo))
} label: {
Label("Leave", systemImage: "rectangle.portrait.and.arrow.right")
SwipeLabel("Leave", systemImage: "rectangle.portrait.and.arrow.right.fill", inverted: oneHandUI)
}
.tint(Color.yellow)
}
@@ -307,7 +329,7 @@ struct ChatListNavLink: View {
Button {
AlertManager.shared.showAlert(deleteGroupAlert(groupInfo))
} label: {
Label("Delete", systemImage: "trash")
deleteLabel
}
.tint(.red)
}
@@ -317,18 +339,18 @@ struct ChatListNavLink: View {
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button {
Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) }
} label: { Label("Accept", systemImage: "checkmark") }
} label: { SwipeLabel("Accept", systemImage: "checkmark", inverted: oneHandUI) }
.tint(theme.colors.primary)
Button {
Task { await acceptContactRequest(incognito: true, contactRequest: contactRequest) }
} label: {
Label("Accept incognito", systemImage: "theatermasks")
SwipeLabel("Accept incognito", systemImage: "theatermasks.fill", inverted: oneHandUI)
}
.tint(.indigo)
Button {
AlertManager.shared.showAlert(rejectContactRequestAlert(contactRequest))
} label: {
Label("Reject", systemImage: "multiply")
SwipeLabel("Reject", systemImage: "multiply.fill", inverted: oneHandUI)
}
.tint(.red)
}
@@ -349,14 +371,14 @@ struct ChatListNavLink: View {
AlertManager.shared.showAlertMsg(title: a.title, message: a.message)
})
} label: {
Label("Delete", systemImage: "trash")
deleteLabel
}
.tint(.red)
Button {
showContactConnectionInfo = true
} label: {
Label("Name", systemImage: "pencil")
SwipeLabel("Name", systemImage: "pencil", inverted: oneHandUI)
}
.tint(theme.colors.primary)
}
@@ -375,6 +397,10 @@ struct ChatListNavLink: View {
}
}
private var deleteLabel: some View {
SwipeLabel("Delete", systemImage: "trash.fill", inverted: oneHandUI)
}
private func deleteGroupAlert(_ groupInfo: GroupInfo) -> Alert {
Alert(
title: Text("Delete group?"),
@@ -423,28 +449,6 @@ struct ChatListNavLink: View {
)
}
private func rejectContactRequestAlert(_ contactRequest: UserContactRequest) -> Alert {
Alert(
title: Text("Reject contact request"),
message: Text("The sender will NOT be notified"),
primaryButton: .destructive(Text("Reject")) {
Task { await rejectContactRequest(contactRequest) }
},
secondaryButton: .cancel()
)
}
private func pendingContactAlert(_ chat: Chat, _ contact: Contact) -> Alert {
Alert(
title: Text("Contact is not connected yet!"),
message: Text("Your contact needs to be online for the connection to complete.\nYou can cancel this connection and remove the contact (and try later with a new link)."),
primaryButton: .cancel(),
secondaryButton: .destructive(Text("Delete Contact")) {
removePendingContact(chat, contact)
}
)
}
private func groupInvitationAcceptedAlert() -> Alert {
Alert(
title: Text("Joining group"),
@@ -452,30 +456,6 @@ struct ChatListNavLink: View {
)
}
private func deletePendingContactAlert(_ chat: Chat, _ contact: Contact) -> Alert {
Alert(
title: Text("Delete pending connection"),
message: Text("Your contact needs to be online for the connection to complete.\nYou can cancel this connection and remove the contact (and try later with a new link)."),
primaryButton: .destructive(Text("Delete")) {
removePendingContact(chat, contact)
},
secondaryButton: .cancel()
)
}
private func removePendingContact(_ chat: Chat, _ contact: Contact) {
Task {
do {
try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId)
DispatchQueue.main.async {
chatModel.removeChat(contact.id)
}
} catch let error {
logger.error("ChatListNavLink.removePendingContact apiDeleteChat error: \(responseError(error))")
}
}
}
private func invalidJSONPreview(_ json: String) -> some View {
Text("invalid chat data")
.foregroundColor(.red)
@@ -490,16 +470,28 @@ struct ChatListNavLink: View {
private func connectContactViaAddress_(_ contact: Contact, _ incognito: Bool) {
Task {
let ok = await connectContactViaAddress(contact.contactId, incognito)
let ok = await connectContactViaAddress(contact.contactId, incognito, showAlert: { AlertManager.shared.showAlert($0) })
if ok {
await MainActor.run {
chatModel.chatId = contact.id
}
AlertManager.shared.showAlert(connReqSentAlert(.contact))
}
}
}
}
func rejectContactRequestAlert(_ contactRequest: UserContactRequest) -> Alert {
Alert(
title: Text("Reject contact request"),
message: Text("The sender will NOT be notified"),
primaryButton: .destructive(Text("Reject")) {
Task { await rejectContactRequest(contactRequest) }
},
secondaryButton: .cancel()
)
}
func deleteContactConnectionAlert(_ contactConnection: PendingContactConnection, showError: @escaping (ErrorAlert) -> Void, success: @escaping () -> Void = {}) -> Alert {
Alert(
title: Text("Delete pending connection?"),
@@ -526,15 +518,14 @@ func deleteContactConnectionAlert(_ contactConnection: PendingContactConnection,
)
}
func connectContactViaAddress(_ contactId: Int64, _ incognito: Bool) async -> Bool {
func connectContactViaAddress(_ contactId: Int64, _ incognito: Bool, showAlert: (Alert) -> Void) async -> Bool {
let (contact, alert) = await apiConnectContactViaAddress(incognito: incognito, contactId: contactId)
if let alert = alert {
AlertManager.shared.showAlert(alert)
showAlert(alert)
return false
} else if let contact = contact {
await MainActor.run {
ChatModel.shared.updateContact(contact)
AlertManager.shared.showAlert(connReqSentAlert(.contact))
}
return true
}
+213 -139
View File
@@ -8,6 +8,9 @@
import SwiftUI
import SimpleXChat
import SwiftUIIntrospect
private weak var collectionView: UICollectionView?
struct ChatListView: View {
@EnvironmentObject var chatModel: ChatModel
@@ -18,11 +21,15 @@ struct ChatListView: View {
@State private var searchText = ""
@State private var searchShowingSimplexLink = false
@State private var searchChatFilteredBySimplexLink: String? = nil
@State private var newChatMenuOption: NewChatMenuOption? = nil
@State private var userPickerVisible = false
@State private var showConnectDesktop = false
@State private var isSearchExpanded = true
@State private var contentOffsetObservation: NSKeyValueObservation?
@AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false
@AppStorage(GROUP_DEFAULT_ONE_HAND_UI, store: groupDefaults) private var oneHandUI = true
@AppStorage(DEFAULT_ONE_HAND_UI_CARD_SHOWN) private var oneHandUICardShown = false
var body: some View {
if #available(iOS 16.0, *) {
@@ -33,21 +40,14 @@ struct ChatListView: View {
}
private var viewBody: some View {
ZStack(alignment: .topLeading) {
ZStack(alignment: oneHandUI ? .bottomLeading : .topLeading) {
NavStackCompat(
isActive: Binding(
get: { chatModel.chatId != nil },
set: { _ in }
),
destination: chatView
) {
VStack {
if chatModel.chats.isEmpty {
onboardingButtons()
}
chatListView
}
}
) { chatListView }
if userPickerVisible {
Rectangle().fill(.white.opacity(0.001)).onTapGesture {
withAnimation {
@@ -67,9 +67,13 @@ struct ChatListView: View {
}
private var chatListView: some View {
VStack {
withToolbar {
chatList
.background(theme.colors.background)
.navigationBarTitleDisplayMode(.inline)
.navigationBarHidden(searchMode || oneHandUI)
}
.scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center)
.onDisappear() { withAnimation { userPickerVisible = false } }
.refreshable {
AlertManager.shared.showAlert(Alert(
@@ -87,79 +91,151 @@ struct ChatListView: View {
secondaryButton: .cancel()
))
}
.listStyle(.plain)
.background(theme.colors.background)
.navigationBarTitleDisplayMode(.inline)
.navigationBarHidden(searchMode)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
let user = chatModel.currentUser ?? User.sampleData
ZStack(alignment: .topTrailing) {
ProfileImage(imageStr: user.image, size: 32, color: Color(uiColor: .quaternaryLabel))
.padding(.trailing, 4)
let allRead = chatModel.users
.filter { u in !u.user.activeUser && !u.user.hidden }
.allSatisfy { u in u.unreadCount == 0 }
if !allRead {
unreadBadge(size: 12)
}
.safeAreaInset(edge: .top) {
if oneHandUI {
Divider().background(Material.thin)
} else {
searchBar
}
}
.safeAreaInset(edge: .bottom) {
if oneHandUI { searchBar }
}
}
/// Used to always return full height search bar height
/// to avoid changing safe are insets during scrolling
private static var searchBarHeight: Double = .zero
@ViewBuilder
var searchBar: some View {
let isVisible = isSearchExpanded || searchFocussed
VStack(spacing: 0) {
if oneHandUI { Divider() }
ChatListSearchBar(
searchMode: $searchMode,
searchFocussed: $searchFocussed,
searchText: $searchText,
searchShowingSimplexLink: $searchShowingSimplexLink,
searchChatFilteredBySimplexLink: $searchChatFilteredBySimplexLink
)
.padding(8)
.overlay {
GeometryReader { proxy in
Self.searchBarHeight = proxy.size.height
return Color.clear
}
.onTapGesture {
if chatModel.users.filter({ u in u.user.activeUser || !u.user.hidden }).count > 1 {
withAnimation {
userPickerVisible.toggle()
}
}
.frame(height: isVisible ? Self.searchBarHeight : 0)
.opacity(isVisible ? 1 : 0)
if !oneHandUI { Divider() }
}
.background(Material.thin)
.frame(height: Self.searchBarHeight, alignment: oneHandUI ? .bottom : .top)
}
@ViewBuilder
func withToolbar(content: () -> some View) -> some View {
if #available(iOS 16.0, *) {
content()
.toolbarBackground(.hidden, for: .navigationBar)
.toolbarBackground(.hidden, for: .bottomBar)
.toolbar {
if oneHandUI {
bottomToolbar
} else {
showSettings = true
topToolbar
}
}
} else {
content().toolbar { topToolbar }
}
}
@ToolbarContentBuilder
var topToolbar: some ToolbarContent {
ToolbarItem(placement: .topBarLeading) { leadingToolbarItem }
ToolbarItem(placement: .principal) { principalToolbarItem }
ToolbarItem(placement: .topBarTrailing) { trailingToolbarItem }
}
@ToolbarContentBuilder
var bottomToolbar: some ToolbarContent {
ToolbarItem(placement: .bottomBar) {
HStack {
leadingToolbarItem
principalToolbarItem
trailingToolbarItem
}
ToolbarItem(placement: .principal) {
HStack(spacing: 4) {
Text("Chats")
.font(.headline)
SubsStatusIndicator()
}
.frame(maxWidth: .infinity, alignment: .center)
}
ToolbarItem(placement: .navigationBarTrailing) {
switch chatModel.chatRunning {
case .some(true): NewChatMenuButton(newChatMenuOption: $newChatMenuOption)
case .some(false): chatStoppedIcon()
case .none: EmptyView()
}
}
@ViewBuilder
var leadingToolbarItem: some View {
let user = chatModel.currentUser ?? User.sampleData
ZStack(alignment: .topTrailing) {
ProfileImage(imageStr: user.image, size: 32, color: Color(uiColor: .quaternaryLabel))
.padding(.trailing, 4)
let allRead = chatModel.users
.filter { u in !u.user.activeUser && !u.user.hidden }
.allSatisfy { u in u.unreadCount == 0 }
if !allRead {
unreadBadge(size: 12)
}
}
.onTapGesture {
if chatModel.users.filter({ u in u.user.activeUser || !u.user.hidden }).count > 1 {
withAnimation {
userPickerVisible.toggle()
}
} else {
showSettings = true
}
}
}
@ViewBuilder
var principalToolbarItem: some View {
HStack(spacing: 4) {
Text("Chats").font(.headline)
SubsStatusIndicator()
}
.frame(maxWidth: .infinity, alignment: .center)
}
@ViewBuilder
var trailingToolbarItem: some View {
switch chatModel.chatRunning {
case .some(true): NewChatMenuButton()
case .some(false): chatStoppedIcon()
case .none: EmptyView()
}
}
@ViewBuilder private var chatList: some View {
let cs = filteredChats()
ZStack {
VStack {
List {
if !chatModel.chats.isEmpty {
ChatListSearchBar(
searchMode: $searchMode,
searchFocussed: $searchFocussed,
searchText: $searchText,
searchShowingSimplexLink: $searchShowingSimplexLink,
searchChatFilteredBySimplexLink: $searchChatFilteredBySimplexLink
)
List {
if !oneHandUICardShown {
OneHandUICard()
.scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center)
.listRowSeparator(.hidden)
.listRowBackground(Color.clear)
.frame(maxWidth: .infinity)
}
ForEach(cs, id: \.viewId) { chat in
ChatListNavLink(chat: chat)
.padding(.trailing, -16)
.disabled(chatModel.chatRunning != true || chatModel.deletedChats.contains(chat.chatInfo.id))
.listRowBackground(Color.clear)
}
.offset(x: -8)
}
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)
}
.onChange(of: chatModel.chatId) { _ in
if chatModel.chatId == nil, let chatId = chatModel.chatToTop {
.introspect(.list, on: .iOS(.v16, .v17, .v18)) { setObservations(for: $0) }
.listStyle(.plain)
.onChange(of: chatModel.chatId) { chId in
if chId == nil, let chatId = chatModel.chatToTop {
chatModel.chatToTop = nil
chatModel.popChat(chatId)
}
@@ -168,8 +244,40 @@ struct ChatListView: View {
.onChange(of: chatModel.currentUser?.userId) { _ in
stopAudioPlayer()
}
// .onAppear {
// oneHandUICardShown = false
// }
if cs.isEmpty && !chatModel.chats.isEmpty {
Text("No filtered chats").foregroundColor(theme.colors.secondary)
Text("No filtered chats")
.scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center)
.foregroundColor(.secondary)
}
}
}
private func setObservations(for cv: UICollectionView) {
if collectionView != cv {
collectionView = cv
var scrollDistance: CGFloat = 0
contentOffsetObservation?.invalidate()
contentOffsetObservation = cv.observe(
\.contentOffset,
options: [.new, .old]
) { (cv, change) in
if let newOffset = change.newValue?.y,
let oldOffset = change.oldValue?.y {
let bottomOffset = cv.contentSize.height - cv.visibleSize.height - newOffset + cv.safeAreaInsets.bottom
// Show/Hide search bar when scrolled for more than `MAX` amount
if newOffset > .zero,
bottomOffset > 0 {
let MAX: CGFloat = 64
scrollDistance = min(max(scrollDistance + oldOffset - newOffset, -MAX), +MAX)
if (isSearchExpanded && scrollDistance == -MAX) ||
(!isSearchExpanded && scrollDistance == +MAX) {
withAnimation(.easeOut(duration: 0.15)) { isSearchExpanded.toggle() }
}
}
}
}
}
}
@@ -180,42 +288,6 @@ struct ChatListView: View {
.foregroundColor(theme.colors.primary)
}
private func onboardingButtons() -> some View {
VStack(alignment: .trailing, spacing: 0) {
Path { p in
p.move(to: CGPoint(x: 8, y: 0))
p.addLine(to: CGPoint(x: 16, y: 10))
p.addLine(to: CGPoint(x: 0, y: 10))
p.addLine(to: CGPoint(x: 8, y: 0))
}
.fill(theme.colors.primary)
.frame(width: 20, height: 10)
.padding(.trailing, 12)
connectButton("Tap to start a new chat") {
newChatMenuOption = .newContact
}
Spacer()
Text("You have no chats")
.foregroundColor(theme.colors.secondary)
.frame(maxWidth: .infinity)
}
.padding(.trailing, 6)
.frame(maxHeight: .infinity)
}
private func connectButton(_ label: LocalizedStringKey, action: @escaping () -> Void) -> some View {
Button(action: action) {
Text(label)
.padding(.vertical, 10)
.padding(.horizontal, 20)
}
.background(theme.colors.primary)
.foregroundColor(.white)
.clipShape(RoundedRectangle(cornerRadius: 16))
}
@ViewBuilder private func chatView() -> some View {
if let chatId = chatModel.chatId, let chat = chatModel.getChat(chatId) {
ChatView(chat: chat)
@@ -233,16 +305,20 @@ struct ChatListView: View {
} else {
let s = searchString()
return s == "" && !showUnreadAndFavorites
? chatModel.chats
? chatModel.chats.filter { chat in
!chat.chatInfo.chatDeleted && chatContactType(chat: chat) != ContactType.card
}
: chatModel.chats.filter { chat in
let cInfo = chat.chatInfo
switch cInfo {
case let .direct(contact):
return s == ""
? filtered(chat)
: (viewNameContains(cInfo, s) ||
contact.profile.displayName.localizedLowercase.contains(s) ||
contact.fullName.localizedLowercase.contains(s))
return !contact.chatDeleted && chatContactType(chat: chat) != ContactType.card && (
s == ""
? filtered(chat)
: (viewNameContains(cInfo, s) ||
contact.profile.displayName.localizedLowercase.contains(s) ||
contact.fullName.localizedLowercase.contains(s))
)
case let .group(gInfo):
return s == ""
? (filtered(chat) || gInfo.membership.memberStatus == .memInvited)
@@ -300,8 +376,9 @@ struct SubsStatusIndicator: View {
.onDisappear {
stopTimer()
}
.sheet(isPresented: $showServersSummary) {
.appSheet(isPresented: $showServersSummary) {
ServersSummaryView()
.environment(\EnvironmentValues.refresh as! WritableKeyPath<EnvironmentValues, RefreshAction?>, nil)
}
}
@@ -341,39 +418,36 @@ struct ChatListSearchBar: View {
@AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false
var body: some View {
VStack(spacing: 12) {
HStack(spacing: 12) {
HStack(spacing: 4) {
Image(systemName: "magnifyingglass")
TextField("Search or paste SimpleX link", text: $searchText)
.foregroundColor(searchShowingSimplexLink ? theme.colors.secondary : theme.colors.onBackground)
.disabled(searchShowingSimplexLink)
.focused($searchFocussed)
.frame(maxWidth: .infinity)
if !searchText.isEmpty {
Image(systemName: "xmark.circle.fill")
.onTapGesture {
searchText = ""
}
}
}
.padding(EdgeInsets(top: 7, leading: 7, bottom: 7, trailing: 7))
.foregroundColor(theme.colors.secondary)
.background(Color(.tertiarySystemFill))
.cornerRadius(10.0)
if searchFocussed {
Text("Cancel")
.foregroundColor(theme.colors.primary)
HStack(spacing: 12) {
HStack(spacing: 4) {
Image(systemName: "magnifyingglass")
TextField("Search or paste SimpleX link", text: $searchText)
.foregroundColor(searchShowingSimplexLink ? theme.colors.secondary : theme.colors.onBackground)
.disabled(searchShowingSimplexLink)
.focused($searchFocussed)
.frame(maxWidth: .infinity)
if !searchText.isEmpty {
Image(systemName: "xmark.circle.fill")
.onTapGesture {
searchText = ""
searchFocussed = false
}
} else if m.chats.count > 0 {
toggleFilterButton()
}
}
Divider()
.padding(EdgeInsets(top: 7, leading: 7, bottom: 7, trailing: 7))
.foregroundColor(theme.colors.secondary)
.background(Color(.tertiarySystemFill))
.cornerRadius(10.0)
if searchFocussed {
Text("Cancel")
.foregroundColor(theme.colors.primary)
.onTapGesture {
searchText = ""
searchFocussed = false
}
} else if m.chats.count > 0 {
toggleFilterButton()
}
}
.onChange(of: searchFocussed) { sf in
withAnimation { searchMode = sf }
@@ -22,14 +22,14 @@ struct ChatPreviewView: View {
@AppStorage(DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS) private var showChatPreviews = true
var dynamicMediaSize: CGFloat { dynamicSizes[userFont]?.mediaSize ?? 36 }
var dynamicChatInfoSize: CGFloat { dynamicSizes[userFont]?.chatInfoSize ?? 18 }
var dynamicMediaSize: CGFloat { dynamicSize(userFont).mediaSize }
var dynamicChatInfoSize: CGFloat { dynamicSize(userFont).chatInfoSize }
var body: some View {
let cItem = chat.chatItems.last
return HStack(spacing: 8) {
ZStack(alignment: .bottomTrailing) {
ChatInfoImage(chat: chat, size: dynamicSizes[userFont]?.profileImageSize ?? 63)
ChatInfoImage(chat: chat, size: dynamicSize(userFont).profileImageSize)
chatPreviewImageOverlayIcon()
.padding([.bottom, .trailing], 1)
}
@@ -77,7 +77,7 @@ struct ChatPreviewView: View {
checkActiveContentPreview(chat, ci, mc)
}
chatStatusImage()
.padding(.top, 26)
.padding(.top, dynamicChatInfoSize * 1.44)
.frame(maxWidth: .infinity, alignment: .trailing)
}
.frame(maxWidth: .infinity, alignment: .leading)
@@ -198,10 +198,10 @@ struct ChatPreviewView: View {
unreadCountText(s.unreadCount)
.font(userFont <= .xxxLarge ? .caption : .caption2)
.foregroundColor(.white)
.padding(.horizontal, dynamicSizes[userFont]?.unreadPadding ?? 4)
.padding(.horizontal, dynamicSize(userFont).unreadPadding)
.frame(minWidth: dynamicChatInfoSize, minHeight: dynamicChatInfoSize)
.background(chat.chatInfo.ntfsEnabled || chat.chatInfo.chatType == .local ? theme.colors.primary : theme.colors.secondary)
.cornerRadius(dynamicSizes[userFont]?.unreadCorner ?? 10)
.cornerRadius(dynamicSize(userFont).unreadCorner)
} else if !chat.chatInfo.ntfsEnabled && chat.chatInfo.chatType != .local {
Image(systemName: "speaker.slash.fill")
.resizable()
@@ -275,7 +275,7 @@ struct ChatPreviewView: View {
} else {
switch (chat.chatInfo) {
case let .direct(contact):
if contact.activeConn == nil && contact.profile.contactLink != nil {
if contact.activeConn == nil && contact.profile.contactLink != nil && contact.active {
chatPreviewInfoText("Tap to Connect")
.foregroundColor(theme.colors.primary)
} else if !contact.sndReady && contact.activeConn != nil {
@@ -372,41 +372,42 @@ struct ChatPreviewView: View {
}
@ViewBuilder private func chatStatusImage() -> some View {
let size = dynamicSize(userFont).incognitoSize
switch chat.chatInfo {
case let .direct(contact):
if contact.active && contact.activeConn != nil {
switch (chatModel.contactNetworkStatus(contact)) {
case .connected: incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary)
case .connected: incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size)
case .error:
Image(systemName: "exclamationmark.circle")
.resizable()
.scaledToFit()
.frame(width: 17, height: 17)
.frame(width: dynamicChatInfoSize, height: dynamicChatInfoSize)
.foregroundColor(theme.colors.secondary)
default:
ProgressView()
}
} else {
incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary)
incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size)
}
case .group:
if progressByTimeout {
ProgressView()
} else {
incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary)
incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size)
}
default:
incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary)
incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size)
}
}
}
@ViewBuilder func incognitoIcon(_ incognito: Bool, _ secondaryColor: Color) -> some View {
@ViewBuilder func incognitoIcon(_ incognito: Bool, _ secondaryColor: Color, size: CGFloat) -> some View {
if incognito {
Image(systemName: "theatermasks")
.resizable()
.scaledToFit()
.frame(width: 22, height: 22)
.frame(width: size, height: size)
.foregroundColor(secondaryColor)
} else {
EmptyView()
@@ -13,6 +13,7 @@ struct ContactConnectionView: View {
@EnvironmentObject var m: ChatModel
@ObservedObject var chat: Chat
@EnvironmentObject var theme: AppTheme
@Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize
@State private var localAlias = ""
@FocusState private var aliasTextFieldFocused: Bool
@State private var showContactConnectionInfo = false
@@ -62,7 +63,7 @@ struct ContactConnectionView: View {
ZStack(alignment: .topTrailing) {
Text(contactConnection.description)
.frame(maxWidth: .infinity, alignment: .leading)
incognitoIcon(contactConnection.incognito, theme.colors.secondary)
incognitoIcon(contactConnection.incognito, theme.colors.secondary, size: dynamicSize(userFont).incognitoSize)
.padding(.top, 26)
.frame(maxWidth: .infinity, alignment: .trailing)
}
@@ -18,7 +18,7 @@ struct ContactRequestView: View {
var body: some View {
HStack(spacing: 8) {
ChatInfoImage(chat: chat, size: dynamicSizes[userFont]?.profileImageSize ?? 63)
ChatInfoImage(chat: chat, size: dynamicSize(userFont).profileImageSize)
.padding(.leading, 4)
VStack(alignment: .leading, spacing: 0) {
HStack(alignment: .top) {
@@ -0,0 +1,52 @@
//
// OneHandUICard.swift
// SimpleX (iOS)
//
// Created by EP on 06/08/2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
import SwiftUI
import SimpleXChat
struct OneHandUICard: View {
@EnvironmentObject var theme: AppTheme
@Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize
@AppStorage(GROUP_DEFAULT_ONE_HAND_UI, store: groupDefaults) private var oneHandUI = true
@AppStorage(DEFAULT_ONE_HAND_UI_CARD_SHOWN) private var oneHandUICardShown = false
@State private var showOneHandUIAlert = false
var body: some View {
ZStack(alignment: .topTrailing) {
VStack(alignment: .leading, spacing: 8) {
Text("Toggle chat list:").font(.title3)
Toggle("Reachable chat toolbar", isOn: $oneHandUI)
}
Image(systemName: "multiply")
.foregroundColor(theme.colors.secondary)
.onTapGesture {
showOneHandUIAlert = true
}
}
.padding()
.background(theme.appColors.sentMessage)
.cornerRadius(12)
.frame(height: dynamicSize(userFont).rowHeight)
.padding(.vertical, 12)
.alert(isPresented: $showOneHandUIAlert) {
Alert(
title: Text("Reachable chat toolbar"),
message: Text("You can change it in Appearance settings."),
dismissButton: .default(Text("Ok")) {
withAnimation {
oneHandUICardShown = true
}
}
)
}
}
}
#Preview {
OneHandUICard()
}
@@ -448,19 +448,23 @@ func subscriptionStatusColorAndPercentage(_ online: Bool, _ onionHosts: OnionHos
let noConnColorAndPercent: (Color, Double, Double, Double) = (Color(uiColor: .tertiaryLabel), 1, 1, 0)
let activeSubsRounded = roundedToQuarter(subs.shareOfActive)
return online && subs.total > 0
? (
subs.ssActive == 0
? (
hasSess ? (activeColor, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) : noConnColorAndPercent
return !online
? noConnColorAndPercent
: (
subs.total == 0 && !hasSess
? (activeColor, 0, 0.33, 0) // On freshly installed app (without chats) and on app start
: (
subs.ssActive == 0
? (
hasSess ? (activeColor, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) : noConnColorAndPercent
)
: ( // ssActive > 0
hasSess
? (activeColor, activeSubsRounded, subs.shareOfActive, subs.shareOfActive)
: (.orange, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) // This would mean implementation error
)
)
: ( // ssActive > 0
hasSess
? (activeColor, activeSubsRounded, subs.shareOfActive, subs.shareOfActive)
: (.orange, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) // This would mean implementation error
)
)
: noConnColorAndPercent
}
struct SMPServerSummaryView: View {
@@ -0,0 +1,272 @@
//
// ContactListNavLink.swift
// SimpleX (iOS)
//
// Created by Diogo Cunha on 01/08/2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
import SwiftUI
import SimpleXChat
struct ContactListNavLink: View {
@EnvironmentObject var theme: AppTheme
@ObservedObject var chat: Chat
var showDeletedChatIcon: Bool
@State private var alert: SomeAlert? = nil
@State private var actionSheet: SomeActionSheet? = nil
@State private var sheet: SomeSheet<AnyView>? = nil
@State private var showConnectContactViaAddressDialog = false
@State private var showContactRequestDialog = false
var body: some View {
let contactType = chatContactType(chat: chat)
Group {
switch (chat.chatInfo) {
case let .direct(contact):
switch contactType {
case .recent:
recentContactNavLink(contact)
case .chatDeleted:
deletedChatNavLink(contact)
case .card:
contactCardNavLink(contact)
default:
EmptyView()
}
case let .contactRequest(contactRequest):
contactRequestNavLink(contactRequest)
default:
EmptyView()
}
}
.alert(item: $alert) { $0.alert }
.actionSheet(item: $actionSheet) { $0.actionSheet }
.sheet(item: $sheet) {
if #available(iOS 16.0, *) {
$0.content
.presentationDetents([.fraction(0.4)])
} else {
$0.content
}
}
}
func recentContactNavLink(_ contact: Contact) -> some View {
Button {
dismissAllSheets(animated: true) {
ChatModel.shared.chatId = contact.id
}
} label: {
contactPreview(contact, titleColor: theme.colors.onBackground)
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button {
deleteContactDialog(
chat,
contact,
dismissToChatList: false,
showAlert: { alert = $0 },
showActionSheet: { actionSheet = $0 },
showSheetContent: { sheet = $0 }
)
} label: {
Label("Delete", systemImage: "trash")
}
.tint(.red)
}
}
func deletedChatNavLink(_ contact: Contact) -> some View {
Button {
Task {
await MainActor.run {
var updatedContact = contact
updatedContact.chatDeleted = false
ChatModel.shared.updateContact(updatedContact)
dismissAllSheets(animated: true) {
ChatModel.shared.chatId = contact.id
}
}
}
} label: {
contactPreview(contact, titleColor: theme.colors.onBackground)
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button {
deleteContactDialog(
chat,
contact,
dismissToChatList: false,
showAlert: { alert = $0 },
showActionSheet: { actionSheet = $0 },
showSheetContent: { sheet = $0 }
)
} label: {
Label("Delete", systemImage: "trash")
}
.tint(.red)
}
}
func contactPreview(_ contact: Contact, titleColor: Color) -> some View {
HStack{
ProfileImage(imageStr: contact.image, size: 30)
previewTitle(contact, titleColor: titleColor)
Spacer()
HStack {
if showDeletedChatIcon && contact.chatDeleted {
Image(systemName: "archivebox")
.resizable()
.scaledToFit()
.frame(width: 18, height: 18)
.foregroundColor(.secondary.opacity(0.65))
} else if chat.chatInfo.chatSettings?.favorite ?? false {
Image(systemName: "star.fill")
.resizable()
.scaledToFill()
.frame(width: 18, height: 18)
.foregroundColor(.secondary.opacity(0.65))
}
if contact.contactConnIncognito {
Image(systemName: "theatermasks")
.resizable()
.scaledToFit()
.frame(width: 22, height: 22)
.foregroundColor(.secondary)
}
}
}
}
@ViewBuilder private func previewTitle(_ contact: Contact, titleColor: Color) -> some View {
let t = Text(chat.chatInfo.chatViewName).foregroundColor(titleColor)
(
contact.verified == true
? verifiedIcon + t
: t
)
.lineLimit(1)
}
private var verifiedIcon: Text {
(Text(Image(systemName: "checkmark.shield")) + Text(" "))
.foregroundColor(.secondary)
.baselineOffset(1)
.kerning(-2)
}
func contactCardNavLink(_ contact: Contact) -> some View {
Button {
showConnectContactViaAddressDialog = true
} label: {
contactCardPreview(contact)
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button {
deleteContactDialog(
chat,
contact,
dismissToChatList: false,
showAlert: { alert = $0 },
showActionSheet: { actionSheet = $0 },
showSheetContent: { sheet = $0 }
)
} label: {
Label("Delete", systemImage: "trash")
}
.tint(.red)
}
.confirmationDialog("Connect with \(contact.chatViewName)", isPresented: $showConnectContactViaAddressDialog, titleVisibility: .visible) {
Button("Use current profile") { connectContactViaAddress_(contact, false) }
Button("Use new incognito profile") { connectContactViaAddress_(contact, true) }
}
}
private func connectContactViaAddress_(_ contact: Contact, _ incognito: Bool) {
Task {
let ok = await connectContactViaAddress(contact.contactId, incognito, showAlert: { alert = SomeAlert(alert: $0, id: "ContactListNavLink connectContactViaAddress") })
if ok {
await MainActor.run {
ChatModel.shared.chatId = contact.id
}
DispatchQueue.main.async {
dismissAllSheets(animated: true) {
AlertManager.shared.showAlert(connReqSentAlert(.contact))
}
}
}
}
}
func contactCardPreview(_ contact: Contact) -> some View {
HStack{
ProfileImage(imageStr: contact.image, size: 30)
Text(chat.chatInfo.chatViewName)
.foregroundColor(.accentColor)
.lineLimit(1)
Spacer()
Image(systemName: "envelope")
.resizable()
.scaledToFill()
.frame(width: 14, height: 14)
.foregroundColor(.accentColor)
}
}
func contactRequestNavLink(_ contactRequest: UserContactRequest) -> some View {
Button {
showContactRequestDialog = true
} label: {
contactRequestPreview(contactRequest)
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button {
Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) }
} label: { Label("Accept", systemImage: "checkmark") }
.tint(theme.colors.primary)
Button {
Task { await acceptContactRequest(incognito: true, contactRequest: contactRequest) }
} label: {
Label("Accept incognito", systemImage: "theatermasks")
}
.tint(.indigo)
Button {
alert = SomeAlert(alert: rejectContactRequestAlert(contactRequest), id: "rejectContactRequestAlert")
} label: {
Label("Reject", systemImage: "multiply")
}
.tint(.red)
}
.confirmationDialog("Accept connection request?", isPresented: $showContactRequestDialog, titleVisibility: .visible) {
Button("Accept") { Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) } }
Button("Accept incognito") { Task { await acceptContactRequest(incognito: true, contactRequest: contactRequest) } }
Button("Reject (sender NOT notified)", role: .destructive) { Task { await rejectContactRequest(contactRequest) } }
}
}
func contactRequestPreview(_ contactRequest: UserContactRequest) -> some View {
HStack{
ProfileImage(imageStr: contactRequest.image, size: 30)
Text(chat.chatInfo.chatViewName)
.foregroundColor(.accentColor)
.lineLimit(1)
Spacer()
Image(systemName: "checkmark")
.resizable()
.scaledToFill()
.frame(width: 14, height: 14)
.foregroundColor(.accentColor)
}
}
}
@@ -15,6 +15,7 @@ enum DatabaseAlert: Identifiable {
case importArchive
case archiveImported
case archiveImportedWithErrors(archiveErrors: [ArchiveError])
case archiveExportedWithErrors(archivePath: URL, archiveErrors: [ArchiveError])
case deleteChat
case chatDeleted
case deleteLegacyDatabase
@@ -29,6 +30,7 @@ enum DatabaseAlert: Identifiable {
case .importArchive: return "importArchive"
case .archiveImported: return "archiveImported"
case .archiveImportedWithErrors: return "archiveImportedWithErrors"
case .archiveExportedWithErrors: return "archiveExportedWithErrors"
case .deleteChat: return "deleteChat"
case .chatDeleted: return "chatDeleted"
case .deleteLegacyDatabase: return "deleteLegacyDatabase"
@@ -265,10 +267,18 @@ struct DatabaseView: View {
title: Text("Chat database imported"),
message: Text("Restart the app to use imported chat database")
)
case .archiveImportedWithErrors:
case let .archiveImportedWithErrors(errs):
return Alert(
title: Text("Chat database imported"),
message: Text("Restart the app to use imported chat database") + Text("\n") + Text("Some non-fatal errors occurred during import - you may see Chat console for more details.")
message: Text("Restart the app to use imported chat database") + Text(verbatim: "\n\n") + Text("Some non-fatal errors occurred during import:") + archiveErrorsText(errs)
)
case let .archiveExportedWithErrors(archivePath, errs):
return Alert(
title: Text("Chat database exported"),
message: Text("You may save the exported archive.") + Text(verbatim: "\n\n") + Text("Some file(s) were not exported:") + archiveErrorsText(errs),
dismissButton: .default(Text("Continue")) {
showShareSheet(items: [archivePath])
}
)
case .deleteChat:
return Alert(
@@ -349,9 +359,16 @@ struct DatabaseView: View {
progressIndicator = true
Task {
do {
let archivePath = try await exportChatArchive()
showShareSheet(items: [archivePath])
await MainActor.run { progressIndicator = false }
let (archivePath, archiveErrors) = try await exportChatArchive()
if archiveErrors.isEmpty {
showShareSheet(items: [archivePath])
await MainActor.run { progressIndicator = false }
} else {
await MainActor.run {
alert = .archiveExportedWithErrors(archivePath: archivePath, archiveErrors: archiveErrors)
progressIndicator = false
}
}
} catch let error {
await MainActor.run {
alert = .error(title: "Error exporting chat database", error: responseError(error))
@@ -486,6 +503,17 @@ struct DatabaseView: View {
}
}
func archiveErrorsText(_ errs: [ArchiveError]) -> Text {
return Text("\n" + errs.map(showArchiveError).joined(separator: "\n"))
func showArchiveError(_ err: ArchiveError) -> String {
switch err {
case let .import(importError): importError
case let .fileError(file, fileError): "\(file): \(fileError)"
}
}
}
func stopChatAsync() async throws {
try await apiStopChat()
ChatReceiver.shared.stop()
@@ -190,7 +190,7 @@ struct MigrateToAppGroupView: View {
do {
try apiSaveAppSettings(settings: AppSettings.current.prepareForExport())
try? FileManager.default.createDirectory(at: getWallpaperDirectory(), withIntermediateDirectories: true)
try await apiExportArchive(config: config)
_ = try await apiExportArchive(config: config)
await MainActor.run { setV3DBMigration(.exported) }
} catch let error {
await MainActor.run {
@@ -222,7 +222,7 @@ struct MigrateToAppGroupView: View {
}
}
func exportChatArchive(_ storagePath: URL? = nil) async throws -> URL {
func exportChatArchive(_ storagePath: URL? = nil) async throws -> (URL, [ArchiveError]) {
let archiveTime = Date.now
let ts = archiveTime.ISO8601Format(Date.ISO8601FormatStyle(timeSeparator: .omitted))
let archiveName = "simplex-chat.\(ts).zip"
@@ -233,13 +233,13 @@ func exportChatArchive(_ storagePath: URL? = nil) async throws -> URL {
try apiSaveAppSettings(settings: AppSettings.current.prepareForExport())
}
try? FileManager.default.createDirectory(at: getWallpaperDirectory(), withIntermediateDirectories: true)
try await apiExportArchive(config: config)
let errs = try await apiExportArchive(config: config)
if storagePath == nil {
deleteOldArchive()
UserDefaults.standard.set(archiveName, forKey: DEFAULT_CHAT_ARCHIVE_NAME)
chatArchiveTimeDefault.set(archiveTime)
}
return archivePath
return (archivePath, errs)
}
func deleteOldArchive() {
@@ -9,8 +9,6 @@
import SwiftUI
import SimpleXChat
let defaultProfileImageCorner = 22.5
struct ProfileImage: View {
@EnvironmentObject var theme: AppTheme
var imageStr: String? = nil
@@ -18,11 +16,12 @@ struct ProfileImage: View {
var size: CGFloat
var color = Color(uiColor: .tertiarySystemGroupedBackground)
var backgroundColor: Color? = nil
var blurred = false
@AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var radius = defaultProfileImageCorner
var body: some View {
if let uiImage = UIImage(base64Encoded: imageStr) {
clipProfileImage(Image(uiImage: uiImage), size: size, radius: radius)
clipProfileImage(Image(uiImage: uiImage), size: size, radius: radius, blurred: blurred)
} else {
let c = color.asAnotherColorFromSecondaryVariant(theme)
Image(systemName: iconName)
@@ -34,26 +33,6 @@ struct ProfileImage: View {
}
}
private let squareToCircleRatio = 0.935
private let radiusFactor = (1 - squareToCircleRatio) / 50
@ViewBuilder func clipProfileImage(_ img: Image, size: CGFloat, radius: Double) -> some View {
let v = img.resizable()
if radius >= 50 {
v.frame(width: size, height: size).clipShape(Circle())
} else if radius <= 0 {
let sz = size * squareToCircleRatio
v.frame(width: sz, height: sz).padding((size - sz) / 2)
} else {
let sz = size * (squareToCircleRatio + radius * radiusFactor)
v.frame(width: sz, height: sz)
.clipShape(RoundedRectangle(cornerRadius: sz * radius / 100, style: .continuous))
.padding((size - sz) / 2)
}
}
extension Color {
func asAnotherColorFromSecondary(_ theme: AppTheme) -> Color {
return self
@@ -0,0 +1,80 @@
//
// SwipeLabel.swift
// SimpleX (iOS)
//
// Created by Levitating Pineapple on 06/08/2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
import SwiftUI
struct SwipeLabel: View {
private let text: String
private let systemImage: String
private let inverted: Bool
init(_ text: String, systemImage: String, inverted: Bool) {
self.text = text
self.systemImage = systemImage
self.inverted = inverted
}
var body: some View {
if inverted {
Image(
uiImage: SwipeActionView(
systemName: systemImage,
text: text
).snapshot(inverted: inverted)
)
} else {
Label(text, systemImage: systemImage)
}
}
private class SwipeActionView: UIView {
private let imageView = UIImageView()
private let label = UILabel()
private let fontSize: CGFloat
init(systemName: String, text: String) {
fontSize = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .subheadline).pointSize
super.init(frame: CGRect(x: 0, y: 0, width: 64, height: 32 + fontSize))
imageView.image = UIImage(systemName: systemName)
imageView.contentMode = .scaleAspectFit
label.text = NSLocalizedString(text, comment: "swipe action")
label.textAlignment = .center
label.font = UIFont.systemFont(ofSize: fontSize, weight: .medium)
addSubview(imageView)
addSubview(label)
}
override func layoutSubviews() {
imageView.frame = CGRect(
x: 20,
y: 0,
width: 24,
height: 24
)
label.frame = CGRect(
x: 0,
y: 32,
width: 64,
height: fontSize
)
}
@available(*, unavailable)
required init?(coder: NSCoder) { fatalError("not implemented") }
func snapshot(inverted: Bool) -> UIImage {
UIGraphicsImageRenderer(bounds: bounds).image { context in
if inverted {
context.cgContext.scaleBy(x: 1, y: -1)
context.cgContext.translateBy(x: 0, y: -bounds.height)
}
layer.render(in: context.cgContext)
}.withRenderingMode(.alwaysTemplate)
}
}
}
@@ -9,8 +9,8 @@
import SwiftUI
extension View {
@ViewBuilder func `if`<Content: View>(_ condition: @autoclosure () -> Bool, transform: (Self) -> Content) -> some View {
if condition() {
@ViewBuilder func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
if condition {
transform(self)
} else {
self
@@ -64,7 +64,7 @@ struct LocalAuthView: View {
deleteAppDatabaseAndFiles()
// Clear sensitive data on screen just in case app fails to hide its views while new database is created
m.chatId = nil
m.reversedChatItems = []
ItemsModel.shared.reversedChatItems = []
m.chats = []
m.users = []
_ = kcAppPassword.set(password)
@@ -32,6 +32,7 @@ private enum MigrateFromDeviceViewAlert: Identifiable {
case keychainError(_ title: LocalizedStringKey = "Keychain error")
case databaseError(_ title: LocalizedStringKey = "Database error", message: String)
case unknownError(_ title: LocalizedStringKey = "Unknown error", message: String)
case archiveExportedWithErrors(archivePath: URL, archiveErrors: [ArchiveError])
case error(title: LocalizedStringKey, error: String = "")
@@ -45,6 +46,7 @@ private enum MigrateFromDeviceViewAlert: Identifiable {
case .keychainError: return "keychainError"
case let .databaseError(title, message): return "\(title) \(message)"
case let .unknownError(title, message): return "\(title) \(message)"
case let .archiveExportedWithErrors(path, _): return "archiveExportedWithErrors \(path)"
case let .error(title, _): return "error \(title)"
}
@@ -166,6 +168,14 @@ struct MigrateFromDevice: View {
return Alert(title: Text(title), message: Text(message))
case let .unknownError(title, message):
return Alert(title: Text(title), message: Text(message))
case let .archiveExportedWithErrors(archivePath, errs):
return Alert(
title: Text("Chat database exported"),
message: Text("You may migrate the exported database.") + Text(verbatim: "\n\n") + Text("Some file(s) were not exported:") + archiveErrorsText(errs),
dismissButton: .default(Text("Continue")) {
Task { await uploadArchive(path: archivePath) }
}
)
case let .error(title, error):
return Alert(title: Text(title), message: Text(error))
}
@@ -449,15 +459,12 @@ struct MigrateFromDevice: View {
Task {
do {
try? FileManager.default.createDirectory(at: getMigrationTempFilesDirectory(), withIntermediateDirectories: true)
let archivePath = try await exportChatArchive(getMigrationTempFilesDirectory())
if let attrs = try? FileManager.default.attributesOfItem(atPath: archivePath.path),
let totalBytes = attrs[.size] as? Int64 {
await MainActor.run {
migrationState = .uploadProgress(uploadedBytes: 0, totalBytes: totalBytes, fileId: 0, archivePath: archivePath, ctrl: nil)
}
let (archivePath, errs) = try await exportChatArchive(getMigrationTempFilesDirectory())
if errs.isEmpty {
await uploadArchive(path: archivePath)
} else {
await MainActor.run {
alert = .error(title: "Exported file doesn't exist")
alert = .archiveExportedWithErrors(archivePath: archivePath, archiveErrors: errs)
migrationState = .uploadConfirmation
}
}
@@ -469,6 +476,20 @@ struct MigrateFromDevice: View {
}
}
}
private func uploadArchive(path archivePath: URL) async {
if let attrs = try? FileManager.default.attributesOfItem(atPath: archivePath.path),
let totalBytes = attrs[.size] as? Int64 {
await MainActor.run {
migrationState = .uploadProgress(uploadedBytes: 0, totalBytes: totalBytes, fileId: 0, archivePath: archivePath, ctrl: nil)
}
} else {
await MainActor.run {
alert = .error(title: "Exported file doesn't exist")
migrationState = .uploadConfirmation
}
}
}
private func initTemporaryDatabase() -> (chat_ctrl, User)? {
let (status, ctrl) = chatInitTemporaryDatabase(url: tempDatabaseUrl)
@@ -30,7 +30,7 @@ struct AddContactLearnMore: View {
}
.listRowBackground(Color.clear)
}
.modifier(ThemedBackground())
.modifier(ThemedBackground(grouped: true))
}
}
@@ -35,24 +35,28 @@ struct AddGroupView: View {
creatingGroup: true,
showFooterCounter: false
) { _ in
dismiss()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
m.chatId = groupInfo.id
dismissAllSheets(animated: true) {
m.chatId = groupInfo.id
}
}
}
.navigationBarTitleDisplayMode(.inline)
} else {
GroupLinkView(
groupId: groupInfo.groupId,
groupLink: $groupLink,
groupLinkMemberRole: $groupLinkMemberRole,
showTitle: true,
showTitle: false,
creatingGroup: true
) {
dismiss()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
m.chatId = groupInfo.id
dismissAllSheets(animated: true) {
m.chatId = groupInfo.id
}
}
}
.navigationBarTitle("Group link")
}
} else {
createGroupView().keyboardPadding()
@@ -62,13 +66,6 @@ struct AddGroupView: View {
func createGroupView() -> some View {
List {
Group {
Text("Create secret group")
.font(.largeTitle)
.bold()
.fixedSize(horizontal: false, vertical: true)
.padding(.bottom, 24)
.onTapGesture(perform: hideKeyboard)
ZStack(alignment: .center) {
ZStack(alignment: .topTrailing) {
ProfileImage(imageStr: profile.image, size: 128)
@@ -204,13 +201,14 @@ struct AddGroupView: View {
chat = c
}
} catch {
dismiss()
AlertManager.shared.showAlert(
Alert(
title: Text("Error creating group"),
message: Text(responseError(error))
dismissAllSheets(animated: true) {
AlertManager.shared.showAlert(
Alert(
title: Text("Error creating group"),
message: Text(responseError(error))
)
)
)
}
}
}
@@ -7,53 +7,454 @@
//
import SwiftUI
import SimpleXChat
enum NewChatMenuOption: Identifiable {
case newContact
case scanPaste
case newGroup
var id: Self { self }
enum ContactType: Int {
case card, request, recent, chatDeleted, unlisted
}
struct NewChatMenuButton: View {
@Binding var newChatMenuOption: NewChatMenuOption?
@State private var showNewChatSheet = false
@State private var alert: SomeAlert? = nil
@State private var globalAlert: SomeAlert? = nil
var body: some View {
Menu {
Button {
newChatMenuOption = .newContact
} label: {
Text("Add contact")
}
Button {
newChatMenuOption = .scanPaste
} label: {
Text("Scan / Paste link")
}
Button {
newChatMenuOption = .newGroup
} label: {
Text("Create group")
}
showNewChatSheet = true
} label: {
Image(systemName: "square.and.pencil")
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
}
.sheet(item: $newChatMenuOption) { opt in
switch opt {
case .newContact: NewChatView(selection: .invite)
case .scanPaste: NewChatView(selection: .connect, showQRCodeScanner: true)
case .newGroup: AddGroupView()
.appSheet(isPresented: $showNewChatSheet) {
NewChatSheet(alert: $alert)
.environment(\EnvironmentValues.refresh as! WritableKeyPath<EnvironmentValues, RefreshAction?>, nil)
.alert(item: $alert) { a in
return a.alert
}
}
// This is a workaround to show "Keep unused invitation" alert in both following cases:
// - on going back from NewChatView to NewChatSheet,
// - on dismissing NewChatMenuButton sheet while on NewChatView (skipping NewChatSheet)
.onChange(of: alert?.id) { a in
if !showNewChatSheet && alert != nil {
globalAlert = alert
alert = nil
}
}
.alert(item: $globalAlert) { a in
return a.alert
}
}
}
#Preview {
NewChatMenuButton(
newChatMenuOption: Binding.constant(nil)
)
private var indent: CGFloat = 36
struct NewChatSheet: View {
@EnvironmentObject var theme: AppTheme
@State private var baseContactTypes: [ContactType] = [.card, .request, .recent]
@EnvironmentObject var chatModel: ChatModel
@State private var searchMode = false
@FocusState var searchFocussed: Bool
@State private var searchText = ""
@State private var searchShowingSimplexLink = false
@State private var searchChatFilteredBySimplexLink: String? = nil
@Binding var alert: SomeAlert?
var body: some View {
NavigationView {
viewBody()
.navigationTitle("New Chat")
.navigationBarTitleDisplayMode(.large)
.navigationBarHidden(searchMode)
.modifier(ThemedBackground(grouped: true))
}
}
@ViewBuilder private func viewBody() -> some View {
List {
HStack {
ContactsListSearchBar(
searchMode: $searchMode,
searchFocussed: $searchFocussed,
searchText: $searchText,
searchShowingSimplexLink: $searchShowingSimplexLink,
searchChatFilteredBySimplexLink: $searchChatFilteredBySimplexLink
)
.frame(maxWidth: .infinity)
}
.listRowSeparator(.hidden)
.listRowBackground(Color.clear)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
if (searchText.isEmpty) {
Section {
NavigationLink {
NewChatView(selection: .invite, parentAlert: $alert)
.navigationTitle("New chat")
.modifier(ThemedBackground(grouped: true))
.navigationBarTitleDisplayMode(.large)
} label: {
Label("Add contact", systemImage: "link.badge.plus")
}
NavigationLink {
NewChatView(selection: .connect, showQRCodeScanner: true, parentAlert: $alert)
.navigationTitle("New chat")
.modifier(ThemedBackground(grouped: true))
.navigationBarTitleDisplayMode(.large)
} label: {
Label("Scan / Paste link", systemImage: "qrcode")
}
NavigationLink {
AddGroupView()
.navigationTitle("Create secret group")
.modifier(ThemedBackground(grouped: true))
.navigationBarTitleDisplayMode(.large)
} label: {
Label("Create group", systemImage: "person.2")
}
}
if (!filterContactTypes(chats: chatModel.chats, contactTypes: [.chatDeleted]).isEmpty) {
Section {
NavigationLink {
DeletedChats()
} label: {
newChatActionButton("archivebox", color: theme.colors.secondary) { Text("Deleted chats") }
}
}
}
}
ContactsList(
baseContactTypes: $baseContactTypes,
searchMode: $searchMode,
searchText: $searchText,
header: "Your Contacts",
searchFocussed: $searchFocussed,
searchShowingSimplexLink: $searchShowingSimplexLink,
searchChatFilteredBySimplexLink: $searchChatFilteredBySimplexLink,
showDeletedChatIcon: true
)
}
}
func newChatActionButton<Content : View>(_ icon: String, color: Color/* = .secondary*/, content: @escaping () -> Content) -> some View {
ZStack(alignment: .leading) {
Image(systemName: icon).frame(maxWidth: 24, maxHeight: 24, alignment: .center)
.symbolRenderingMode(.monochrome)
.foregroundColor(color)
content().foregroundColor(theme.colors.onBackground).padding(.leading, indent)
}
}
}
func chatContactType(chat: Chat) -> ContactType {
switch chat.chatInfo {
case .contactRequest:
return .request
case let .direct(contact):
if contact.activeConn == nil && contact.profile.contactLink != nil {
return .card
} else if contact.chatDeleted {
return .chatDeleted
} else if contact.contactStatus == .active {
return .recent
} else {
return .unlisted
}
default:
return .unlisted
}
}
private func filterContactTypes(chats: [Chat], contactTypes: [ContactType]) -> [Chat] {
return chats.filter { chat in
contactTypes.contains(chatContactType(chat: chat))
}
}
struct ContactsList: View {
@EnvironmentObject var theme: AppTheme
@EnvironmentObject var chatModel: ChatModel
@Binding var baseContactTypes: [ContactType]
@Binding var searchMode: Bool
@Binding var searchText: String
var header: String? = nil
@FocusState.Binding var searchFocussed: Bool
@Binding var searchShowingSimplexLink: Bool
@Binding var searchChatFilteredBySimplexLink: String?
var showDeletedChatIcon: Bool
@AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false
var body: some View {
let contactTypes = contactTypesSearchTargets(baseContactTypes: baseContactTypes, searchEmpty: searchText.isEmpty)
let contactChats = filterContactTypes(chats: chatModel.chats, contactTypes: contactTypes)
let filteredContactChats = filteredContactChats(
showUnreadAndFavorites: showUnreadAndFavorites,
searchShowingSimplexLink: searchShowingSimplexLink,
searchChatFilteredBySimplexLink: searchChatFilteredBySimplexLink,
searchText: searchText,
contactChats: contactChats
)
if !filteredContactChats.isEmpty {
Section(header: Group {
if let header = header {
Text(header)
.textCase(.uppercase)
.foregroundColor(theme.colors.secondary)
}
}
) {
ForEach(filteredContactChats, id: \.viewId) { chat in
ContactListNavLink(chat: chat, showDeletedChatIcon: showDeletedChatIcon)
.disabled(chatModel.chatRunning != true)
}
}
}
if filteredContactChats.isEmpty && !contactChats.isEmpty {
noResultSection(text: "No filtered contacts")
} else if contactChats.isEmpty {
noResultSection(text: "No contacts")
}
}
@ViewBuilder private func noResultSection(text: String) -> some View {
Section {
Text(text)
.foregroundColor(theme.colors.secondary)
.frame(maxWidth: .infinity, alignment: .center)
}
.listRowSeparator(.hidden)
.listRowBackground(Color.clear)
.listRowInsets(EdgeInsets(top: 7, leading: 0, bottom: 7, trailing: 0))
}
private func contactTypesSearchTargets(baseContactTypes: [ContactType], searchEmpty: Bool) -> [ContactType] {
if baseContactTypes.contains(.chatDeleted) || searchEmpty {
return baseContactTypes
} else {
return baseContactTypes + [.chatDeleted]
}
}
private func chatsByTypeComparator(chat1: Chat, chat2: Chat) -> Bool {
let chat1Type = chatContactType(chat: chat1)
let chat2Type = chatContactType(chat: chat2)
if chat1Type.rawValue < chat2Type.rawValue {
return true
} else if chat1Type.rawValue > chat2Type.rawValue {
return false
} else {
return chat2.chatInfo.chatTs < chat1.chatInfo.chatTs
}
}
private func filterChat(chat: Chat, searchText: String, showUnreadAndFavorites: Bool) -> Bool {
var meetsPredicate = true
let s = searchText.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
let cInfo = chat.chatInfo
if !searchText.isEmpty {
if (!cInfo.chatViewName.lowercased().contains(searchText.lowercased())) {
if case let .direct(contact) = cInfo {
meetsPredicate = contact.profile.displayName.lowercased().contains(s) || contact.fullName.lowercased().contains(s)
} else {
meetsPredicate = false
}
}
}
if showUnreadAndFavorites {
meetsPredicate = meetsPredicate && (cInfo.chatSettings?.favorite ?? false)
}
return meetsPredicate
}
func filteredContactChats(
showUnreadAndFavorites: Bool,
searchShowingSimplexLink: Bool,
searchChatFilteredBySimplexLink: String?,
searchText: String,
contactChats: [Chat]
) -> [Chat] {
let linkChatId = searchChatFilteredBySimplexLink
let s = searchShowingSimplexLink ? "" : searchText.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
let filteredChats: [Chat]
if let linkChatId = linkChatId {
filteredChats = contactChats.filter { $0.id == linkChatId }
} else {
filteredChats = contactChats.filter { chat in
filterChat(chat: chat, searchText: s, showUnreadAndFavorites: showUnreadAndFavorites)
}
}
return filteredChats.sorted(by: chatsByTypeComparator)
}
}
struct ContactsListSearchBar: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
@Binding var searchMode: Bool
@FocusState.Binding var searchFocussed: Bool
@Binding var searchText: String
@Binding var searchShowingSimplexLink: Bool
@Binding var searchChatFilteredBySimplexLink: String?
@State private var ignoreSearchTextChange = false
@State private var alert: PlanAndConnectAlert?
@State private var sheet: PlanAndConnectActionSheet?
@AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false
var body: some View {
HStack(spacing: 12) {
HStack(spacing: 4) {
Spacer()
.frame(width: 8)
Image(systemName: "magnifyingglass")
.resizable()
.scaledToFit()
.frame(width: 16, height: 16)
TextField("Search or paste SimpleX link", text: $searchText)
.foregroundColor(searchShowingSimplexLink ? theme.colors.secondary : theme.colors.onBackground)
.disabled(searchShowingSimplexLink)
.focused($searchFocussed)
.frame(maxWidth: .infinity)
if !searchText.isEmpty {
Image(systemName: "xmark.circle.fill")
.resizable()
.scaledToFit()
.frame(width: 16, height: 16)
.onTapGesture {
searchText = ""
}
}
}
.padding(EdgeInsets(top: 7, leading: 7, bottom: 7, trailing: 7))
.foregroundColor(theme.colors.secondary)
.background(theme.colors.isLight ? theme.colors.background : theme.colors.secondaryVariant)
.cornerRadius(10.0)
if searchFocussed {
Text("Cancel")
.foregroundColor(theme.colors.primary)
.onTapGesture {
searchText = ""
searchFocussed = false
}
} else if m.chats.count > 0 {
toggleFilterButton()
}
}
.onChange(of: searchFocussed) { sf in
withAnimation { searchMode = sf }
}
.onChange(of: searchText) { t in
if ignoreSearchTextChange {
ignoreSearchTextChange = false
} else {
if let link = strHasSingleSimplexLink(t.trimmingCharacters(in: .whitespaces)) { // if SimpleX link is pasted, show connection dialogue
searchFocussed = false
if case let .simplexLink(linkType, _, smpHosts) = link.format {
ignoreSearchTextChange = true
searchText = simplexLinkText(linkType, smpHosts)
}
searchShowingSimplexLink = true
searchChatFilteredBySimplexLink = nil
connect(link.text)
} else {
if t != "" { // if some other text is pasted, enter search mode
searchFocussed = true
}
searchShowingSimplexLink = false
searchChatFilteredBySimplexLink = nil
}
}
}
.alert(item: $alert) { a in
planAndConnectAlert(a, dismiss: true, cleanup: { searchText = "" })
}
.actionSheet(item: $sheet) { s in
planAndConnectActionSheet(s, dismiss: true, cleanup: { searchText = "" })
}
}
private func toggleFilterButton() -> some View {
ZStack {
Color.clear
.frame(width: 22, height: 22)
Image(systemName: showUnreadAndFavorites ? "line.3.horizontal.decrease.circle.fill" : "line.3.horizontal.decrease")
.resizable()
.scaledToFit()
.foregroundColor(showUnreadAndFavorites ? theme.colors.primary : theme.colors.secondary)
.frame(width: showUnreadAndFavorites ? 22 : 16, height: showUnreadAndFavorites ? 22 : 16)
.onTapGesture {
showUnreadAndFavorites = !showUnreadAndFavorites
}
}
}
private func connect(_ link: String) {
planAndConnect(
link,
showAlert: { alert = $0 },
showActionSheet: { sheet = $0 },
dismiss: true,
incognito: nil,
filterKnownContact: { searchChatFilteredBySimplexLink = $0.id }
)
}
}
struct DeletedChats: View {
@State private var baseContactTypes: [ContactType] = [.chatDeleted]
@State private var searchMode = false
@FocusState var searchFocussed: Bool
@State private var searchText = ""
@State private var searchShowingSimplexLink = false
@State private var searchChatFilteredBySimplexLink: String? = nil
var body: some View {
List {
ContactsListSearchBar(
searchMode: $searchMode,
searchFocussed: $searchFocussed,
searchText: $searchText,
searchShowingSimplexLink: $searchShowingSimplexLink,
searchChatFilteredBySimplexLink: $searchChatFilteredBySimplexLink
)
.listRowSeparator(.hidden)
.listRowBackground(Color.clear)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
.frame(maxWidth: .infinity)
ContactsList(
baseContactTypes: $baseContactTypes,
searchMode: $searchMode,
searchText: $searchText,
searchFocussed: $searchFocussed,
searchShowingSimplexLink: $searchShowingSimplexLink,
searchChatFilteredBySimplexLink: $searchChatFilteredBySimplexLink,
showDeletedChatIcon: false
)
}
.navigationTitle("Deleted chats")
.navigationBarTitleDisplayMode(.inline)
.navigationBarHidden(searchMode)
.modifier(ThemedBackground(grouped: true))
}
}
#Preview {
NewChatMenuButton()
}
+47 -31
View File
@@ -17,10 +17,19 @@ struct SomeAlert: Identifiable {
var id: String
}
struct SomeActionSheet: Identifiable {
var actionSheet: ActionSheet
var id: String
}
struct SomeSheet<Content: View>: Identifiable {
@ViewBuilder var content: Content
var id: String
}
private enum NewChatViewAlert: Identifiable {
case planAndConnectAlert(alert: PlanAndConnectAlert)
case newChatSomeAlert(alert: SomeAlert)
var id: String {
switch self {
case let .planAndConnectAlert(alert): return "planAndConnectAlert \(alert.id)"
@@ -47,22 +56,10 @@ struct NewChatView: View {
@State private var creatingConnReq = false
@State private var pastedLink: String = ""
@State private var alert: NewChatViewAlert?
@Binding var parentAlert: SomeAlert?
var body: some View {
VStack(alignment: .leading) {
HStack {
Text("New chat")
.font(.largeTitle)
.bold()
.fixedSize(horizontal: false, vertical: true)
Spacer()
InfoSheetButton {
AddContactLearnMore(showTitle: true)
}
}
.padding()
.padding(.top)
Picker("New chat", selection: $selection) {
Label("Add contact", systemImage: "link")
.tag(NewChatOption.invite)
@@ -88,6 +85,7 @@ struct NewChatView: View {
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.modifier(ThemedBackground(grouped: true))
.background(
// Rectangle is needed for swipe gesture to work on mostly empty views (creatingLinkProgressView and retryButton)
Rectangle()
@@ -110,6 +108,13 @@ struct NewChatView: View {
}
)
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
InfoSheetButton {
AddContactLearnMore(showTitle: true)
}
}
}
.modifier(ThemedBackground(grouped: true))
.onChange(of: invitationUsed) { used in
if used && !(m.showingInvitation?.connChatUsed ?? true) {
@@ -119,19 +124,22 @@ struct NewChatView: View {
.onDisappear {
if !(m.showingInvitation?.connChatUsed ?? true),
let conn = contactConnection {
AlertManager.shared.showAlert(Alert(
title: Text("Keep unused invitation?"),
message: Text("You can view invitation link again in connection details."),
primaryButton: .default(Text("Keep")) {},
secondaryButton: .destructive(Text("Delete")) {
Task {
await deleteChat(Chat(
chatInfo: .contactConnection(contactConnection: conn),
chatItems: []
))
parentAlert = SomeAlert(
alert: Alert(
title: Text("Keep unused invitation?"),
message: Text("You can view invitation link again in connection details."),
primaryButton: .default(Text("Keep")) {},
secondaryButton: .destructive(Text("Delete")) {
Task {
await deleteChat(Chat(
chatInfo: .contactConnection(contactConnection: conn),
chatItems: []
))
}
}
}
))
),
id: "keepUnusedInvitation"
)
}
m.showingInvitation = nil
}
@@ -837,7 +845,10 @@ private func connectContactViaAddress_(_ contact: Contact, dismiss: Bool, incogn
dismissAllSheets(animated: true)
}
}
_ = await connectContactViaAddress(contact.contactId, incognito)
let ok = await connectContactViaAddress(contact.contactId, incognito, showAlert: { AlertManager.shared.showAlert($0) })
if ok {
AlertManager.shared.showAlert(connReqSentAlert(.contact))
}
cleanup?()
}
}
@@ -961,8 +972,13 @@ func connReqSentAlert(_ type: ConnReqType) -> Alert {
)
}
#Preview {
NewChatView(
selection: .invite
)
struct NewChatView_Previews: PreviewProvider {
static var previews: some View {
@State var parentAlert: SomeAlert?
NewChatView(
selection: .invite,
parentAlert: $parentAlert
)
}
}
@@ -24,34 +24,114 @@ enum NetworkSettingsAlert: Identifiable {
}
struct AdvancedNetworkSettings: View {
@Environment(\.dismiss) var dismiss: DismissAction
@EnvironmentObject var theme: AppTheme
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
@AppStorage(DEFAULT_SHOW_SENT_VIA_RPOXY) private var showSentViaProxy = false
@State private var netCfg = NetCfg.defaults
@State private var currentNetCfg = NetCfg.defaults
@State private var cfgLoaded = false
@State private var enableKeepAlive = true
@State private var keepAliveOpts = KeepAliveOpts.defaults
@State private var showSettingsAlert: NetworkSettingsAlert?
@State private var onionHosts: OnionHosts = .no
@State private var showSaveDialog = false
var body: some View {
VStack {
List {
Section {
Button {
updateNetCfgView(NetCfg.defaults)
showSettingsAlert = .update
NavigationLink {
List {
Section {
SelectionListView(list: SMPProxyMode.values, selection: $netCfg.smpProxyMode) { mode in
netCfg.smpProxyMode = mode
}
} footer: {
Text(proxyModeInfo(netCfg.smpProxyMode))
.font(.callout)
.foregroundColor(theme.colors.secondary)
}
}
.navigationTitle("Private routing")
.modifier(ThemedBackground(grouped: true))
.navigationBarTitleDisplayMode(.inline)
} label: {
Text("Reset to defaults")
HStack {
Text("Private routing")
Spacer()
Text(netCfg.smpProxyMode.label)
}
}
.disabled(currentNetCfg == NetCfg.defaults)
Button {
updateNetCfgView(NetCfg.proxyDefaults)
showSettingsAlert = .update
NavigationLink {
List {
Section {
SelectionListView(list: SMPProxyFallback.values, selection: $netCfg.smpProxyFallback) { mode in
netCfg.smpProxyFallback = mode
}
.disabled(netCfg.smpProxyMode == .never)
} footer: {
Text(proxyFallbackInfo(netCfg.smpProxyFallback))
.font(.callout)
.foregroundColor(theme.colors.secondary)
}
}
.navigationTitle("Allow downgrade")
.modifier(ThemedBackground(grouped: true))
.navigationBarTitleDisplayMode(.inline)
} label: {
Text("Set timeouts for proxy/VPN")
HStack {
Text("Allow downgrade")
Spacer()
Text(netCfg.smpProxyFallback.label)
}
}
.disabled(currentNetCfg == NetCfg.proxyDefaults)
Toggle("Show message status", isOn: $showSentViaProxy)
} header: {
Text("Private message routing")
.foregroundColor(theme.colors.secondary)
} footer: {
VStack(alignment: .leading) {
Text("To protect your IP address, private routing uses your SMP servers to deliver messages.")
if showSentViaProxy {
Text("Show → on messages sent via private routing.")
}
}
.foregroundColor(theme.colors.secondary)
}
Section {
Picker("Use .onion hosts", selection: $onionHosts) {
ForEach(OnionHosts.values, id: \.self) { Text($0.text) }
}
.frame(height: 36)
} footer: {
Text(onionHostsInfo(onionHosts))
.foregroundColor(theme.colors.secondary)
}
.onChange(of: onionHosts) { hosts in
if hosts != OnionHosts(netCfg: currentNetCfg) {
let (hostMode, requiredHostMode) = hosts.hostMode
netCfg.hostMode = hostMode
netCfg.requiredHostMode = requiredHostMode
}
}
if developerTools {
Section {
Picker("Transport isolation", selection: $netCfg.sessionMode) {
ForEach(TransportSessionMode.values, id: \.self) { Text($0.text) }
}
.frame(height: 36)
} footer: {
Text(sessionModeInfo(netCfg.sessionMode))
.foregroundColor(theme.colors.secondary)
}
}
Section("TCP connection") {
timeoutSettingPicker("TCP connection timeout", selection: $netCfg.tcpConnectTimeout, values: [10_000000, 15_000000, 20_000000, 30_000000, 45_000000, 60_000000, 90_000000], label: secondsLabel)
timeoutSettingPicker("Protocol timeout", selection: $netCfg.tcpTimeout, values: [5_000000, 7_000000, 10_000000, 15_000000, 20_000000, 30_000000], label: secondsLabel)
timeoutSettingPicker("Protocol timeout per KB", selection: $netCfg.tcpTimeoutPerKb, values: [2_500, 5_000, 10_000, 15_000, 20_000, 30_000], label: secondsLabel)
@@ -72,24 +152,21 @@ struct AdvancedNetworkSettings: View {
}
.foregroundColor(theme.colors.secondary)
}
} header: {
Text("")
.foregroundColor(theme.colors.secondary)
} footer: {
HStack {
Button {
updateNetCfgView(currentNetCfg)
} label: {
Label("Revert", systemImage: "arrow.counterclockwise").font(.callout)
}
}
Section {
Button("Reset to defaults") {
updateNetCfgView(NetCfg.defaults)
}
.disabled(netCfg == NetCfg.defaults)
Spacer()
Button {
showSettingsAlert = .update
} label: {
Label("Save", systemImage: "checkmark").font(.callout)
}
Button("Set timeouts for proxy/VPN") {
updateNetCfgView(netCfg.withProxyTimeouts)
}
.disabled(netCfg.hasProxyTimeouts)
Button("Save and reconnect") {
showSettingsAlert = .update
}
.disabled(netCfg == currentNetCfg)
}
@@ -111,10 +188,10 @@ struct AdvancedNetworkSettings: View {
switch a {
case .update:
return Alert(
title: Text("Update network settings?"),
title: Text("Update settings?"),
message: Text("Updating settings will re-connect the client to all servers."),
primaryButton: .default(Text("Ok")) {
saveNetCfg()
_ = saveNetCfg()
},
secondaryButton: .cancel()
)
@@ -125,23 +202,43 @@ struct AdvancedNetworkSettings: View {
)
}
}
.modifier(BackButton(disabled: Binding.constant(false)) {
if netCfg == currentNetCfg {
dismiss()
cfgLoaded = false
} else {
showSaveDialog = true
}
})
.confirmationDialog("Update network settings?", isPresented: $showSaveDialog, titleVisibility: .visible) {
Button("Save and reconnect") {
if saveNetCfg() {
dismiss()
cfgLoaded = false
}
}
Button("Exit without saving") { dismiss() }
}
}
private func updateNetCfgView(_ cfg: NetCfg) {
netCfg = cfg
onionHosts = OnionHosts(netCfg: netCfg)
enableKeepAlive = netCfg.enableKeepAlive
keepAliveOpts = netCfg.tcpKeepAlive ?? KeepAliveOpts.defaults
}
private func saveNetCfg() {
private func saveNetCfg() -> Bool {
do {
try setNetworkConfig(netCfg)
currentNetCfg = netCfg
setNetCfg(netCfg)
return true
} catch let error {
let err = responseError(error)
showSettingsAlert = .error(err: err)
logger.error("\(err)")
return false
}
}
@@ -164,6 +261,38 @@ struct AdvancedNetworkSettings: View {
}
.frame(height: 36)
}
private func onionHostsInfo(_ hosts: OnionHosts) -> LocalizedStringKey {
switch hosts {
case .no: return "Onion hosts will not be used."
case .prefer: return "Onion hosts will be used when available.\nRequires compatible VPN."
case .require: return "Onion hosts will be **required** for connection.\nRequires compatible VPN."
}
}
private func sessionModeInfo(_ mode: TransportSessionMode) -> LocalizedStringKey {
switch mode {
case .user: return "A separate TCP connection will be used **for each chat profile you have in the app**."
case .entity: return "A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail."
}
}
private func proxyModeInfo(_ mode: SMPProxyMode) -> LocalizedStringKey {
switch mode {
case .always: return "Always use private routing."
case .unknown: return "Use private routing with unknown servers."
case .unprotected: return "Use private routing with unknown servers when IP address is not protected."
case .never: return "Do NOT use private routing."
}
}
private func proxyFallbackInfo(_ proxyFallback: SMPProxyFallback) -> LocalizedStringKey {
switch proxyFallback {
case .allow: return "Send messages directly when your or destination server does not support private routing."
case .allowProtected: return "Send messages directly when IP address is protected and your or destination server does not support private routing."
case .prohibit: return "Do NOT send messages directly, even if your or destination server does not support private routing."
}
}
}
struct AdvancedNetworkSettings_Previews: PreviewProvider {
@@ -28,7 +28,10 @@ extension AppSettings {
privacyAcceptImagesGroupDefault.set(val)
def.setValue(val, forKey: DEFAULT_PRIVACY_ACCEPT_IMAGES)
}
if let val = privacyLinkPreviews { def.setValue(val, forKey: DEFAULT_PRIVACY_LINK_PREVIEWS) }
if let val = privacyLinkPreviews {
privacyLinkPreviewsGroupDefault.set(val)
def.setValue(val, forKey: DEFAULT_PRIVACY_LINK_PREVIEWS)
}
if let val = privacyShowChatPreviews { def.setValue(val, forKey: DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS) }
if let val = privacySaveLastDraft { def.setValue(val, forKey: DEFAULT_PRIVACY_SAVE_LAST_DRAFT) }
if let val = privacyProtectScreen { def.setValue(val, forKey: DEFAULT_PRIVACY_PROTECT_SCREEN) }
@@ -45,11 +48,15 @@ extension AppSettings {
if let val = androidCallOnLockScreen { def.setValue(val.rawValue, forKey: ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN) }
if let val = iosCallKitEnabled { callKitEnabledGroupDefault.set(val) }
if let val = iosCallKitCallsInRecents { def.setValue(val, forKey: DEFAULT_CALL_KIT_CALLS_IN_RECENTS) }
if let val = uiProfileImageCornerRadius { def.setValue(val, forKey: DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) }
if let val = uiProfileImageCornerRadius {
profileImageCornerRadiusGroupDefault.set(val)
def.setValue(val, forKey: DEFAULT_PROFILE_IMAGE_CORNER_RADIUS)
}
if let val = uiColorScheme { def.setValue(val, forKey: DEFAULT_CURRENT_THEME) }
if let val = uiDarkColorScheme { def.setValue(val, forKey: DEFAULT_SYSTEM_DARK_THEME) }
if let val = uiCurrentThemeIds { def.setValue(val, forKey: DEFAULT_CURRENT_THEME_IDS) }
if let val = uiThemes { def.setValue(val.skipDuplicates(), forKey: DEFAULT_THEME_OVERRIDES) }
if let val = oneHandUI { groupDefaults.setValue(val, forKey: GROUP_DEFAULT_ONE_HAND_UI) }
}
public static var current: AppSettings {
@@ -76,11 +83,12 @@ extension AppSettings {
c.androidCallOnLockScreen = AppSettingsLockScreenCalls(rawValue: def.string(forKey: ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN)!)
c.iosCallKitEnabled = callKitEnabledGroupDefault.get()
c.iosCallKitCallsInRecents = def.bool(forKey: DEFAULT_CALL_KIT_CALLS_IN_RECENTS)
c.uiProfileImageCornerRadius = def.float(forKey: DEFAULT_PROFILE_IMAGE_CORNER_RADIUS)
c.uiProfileImageCornerRadius = def.double(forKey: DEFAULT_PROFILE_IMAGE_CORNER_RADIUS)
c.uiColorScheme = currentThemeDefault.get()
c.uiDarkColorScheme = systemDarkThemeDefault.get()
c.uiCurrentThemeIds = currentThemeIdsDefault.get()
c.uiThemes = themeOverridesDefault.get()
c.oneHandUI = groupDefaults.bool(forKey: GROUP_DEFAULT_ONE_HAND_UI)
return c
}
}
@@ -33,6 +33,7 @@ struct AppearanceSettings: View {
}()
@State private var darkModeTheme: String = UserDefaults.standard.string(forKey: DEFAULT_SYSTEM_DARK_THEME) ?? DefaultTheme.DARK.themeName
@AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var profileImageCornerRadius = defaultProfileImageCorner
@AppStorage(GROUP_DEFAULT_ONE_HAND_UI, store: groupDefaults) private var oneHandUI = true
@State var themeUserDestination: (Int64, ThemeModeOverrides?)? = {
if let currentUser = ChatModel.shared.currentUser, let uiThemes = currentUser.uiThemes, uiThemes.preferredMode(!CurrentColors.colors.isLight) != nil {
@@ -62,6 +63,10 @@ struct AppearanceSettings: View {
}
}
Section("Chat list") {
Toggle("Reachable chat toolbar", isOn: $oneHandUI)
}
Section {
ThemeDestinationPicker(themeUserDestination: $themeUserDestination, themeUserDest: themeUserDestination?.0, customizeThemeIsOpen: $customizeThemeIsOpen)
@@ -143,7 +148,8 @@ struct AppearanceSettings: View {
Text("Themes")
.foregroundColor(theme.colors.secondary)
}
.onChange(of: profileImageCornerRadius) { _ in
.onChange(of: profileImageCornerRadius) { cornerRadius in
profileImageCornerRadiusGroupDefault.set(cornerRadius)
saveThemeToDatabase(nil)
}
.onChange(of: colorMode) { mode in
@@ -33,9 +33,6 @@ struct DeveloperView: View {
} label: {
settingsRow("terminal", color: theme.colors.secondary) { Text("Chat console") }
}
settingsRow("internaldrive", color: theme.colors.secondary) {
Toggle("Confirm database upgrades", isOn: $confirmDatabaseUpgrades)
}
settingsRow("chevron.left.forwardslash.chevron.right", color: theme.colors.secondary) {
Toggle("Show developer options", isOn: $developerTools)
}
@@ -45,6 +42,16 @@ struct DeveloperView: View {
((developerTools ? Text("Show:") : Text("Hide:")) + Text(" ") + Text("Database IDs and Transport isolation option."))
.foregroundColor(theme.colors.secondary)
}
if developerTools {
Section {
settingsRow("internaldrive", color: theme.colors.secondary) {
Toggle("Confirm database upgrades", isOn: $confirmDatabaseUpgrades)
}
} header: {
Text("Developer options")
}
}
}
}
}
@@ -10,18 +10,10 @@ import SwiftUI
import SimpleXChat
private enum NetworkAlert: Identifiable {
case updateOnionHosts(hosts: OnionHosts)
case updateSessionMode(mode: TransportSessionMode)
case updateSMPProxyMode(proxyMode: SMPProxyMode)
case updateSMPProxyFallback(proxyFallback: SMPProxyFallback)
case error(err: String)
var id: String {
switch self {
case let .updateOnionHosts(hosts): return "updateOnionHosts \(hosts)"
case let .updateSessionMode(mode): return "updateSessionMode \(mode)"
case let .updateSMPProxyMode(proxyMode): return "updateSMPProxyMode \(proxyMode)"
case let .updateSMPProxyFallback(proxyFallback): return "updateSMPProxyFallback \(proxyFallback)"
case let .error(err): return "error \(err)"
}
}
@@ -30,16 +22,6 @@ private enum NetworkAlert: Identifiable {
struct NetworkAndServers: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
@AppStorage(DEFAULT_SHOW_SENT_VIA_RPOXY) private var showSentViaProxy = false
@State private var cfgLoaded = false
@State private var currentNetCfg = NetCfg.defaults
@State private var netCfg = NetCfg.defaults
@State private var onionHosts: OnionHosts = .no
@State private var sessionMode: TransportSessionMode = .user
@State private var proxyMode: SMPProxyMode = .never
@State private var proxyFallback: SMPProxyFallback = .allow
@State private var alert: NetworkAlert?
var body: some View {
VStack {
@@ -50,7 +32,7 @@ struct NetworkAndServers: View {
.navigationTitle("Your SMP servers")
.modifier(ThemedBackground(grouped: true))
} label: {
Text("SMP servers")
Text("Message servers")
}
NavigationLink {
@@ -58,24 +40,12 @@ struct NetworkAndServers: View {
.navigationTitle("Your XFTP servers")
.modifier(ThemedBackground(grouped: true))
} label: {
Text("XFTP servers")
}
Picker("Use .onion hosts", selection: $onionHosts) {
ForEach(OnionHosts.values, id: \.self) { Text($0.text) }
}
.frame(height: 36)
if developerTools {
Picker("Transport isolation", selection: $sessionMode) {
ForEach(TransportSessionMode.values, id: \.self) { Text($0.text) }
}
.frame(height: 36)
Text("Media & file servers")
}
NavigationLink {
AdvancedNetworkSettings()
.navigationTitle("Network settings")
.navigationTitle("Advanced settings")
.modifier(ThemedBackground(grouped: true))
} label: {
Text("Advanced network settings")
@@ -83,35 +53,6 @@ struct NetworkAndServers: View {
} header: {
Text("Messages & files")
.foregroundColor(theme.colors.secondary)
} footer: {
Text("Using .onion hosts requires compatible VPN provider.")
.foregroundColor(theme.colors.secondary)
}
Section {
Picker("Private routing", selection: $proxyMode) {
ForEach(SMPProxyMode.values, id: \.self) { Text($0.text) }
}
.frame(height: 36)
Picker("Allow downgrade", selection: $proxyFallback) {
ForEach(SMPProxyFallback.values, id: \.self) { Text($0.text) }
}
.disabled(proxyMode == .never)
.frame(height: 36)
Toggle("Show message status", isOn: $showSentViaProxy)
} header: {
Text("Private message routing")
.foregroundColor(theme.colors.secondary)
} footer: {
VStack(alignment: .leading) {
Text("To protect your IP address, private routing uses your SMP servers to deliver messages.")
if showSentViaProxy {
Text("Show → on messages sent via private routing.")
}
}
.foregroundColor(theme.colors.secondary)
}
Section(header: Text("Calls").foregroundColor(theme.colors.secondary)) {
@@ -133,147 +74,6 @@ struct NetworkAndServers: View {
}
}
}
.onAppear {
if cfgLoaded { return }
cfgLoaded = true
currentNetCfg = getNetCfg()
resetNetCfgView()
}
.onChange(of: onionHosts) { hosts in
if hosts != OnionHosts(netCfg: currentNetCfg) {
alert = .updateOnionHosts(hosts: hosts)
}
}
.onChange(of: sessionMode) { mode in
if mode != netCfg.sessionMode {
alert = .updateSessionMode(mode: mode)
}
}
.onChange(of: proxyMode) { mode in
if mode != netCfg.smpProxyMode {
alert = .updateSMPProxyMode(proxyMode: mode)
}
}
.onChange(of: proxyFallback) { fallbackMode in
if fallbackMode != netCfg.smpProxyFallback {
alert = .updateSMPProxyFallback(proxyFallback: fallbackMode)
}
}
.alert(item: $alert) { a in
switch a {
case let .updateOnionHosts(hosts):
return Alert(
title: Text("Update .onion hosts setting?"),
message: Text(onionHostsInfo(hosts)) + Text("\n") + Text("Updating this setting will re-connect the client to all servers."),
primaryButton: .default(Text("Ok")) {
let (hostMode, requiredHostMode) = hosts.hostMode
netCfg.hostMode = hostMode
netCfg.requiredHostMode = requiredHostMode
saveNetCfg()
},
secondaryButton: .cancel() {
resetNetCfgView()
}
)
case let .updateSessionMode(mode):
return Alert(
title: Text("Update transport isolation mode?"),
message: Text(sessionModeInfo(mode)) + Text("\n") + Text("Updating this setting will re-connect the client to all servers."),
primaryButton: .default(Text("Ok")) {
netCfg.sessionMode = mode
saveNetCfg()
},
secondaryButton: .cancel() {
resetNetCfgView()
}
)
case let .updateSMPProxyMode(proxyMode):
return Alert(
title: Text("Message routing mode"),
message: Text(proxyModeInfo(proxyMode)) + Text("\n") + Text("Updating this setting will re-connect the client to all servers."),
primaryButton: .default(Text("Ok")) {
netCfg.smpProxyMode = proxyMode
saveNetCfg()
},
secondaryButton: .cancel() {
resetNetCfgView()
}
)
case let .updateSMPProxyFallback(proxyFallback):
return Alert(
title: Text("Message routing fallback"),
message: Text(proxyFallbackInfo(proxyFallback)) + Text("\n") + Text("Updating this setting will re-connect the client to all servers."),
primaryButton: .default(Text("Ok")) {
netCfg.smpProxyFallback = proxyFallback
saveNetCfg()
},
secondaryButton: .cancel() {
resetNetCfgView()
}
)
case let .error(err):
return Alert(
title: Text("Error updating settings"),
message: Text(err)
)
}
}
}
private func saveNetCfg() {
do {
let def = netCfg.hostMode == .onionHost ? NetCfg.proxyDefaults : NetCfg.defaults
netCfg.tcpConnectTimeout = def.tcpConnectTimeout
netCfg.tcpTimeout = def.tcpTimeout
try setNetworkConfig(netCfg)
currentNetCfg = netCfg
setNetCfg(netCfg)
} catch let error {
let err = responseError(error)
resetNetCfgView()
alert = .error(err: err)
logger.error("\(err)")
}
}
private func resetNetCfgView() {
netCfg = currentNetCfg
onionHosts = OnionHosts(netCfg: netCfg)
sessionMode = netCfg.sessionMode
proxyMode = netCfg.smpProxyMode
proxyFallback = netCfg.smpProxyFallback
}
private func onionHostsInfo(_ hosts: OnionHosts) -> LocalizedStringKey {
switch hosts {
case .no: return "Onion hosts will not be used."
case .prefer: return "Onion hosts will be used when available. Requires enabling VPN."
case .require: return "Onion hosts will be required for connection. Requires enabling VPN."
}
}
private func sessionModeInfo(_ mode: TransportSessionMode) -> LocalizedStringKey {
switch mode {
case .user: return "A separate TCP connection will be used **for each chat profile you have in the app**."
case .entity: return "A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail."
}
}
private func proxyModeInfo(_ mode: SMPProxyMode) -> LocalizedStringKey {
switch mode {
case .always: return "Always use private routing."
case .unknown: return "Use private routing with unknown servers."
case .unprotected: return "Use private routing with unknown servers when IP address is not protected."
case .never: return "Do NOT use private routing."
}
}
private func proxyFallbackInfo(_ proxyFallback: SMPProxyFallback) -> LocalizedStringKey {
switch proxyFallback {
case .allow: return "Send messages directly when your or destination server does not support private routing."
case .allowProtected: return "Send messages directly when IP address is protected and your or destination server does not support private routing."
case .prohibit: return "Do NOT send messages directly, even if your or destination server does not support private routing."
}
}
}
@@ -70,6 +70,9 @@ struct PrivacySettings: View {
Section {
settingsRow("network", color: theme.colors.secondary) {
Toggle("Send link previews", isOn: $useLinkPreviews)
.onChange(of: useLinkPreviews) { linkPreviews in
privacyLinkPreviewsGroupDefault.set(linkPreviews)
}
}
settingsRow("message", color: theme.colors.secondary) {
Toggle("Show last messages", isOn: $showChatPreviews)
@@ -365,6 +368,7 @@ struct SimplexLockView: View {
@State private var selfDestruct: Bool = UserDefaults.standard.bool(forKey: DEFAULT_LA_SELF_DESTRUCT)
@State private var currentSelfDestruct: Bool = UserDefaults.standard.bool(forKey: DEFAULT_LA_SELF_DESTRUCT)
@AppStorage(DEFAULT_LA_SELF_DESTRUCT_DISPLAY_NAME) private var selfDestructDisplayName = ""
@AppStorage(GROUP_DEFAULT_ALLOW_SHARE_EXTENSION, store: groupDefaults) private var allowShareExtension = false
@State private var performLAToggleReset = false
@State private var performLAModeReset = false
@State private var performLASelfDestructReset = false
@@ -436,6 +440,12 @@ struct SimplexLockView: View {
}
}
if performLA {
Section("Share to SimpleX") {
Toggle("Allow sharing", isOn: $allowShareExtension)
}
}
if performLA && laMode == .passcode {
Section(header: Text("Self-destruct passcode").foregroundColor(theme.colors.secondary)) {
Toggle(isOn: $selfDestruct) {
@@ -460,6 +470,7 @@ struct SimplexLockView: View {
}
}
.onChange(of: performLA) { performLAToggle in
performLAGroupDefault.set(performLAToggle)
prefLANoticeShown = true
if performLAToggleReset {
performLAToggleReset = false
@@ -130,7 +130,7 @@ struct ProtocolServersView: View {
showSaveDialog = true
}
})
.confirmationDialog("Save servers?", isPresented: $showSaveDialog) {
.confirmationDialog("Save servers?", isPresented: $showSaveDialog, titleVisibility: .visible) {
Button("Save") {
saveServers()
dismiss()
@@ -18,7 +18,7 @@ let appBuild = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as?
let DEFAULT_SHOW_LA_NOTICE = "showLocalAuthenticationNotice"
let DEFAULT_LA_NOTICE_SHOWN = "localAuthenticationNoticeShown"
let DEFAULT_PERFORM_LA = "performLocalAuthentication"
let DEFAULT_PERFORM_LA = "performLocalAuthentication" // deprecated, moved to app group
let DEFAULT_LA_MODE = "localAuthenticationMode"
let DEFAULT_LA_LOCK_DELAY = "localAuthenticationLockDelay"
let DEFAULT_LA_SELF_DESTRUCT = "localAuthenticationSelfDestruct"
@@ -28,7 +28,7 @@ let DEFAULT_WEBRTC_POLICY_RELAY = "webrtcPolicyRelay"
let DEFAULT_WEBRTC_ICE_SERVERS = "webrtcICEServers"
let DEFAULT_CALL_KIT_CALLS_IN_RECENTS = "callKitCallsInRecents"
let DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages" // unused. Use GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES instead
let DEFAULT_PRIVACY_LINK_PREVIEWS = "privacyLinkPreviews"
let DEFAULT_PRIVACY_LINK_PREVIEWS = "privacyLinkPreviews" // deprecated, moved to app group
let DEFAULT_PRIVACY_SIMPLEX_LINK_MODE = "privacySimplexLinkMode"
let DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS = "privacyShowChatPreviews"
let DEFAULT_PRIVACY_SAVE_LAST_DRAFT = "privacySaveLastDraft"
@@ -47,6 +47,7 @@ let DEFAULT_ACCENT_COLOR_GREEN = "accentColorGreen" // deprecated, only used for
let DEFAULT_ACCENT_COLOR_BLUE = "accentColorBlue" // deprecated, only used for migration
let DEFAULT_USER_INTERFACE_STYLE = "userInterfaceStyle" // deprecated, only used for migration
let DEFAULT_PROFILE_IMAGE_CORNER_RADIUS = "profileImageCornerRadius"
let DEFAULT_ONE_HAND_UI_CARD_SHOWN = "oneHandUICardShown"
let DEFAULT_CONNECT_VIA_LINK_TAB = "connectViaLinkTab"
let DEFAULT_LIVE_MESSAGE_ALERT_SHOWN = "liveMessageAlertShown"
let DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE = "showHiddenProfilesNotice"
@@ -61,6 +62,8 @@ let DEFAULT_DEVICE_NAME_FOR_REMOTE_ACCESS = "deviceNameForRemoteAccess"
let DEFAULT_CONFIRM_REMOTE_SESSIONS = "confirmRemoteSessions"
let DEFAULT_CONNECT_REMOTE_VIA_MULTICAST = "connectRemoteViaMulticast"
let DEFAULT_CONNECT_REMOTE_VIA_MULTICAST_AUTO = "connectRemoteViaMulticastAuto"
let DEFAULT_SHOW_DELETE_CONVERSATION_NOTICE = "showDeleteConversationNotice"
let DEFAULT_SHOW_DELETE_CONTACT_NOTICE = "showDeleteContactNotice"
let DEFAULT_SHOW_SENT_VIA_RPOXY = "showSentViaProxy"
let DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE = "showSubscriptionPercentage"
@@ -94,6 +97,7 @@ let appDefaults: [String: Any] = [
DEFAULT_DEVELOPER_TOOLS: false,
DEFAULT_ENCRYPTION_STARTED: false,
DEFAULT_PROFILE_IMAGE_CORNER_RADIUS: defaultProfileImageCorner,
DEFAULT_ONE_HAND_UI_CARD_SHOWN: false,
DEFAULT_CONNECT_VIA_LINK_TAB: ConnectViaLinkTab.scan.rawValue,
DEFAULT_LIVE_MESSAGE_ALERT_SHOWN: false,
DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE: true,
@@ -104,6 +108,8 @@ let appDefaults: [String: Any] = [
DEFAULT_CONFIRM_REMOTE_SESSIONS: false,
DEFAULT_CONNECT_REMOTE_VIA_MULTICAST: true,
DEFAULT_CONNECT_REMOTE_VIA_MULTICAST_AUTO: true,
DEFAULT_SHOW_DELETE_CONVERSATION_NOTICE: true,
DEFAULT_SHOW_DELETE_CONTACT_NOTICE: true,
DEFAULT_SHOW_SENT_VIA_RPOXY: false,
DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE: false,
ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN: AppSettingsLockScreenCalls.show.rawValue,
@@ -158,6 +164,9 @@ let onboardingStageDefault = EnumDefault<OnboardingStage>(defaults: UserDefaults
let customDisappearingMessageTimeDefault = IntDefault(defaults: UserDefaults.standard, forKey: DEFAULT_CUSTOM_DISAPPEARING_MESSAGE_TIME)
let showDeleteConversationNoticeDefault = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SHOW_DELETE_CONVERSATION_NOTICE)
let showDeleteContactNoticeDefault = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SHOW_DELETE_CONTACT_NOTICE)
let currentThemeDefault = StringDefault(defaults: UserDefaults.standard, forKey: DEFAULT_CURRENT_THEME, withDefault: DefaultTheme.SYSTEM_THEME_NAME)
let systemDarkThemeDefault = StringDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SYSTEM_DARK_THEME, withDefault: DefaultTheme.DARK.themeName)
let currentThemeIdsDefault = CodableDefault<[String: String]>(defaults: UserDefaults.standard, forKey: DEFAULT_CURRENT_THEME_IDS, withDefault: [:] )
@@ -165,6 +174,9 @@ let themeOverridesDefault: CodableDefault<[ThemeOverrides]> = CodableDefault(def
func setGroupDefaults() {
privacyAcceptImagesGroupDefault.set(UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_ACCEPT_IMAGES))
performLAGroupDefault.set(UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA))
privacyLinkPreviewsGroupDefault.set(UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_LINK_PREVIEWS))
profileImageCornerRadiusGroupDefault.set(UserDefaults.standard.double(forKey: DEFAULT_PROFILE_IMAGE_CORNER_RADIUS))
}
public class StringDefault {
@@ -39,7 +39,7 @@
</trans-unit>
<trans-unit id="!1 colored!" xml:space="preserve" approved="no">
<source>!1 colored!</source>
<target state="translated">! 1 ملون!</target>
<target state="translated">! 1 مُلوَّن!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="#secret#" xml:space="preserve" approved="no">
@@ -69,7 +69,7 @@
</trans-unit>
<trans-unit id="%@ is not verified" xml:space="preserve" approved="no">
<source>%@ is not verified</source>
<target state="translated">%@ لم يتم التحقق منها</target>
<target state="translated">%@ لم يتم التحقق منه</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ is verified" xml:space="preserve" approved="no">
@@ -107,8 +107,9 @@
<target state="translated">%d ثانية</target>
<note>message ttl</note>
</trans-unit>
<trans-unit id="%d skipped message(s)" xml:space="preserve">
<trans-unit id="%d skipped message(s)" xml:space="preserve" approved="no">
<source>%d skipped message(s)</source>
<target state="translated">%d الرسائل المتخطية</target>
<note>integrity error chat item</note>
</trans-unit>
<trans-unit id="%lld" xml:space="preserve" approved="no">
@@ -121,12 +122,14 @@
<target state="needs-translation">%lld %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld contact(s) selected" xml:space="preserve">
<trans-unit id="%lld contact(s) selected" xml:space="preserve" approved="no">
<source>%lld contact(s) selected</source>
<target state="translated">%lld تم اختيار جهات الاتصال</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld file(s) with total size of %@" xml:space="preserve">
<trans-unit id="%lld file(s) with total size of %@" xml:space="preserve" approved="no">
<source>%lld file(s) with total size of %@</source>
<target state="translated">%lld الملفات ذات الحجم الإجمالي %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld members" xml:space="preserve" approved="no">
@@ -134,8 +137,9 @@
<target state="translated">%lld أعضاء</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld second(s)" xml:space="preserve">
<trans-unit id="%lld second(s)" xml:space="preserve" approved="no">
<source>%lld second(s)</source>
<target state="translated">%lld ثوانى</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lldd" xml:space="preserve" approved="no">
@@ -185,7 +189,7 @@
</trans-unit>
<trans-unit id="**Add new contact**: to create your one-time QR Code for your contact." xml:space="preserve" approved="no">
<source>**Add new contact**: to create your one-time QR Code or link for your contact.</source>
<target state="translated">** إضافة جهة اتصال جديدة **: لإنشاء رمز QR لمرة واحدة أو رابط جهة الاتصال الخاصة بك.</target>
<target state="translated">** إضافة جهة اتصال جديدة **: لإنشاء رمز QR لمرة واحدة أو رابط جهة الاتصال الخاصة بكم.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Create link / QR code** for your contact to use." xml:space="preserve" approved="no">
@@ -195,12 +199,12 @@
</trans-unit>
<trans-unit id="**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." xml:space="preserve" approved="no">
<source>**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have.</source>
<target state="translated">** المزيد من الخصوصية **: تحقق من الرسائل الجديدة كل 20 دقيقة. تتم مشاركة رمز الجهاز مع خادم SimpleX Chat ، ولكن ليس عدد جهات الاتصال أو الرسائل لديك.</target>
<target state="translated">** المزيد من الخصوصية **: تحققوا من الرسائل الجديدة كل 20 دقيقة. تتم مشاركة رمز الجهاز مع خادم SimpleX Chat ، ولكن ليس عدد جهات الاتصال أو الرسائل لديكم.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." xml:space="preserve" approved="no">
<source>**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app).</source>
<target state="translated">** الأكثر خصوصية **: لا تستخدم خادم إشعارات SimpleX Chat ، وتحقق من الرسائل بشكل دوري في الخلفية (يعتمد على عدد مرات استخدامك للتطبيق).</target>
<target state="translated">** الأكثر خصوصية **: لا تستخدم خادم إشعارات SimpleX Chat ، وتحقق من الرسائل بشكل دوري في الخلفية (يعتمد على عدد مرات استخدامكم للتطبيق).</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Paste received link** or open it in the browser and tap **Open in mobile app**." xml:space="preserve" approved="no">
@@ -210,7 +214,7 @@
</trans-unit>
<trans-unit id="**Please note**: you will NOT be able to recover or change passphrase if you lose it." xml:space="preserve" approved="no">
<source>**Please note**: you will NOT be able to recover or change passphrase if you lose it.</source>
<target state="translated">** يرجى ملاحظة **: لن تتمكن من استعادة أو تغيير عبارة المرور إذا فقدتها.</target>
<target state="translated">** يرجى ملاحظة **: لن تتمكنوا من استعادة أو تغيير عبارة المرور إذا فقدتموها.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." xml:space="preserve" approved="no">
@@ -305,7 +309,7 @@
</trans-unit>
<trans-unit id="A separate TCP connection will be used **for each chat profile you have in the app**." xml:space="preserve" approved="no">
<source>A separate TCP connection will be used **for each chat profile you have in the app**.</source>
<target state="translated">سيتم استخدام اتصال TCP منفصل ** لكل ملف تعريف دردشة لديك في التطبيق **.</target>
<target state="translated">سيتم استخدام اتصال TCP منفصل ** لكل ملف تعريف دردشة لديكم في التطبيق **.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="A separate TCP connection will be used **for each contact and group member**.&#10;**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail." xml:space="preserve" approved="no">
@@ -355,24 +359,29 @@
<source>Accept requests</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Add preset servers" xml:space="preserve">
<trans-unit id="Add preset servers" xml:space="preserve" approved="no">
<source>Add preset servers</source>
<target state="translated">إضافة خوادم محددة مسبقا</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Add profile" xml:space="preserve">
<trans-unit id="Add profile" xml:space="preserve" approved="no">
<source>Add profile</source>
<target state="translated">إضافة الملف الشخصي</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Add servers by scanning QR codes." xml:space="preserve">
<trans-unit id="Add servers by scanning QR codes." xml:space="preserve" approved="no">
<source>Add servers by scanning QR codes.</source>
<target state="translated">إضافة خوادم عن طريق مسح رموز QR.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Add server" xml:space="preserve">
<trans-unit id="Add server" xml:space="preserve" approved="no">
<source>Add server</source>
<target state="translated">أضف الخادم</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Add to another device" xml:space="preserve">
<trans-unit id="Add to another device" xml:space="preserve" approved="no">
<source>Add to another device</source>
<target state="translated">أضف إلى جهاز آخر</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Admins can create the links to join groups." xml:space="preserve">
@@ -3667,7 +3676,7 @@ SimpleX servers cannot see your profile.</source>
</trans-unit>
<trans-unit id="## In reply to" xml:space="preserve" approved="no">
<source>## In reply to</source>
<target state="translated">## ردًا على</target>
<target state="translated">## ردًّا على</target>
<note>copied message info</note>
</trans-unit>
<trans-unit id="%@ and %@ connected" xml:space="preserve" approved="no">
@@ -3675,6 +3684,208 @@ SimpleX servers cannot see your profile.</source>
<target state="translated">%@ و %@ متصل</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ downloaded" xml:space="preserve" approved="no">
<source>%@ downloaded</source>
<target state="translated">%@ تم التنزيل</target>
</trans-unit>
<trans-unit id="%@ and %@" xml:space="preserve" approved="no">
<source>%@ and %@</source>
<target state="translated">%@ و %@</target>
</trans-unit>
<trans-unit id="%@ connected" xml:space="preserve" approved="no">
<source>%@ connected</source>
<target state="translated">%@ متصل</target>
</trans-unit>
<trans-unit id="%lld minutes" xml:space="preserve" approved="no">
<source>%lld minutes</source>
<target state="translated">%lld دقائق</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="%d weeks" xml:space="preserve" approved="no">
<source>%d weeks</source>
<target state="translated">%d أسابيع</target>
</trans-unit>
<trans-unit id="%@ uploaded" xml:space="preserve" approved="no">
<source>%@ uploaded</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="%lld seconds" xml:space="preserve" approved="no">
<source>%lld seconds</source>
<target state="translated">%lld ثواني</target>
</trans-unit>
<trans-unit id="%u messages failed to decrypt." xml:space="preserve" approved="no">
<source>%u messages failed to decrypt.</source>
<target state="translated">%u فشلت عملية فك تشفير الرسائل.</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="%lld messages moderated by %@" xml:space="preserve" approved="no">
<source>%lld messages moderated by %@</source>
<target state="translated">%lld رسائل تمت إدارتها بواسطة %@</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="%lld group events" xml:space="preserve" approved="no">
<source>%lld group events</source>
<target state="translated">%lld أحداث المجموعة</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">%lld رسائل محظورة بواسطه المسؤول</target>
</trans-unit>
<trans-unit id="%lld messages blocked" xml:space="preserve" approved="no">
<source>%lld messages blocked</source>
<target state="translated">%lld رسائل تم حظرها</target>
</trans-unit>
<trans-unit id="%u messages skipped." xml:space="preserve" approved="no">
<source>%u messages skipped.</source>
<target state="translated">%u تم تخطي الرسائل.</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">**إضافة جهة اتصال**: لإنشاء رابط دعوة جديد، أو الاتصال عبر الرابط الذي تلقيتوهم.</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="(this device v%@)" xml:space="preserve" approved="no">
<source>(this device v%@)</source>
<target state="translated">(هذا الجهاز v%@)</target>
</trans-unit>
<trans-unit id="(new)" xml:space="preserve" approved="no">
<source>(new)</source>
<target state="translated">(جديد)</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">**يرجى الملاحظة**: سيؤدي استخدام نفس قاعدة البيانات على جهازين إلى كسر فك تشفير الرسائل من اتصالاتكم كحماية أمنية.</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">سيتم مشاركة ملف تعريفي عشوائي جديد.</target>
</trans-unit>
<trans-unit id="30 seconds" xml:space="preserve" approved="no">
<source>30 seconds</source>
<target state="translated">30 ثانيه</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="0 sec" xml:space="preserve" approved="no">
<source>0 sec</source>
<target state="translated">0 ثانيه</target>
</trans-unit>
<trans-unit id="1 minute" xml:space="preserve" approved="no">
<source>1 minute</source>
<target state="translated">1 دقيقة</target>
</trans-unit>
<trans-unit id="5 minutes" xml:space="preserve" approved="no">
<source>5 minutes</source>
<target state="translated">5 دقائق</target>
</trans-unit>
<trans-unit id="&lt;p&gt;Hi!&lt;/p&gt;&#10;&lt;p&gt;&lt;a href=&quot;%@&quot;&gt;Connect to me via SimpleX Chat&lt;/a&gt;&lt;/p&gt;" xml:space="preserve" approved="no">
<source>&lt;p&gt;Hi!&lt;/p&gt;
&lt;p&gt;&lt;a href="%@"&gt;Connect to me via SimpleX Chat&lt;/a&gt;&lt;/p&gt;</source>
<target state="translated">&lt;p&gt;مرحبا!&lt;/p&gt;
&lt;p&gt;&lt;a href="%@"&gt;أتصل بى من خلال SimpleX Chat&lt;/a&gt;&lt;/p&gt;</target>
</trans-unit>
<trans-unit id="0s" xml:space="preserve" approved="no">
<source>0s</source>
<target state="translated">0 ث</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="- connect to [directory service](simplex:/contact#/?v=1-4&amp;smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!&#10;- delivery receipts (up to 20 members).&#10;- faster and more stable." xml:space="preserve" approved="no">
<source>- connect to [directory service](simplex:/contact#/?v=1-4&amp;smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!
- delivery receipts (up to 20 members).
- faster and more stable.</source>
<target state="translated">- أتصل بـ [directory service](simplex:/contact#/?v=1-4&amp;smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!
- delivery receipts (up to 20 members).
- أسرع و أكثر اسْتِقْرارًا.</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">**تحذير**: سيتم إزالة الأرشيف.</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="- voice messages up to 5 minutes.&#10;- custom time to disappear.&#10;- editing history." xml:space="preserve" approved="no">
<source>- voice messages up to 5 minutes.
- custom time to disappear.
- editing history.</source>
<target state="translated">- رسائل صوتية تصل مدتها إلى 5 دقائق.
- وقت مخصص للاختفاء.
- تعديل السجل.</target>
</trans-unit>
<trans-unit id="Add welcome message" xml:space="preserve" approved="no">
<source>Add welcome message</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="Add contact" xml:space="preserve" approved="no">
<source>Add contact</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="About SimpleX address" xml:space="preserve" approved="no">
<source>About SimpleX address</source>
<target state="translated">حول عنوان SimpleX</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="Acknowledged" xml:space="preserve" approved="no">
<source>Acknowledged</source>
<target state="translated">معترف به</target>
</trans-unit>
<trans-unit id="Acknowledgement errors" xml:space="preserve" approved="no">
<source>Acknowledgement errors</source>
<target state="translated">أخطاء الإقرار</target>
</trans-unit>
<trans-unit id="Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." xml:space="preserve" approved="no">
<source>Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts.</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="Active connections" xml:space="preserve" approved="no">
<source>Active connections</source>
<target state="translated">اتصالات نشطة</target>
</trans-unit>
</body>
</file>
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="ar" datatype="plaintext">
@@ -772,6 +772,10 @@
<target>Разреши изпращането на изчезващи съобщения.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Allow sharing" xml:space="preserve">
<source>Allow sharing</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Allow to irreversibly delete sent messages. (24 hours)" xml:space="preserve">
<source>Allow to irreversibly delete sent messages. (24 hours)</source>
<target>Позволи необратимо изтриване на изпратените съобщения. (24 часа)</target>
@@ -1058,6 +1062,10 @@
<target>Блокиран от админ</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Blur media" xml:space="preserve">
<source>Blur media</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
<source>Both you and your contact can add message reactions.</source>
<target>И вие, и вашият контакт можете да добавяте реакции към съобщението.</target>
@@ -1226,6 +1234,10 @@
<target>Базата данни на чата е изтрита</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat database exported" xml:space="preserve">
<source>Chat database exported</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat database imported" xml:space="preserve">
<source>Chat database imported</source>
<target>Базата данни на чат е импортирана</target>
@@ -1509,6 +1521,10 @@ This is your own one-time link!</source>
<target>Грешка при свързване (AUTH)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection notifications" xml:space="preserve">
<source>Connection notifications</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection request sent!" xml:space="preserve">
<source>Connection request sent!</source>
<target>Заявката за връзка е изпратена!</target>
@@ -1849,6 +1865,10 @@ This is your own one-time link!</source>
<target>Изтрий</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Delete %lld messages of members?" xml:space="preserve">
<source>Delete %lld messages of members?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete %lld messages?" xml:space="preserve">
<source>Delete %lld messages?</source>
<target>Изтриване на %lld съобщения?</target>
@@ -2089,10 +2109,18 @@ This cannot be undone!</source>
<target>Настолни устройства</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Destination server error: %@" xml:space="preserve">
<source>Destination server error: %@</source>
<note>snd error text</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Detailed statistics" xml:space="preserve">
<source>Detailed statistics</source>
<note>No comment provided by engineer.</note>
@@ -2156,6 +2184,10 @@ This cannot be undone!</source>
<target>Деактивиране за всички</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Disabled" xml:space="preserve">
<source>Disabled</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Disappearing message" xml:space="preserve">
<source>Disappearing message</source>
<target>Изчезващо съобщение</target>
@@ -2376,6 +2408,10 @@ This cannot be undone!</source>
<target>Активирай kод за достъп за самоунищожение</target>
<note>set passcode view</note>
</trans-unit>
<trans-unit id="Enabled" xml:space="preserve">
<source>Enabled</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enabled for" xml:space="preserve">
<source>Enabled for</source>
<target>Активирано за</target>
@@ -2546,6 +2582,10 @@ This cannot be undone!</source>
<target>Грешка при промяна на настройката</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error connecting to forwarding server %@. Please try later." xml:space="preserve">
<source>Error connecting to forwarding server %@. Please try later.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating address" xml:space="preserve">
<source>Error creating address</source>
<target>Грешка при създаване на адрес</target>
@@ -3040,6 +3080,18 @@ This cannot be undone!</source>
<target>Препратено от</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarding server address is incompatible with network settings: %@." xml:space="preserve">
<source>Forwarding server address is incompatible with network settings: %@.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarding server version is incompatible with network settings: %@." xml:space="preserve">
<source>Forwarding server version is incompatible with network settings: %@.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarding server: %@&#10;Destination server error: %@" xml:space="preserve">
<source>Forwarding server: %1$@
Destination server error: %2$@</source>
@@ -3851,6 +3903,14 @@ This is your link for group %@!</source>
<target>Макс. 30 секунди, получено незабавно.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Media &amp; file servers" xml:space="preserve">
<source>Media &amp; file servers</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Medium" xml:space="preserve">
<source>Medium</source>
<note>blur media</note>
</trans-unit>
<trans-unit id="Member" xml:space="preserve">
<source>Member</source>
<target>Член</target>
@@ -3929,12 +3989,8 @@ This is your link for group %@!</source>
<source>Message reception</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message routing fallback" xml:space="preserve">
<source>Message routing fallback</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message routing mode" xml:space="preserve">
<source>Message routing mode</source>
<trans-unit id="Message servers" xml:space="preserve">
<source>Message servers</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message source remains private." xml:space="preserve">
@@ -4260,6 +4316,10 @@ This is your link for group %@!</source>
<target>Несъвместим!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Nothing selected" xml:space="preserve">
<source>Nothing selected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>Известия</target>
@@ -4287,7 +4347,7 @@ This is your link for group %@!</source>
<trans-unit id="Off" xml:space="preserve">
<source>Off</source>
<target>Изключено</target>
<note>No comment provided by engineer.</note>
<note>blur media</note>
</trans-unit>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
@@ -4309,14 +4369,18 @@ This is your link for group %@!</source>
<target>Линк за еднократна покана</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Onion hosts will be required for connection. Requires enabling VPN." xml:space="preserve">
<source>Onion hosts will be required for connection. Requires enabling VPN.</source>
<target>За свързване ще са необходими Onion хостове. Изисква се активиране на VPN.</target>
<trans-unit id="Onion hosts will be **required** for connection.&#10;Requires compatible VPN." xml:space="preserve">
<source>Onion hosts will be **required** for connection.
Requires compatible VPN.</source>
<target>За свързване ще са **необходими** Onion хостове.
Изисква се активиране на VPN.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Onion hosts will be used when available. Requires enabling VPN." xml:space="preserve">
<source>Onion hosts will be used when available. Requires enabling VPN.</source>
<target>Ще се използват Onion хостове, когато са налични. Изисква се активиране на VPN.</target>
<trans-unit id="Onion hosts will be used when available.&#10;Requires compatible VPN." xml:space="preserve">
<source>Onion hosts will be used when available.
Requires compatible VPN.</source>
<target>Ще се използват Onion хостове, когато са налични.
Изисква се активиране на VPN.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Onion hosts will not be used." xml:space="preserve">
@@ -4638,10 +4702,6 @@ Error: %@</source>
<target>Моля, съхранявайте паролата на сигурно място, НЯМА да можете да я промените, ако я загубите.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Please try later." xml:space="preserve">
<source>Please try later.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Polish interface" xml:space="preserve">
<source>Polish interface</source>
<target>Полски интерфейс</target>
@@ -5179,11 +5239,6 @@ Enable in *Network &amp; servers* settings.</source>
<target>Покажи</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Revert" xml:space="preserve">
<source>Revert</source>
<target>Отмени промените</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>Отзови</target>
@@ -5213,11 +5268,6 @@ Enable in *Network &amp; servers* settings.</source>
<source>SMP server</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="SMP servers" xml:space="preserve">
<source>SMP servers</source>
<target>SMP сървъри</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Safely receive files" xml:space="preserve">
<source>Safely receive files</source>
<note>No comment provided by engineer.</note>
@@ -5247,6 +5297,10 @@ Enable in *Network &amp; servers* settings.</source>
<target>Запази и уведоми членовете на групата</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Save and reconnect" xml:space="preserve">
<source>Save and reconnect</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Save and update group profile" xml:space="preserve">
<source>Save and update group profile</source>
<target>Запази и актуализирай профила на групата</target>
@@ -5401,6 +5455,10 @@ Enable in *Network &amp; servers* settings.</source>
<trans-unit id="Select" xml:space="preserve">
<source>Select</source>
<target>Избери</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Selected %lld" xml:space="preserve">
<source>Selected %lld</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
@@ -5639,10 +5697,6 @@ Enable in *Network &amp; servers* settings.</source>
<source>Server version is incompatible with network settings.</source>
<note>srv error text</note>
</trans-unit>
<trans-unit id="Server version is incompatible with network settings: %@." xml:space="preserve">
<source>Server version is incompatible with network settings: %@.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Server version is incompatible with your app: %@." xml:space="preserve">
<source>Server version is incompatible with your app: %@.</source>
<note>No comment provided by engineer.</note>
@@ -5754,6 +5808,10 @@ Enable in *Network &amp; servers* settings.</source>
<target>Сподели този еднократен линк за връзка</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Share to SimpleX" xml:space="preserve">
<source>Share to SimpleX</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Share with contacts" xml:space="preserve">
<source>Share with contacts</source>
<target>Сподели с контактите</target>
@@ -5899,11 +5957,23 @@ Enable in *Network &amp; servers* settings.</source>
<target>Малки групи (максимум 20)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Soft" xml:space="preserve">
<source>Soft</source>
<note>blur media</note>
</trans-unit>
<trans-unit id="Some file(s) were not exported:" xml:space="preserve">
<source>Some file(s) were not exported:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Some non-fatal errors occurred during import - you may see Chat console for more details." xml:space="preserve">
<source>Some non-fatal errors occurred during import - you may see Chat console for more details.</source>
<target>Някои не-фатални грешки са възникнали по време на импортиране - може да видите конзолата за повече подробности.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Some non-fatal errors occurred during import:" xml:space="preserve">
<source>Some non-fatal errors occurred during import:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Somebody" xml:space="preserve">
<source>Somebody</source>
<target>Някой</target>
@@ -5997,6 +6067,10 @@ Enable in *Network &amp; servers* settings.</source>
<target>Спиране на чата</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Strong" xml:space="preserve">
<source>Strong</source>
<note>blur media</note>
</trans-unit>
<trans-unit id="Submit" xml:space="preserve">
<source>Submit</source>
<target>Изпрати</target>
@@ -6029,6 +6103,10 @@ Enable in *Network &amp; servers* settings.</source>
<target>Системна идентификация</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="TCP connection" xml:space="preserve">
<source>TCP connection</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="TCP connection timeout" xml:space="preserve">
<source>TCP connection timeout</source>
<target>Времето на изчакване за установяване на TCP връзка</target>
@@ -6199,6 +6277,14 @@ It can happen because of some bug or when the connection is compromised.</source
<target>Съобщението ще бъде маркирано като модерирано за всички членове.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The messages will be deleted for all members." xml:space="preserve">
<source>The messages will be deleted for all members.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The next generation of private messaging" xml:space="preserve">
<source>The next generation of private messaging</source>
<target>Ново поколение поверителни съобщения</target>
@@ -6551,11 +6637,6 @@ To connect, please ask your contact to create another connection link and check
<target>Актуализация</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Update .onion hosts setting?" xml:space="preserve">
<source>Update .onion hosts setting?</source>
<target>Актуализиране на настройката за .onion хостове?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Update database passphrase" xml:space="preserve">
<source>Update database passphrase</source>
<target>Актуализирай паролата на базата данни</target>
@@ -6566,9 +6647,8 @@ To connect, please ask your contact to create another connection link and check
<target>Актуализиране на мрежовите настройки?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Update transport isolation mode?" xml:space="preserve">
<source>Update transport isolation mode?</source>
<target>Актуализиране на режима на изолация на транспорта?</target>
<trans-unit id="Update settings?" xml:space="preserve">
<source>Update settings?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Updating settings will re-connect the client to all servers." xml:space="preserve">
@@ -6576,11 +6656,6 @@ To connect, please ask your contact to create another connection link and check
<target>Актуализирането на настройките ще свърже отново клиента към всички сървъри.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Updating this setting will re-connect the client to all servers." xml:space="preserve">
<source>Updating this setting will re-connect the client to all servers.</source>
<target>Актуализирането на тази настройка ще свърже повторно клиента към всички сървъри.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Upgrade and open chat" xml:space="preserve">
<source>Upgrade and open chat</source>
<target>Актуализирай и отвори чата</target>
@@ -6685,11 +6760,6 @@ To connect, please ask your contact to create another connection link and check
<source>User selection</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Using .onion hosts requires compatible VPN provider." xml:space="preserve">
<source>Using .onion hosts requires compatible VPN provider.</source>
<target>Използването на .onion хостове изисква съвместим VPN доставчик.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Using SimpleX Chat servers." xml:space="preserve">
<source>Using SimpleX Chat servers.</source>
<target>Използват се сървърите на SimpleX Chat.</target>
@@ -6943,11 +7013,6 @@ To connect, please ask your contact to create another connection link and check
<source>XFTP server</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="XFTP servers" xml:space="preserve">
<source>XFTP servers</source>
<target>XFTP сървъри</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You" xml:space="preserve">
<source>You</source>
<target>Вие</target>
@@ -7161,6 +7226,14 @@ Repeat connection request?</source>
<target>Вие се присъединихте към тази група. Свързване с поканващия член на групата.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You may migrate the exported database." xml:space="preserve">
<source>You may migrate the exported database.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You may save the exported archive." xml:space="preserve">
<source>You may save the exported archive.</source>
<note>No comment provided by engineer.</note>
</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">
<source>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.</source>
<target>Трябва да използвате най-новата версия на вашата чат база данни САМО на едно устройство, в противен случай може да спрете да получавате съобщения от някои контакти.</target>
@@ -8131,8 +8204,8 @@ last received msg: %2$@</source>
<target>неизвестен</target>
<note>connection info</note>
</trans-unit>
<trans-unit id="unknown relays" xml:space="preserve">
<source>unknown relays</source>
<trans-unit id="unknown servers" xml:space="preserve">
<source>unknown servers</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="unknown status" xml:space="preserve">
@@ -8354,4 +8427,178 @@ last received msg: %2$@</source>
</trans-unit>
</body>
</file>
<file original="SimpleX SE/en.lproj/InfoPlist.strings" source-language="en" target-language="bg" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
</header>
<body>
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
<source>SimpleX SE</source>
<note>Bundle display name</note>
</trans-unit>
<trans-unit id="CFBundleName" xml:space="preserve">
<source>SimpleX SE</source>
<note>Bundle name</note>
</trans-unit>
<trans-unit id="NSHumanReadableCopyright" xml:space="preserve">
<source>Copyright © 2024 SimpleX Chat. All rights reserved.</source>
<note>Copyright (human-readable)</note>
</trans-unit>
</body>
</file>
<file original="SimpleX SE/en.lproj/Localizable.strings" source-language="en" target-language="bg" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
</header>
<body>
<trans-unit id="%@" xml:space="preserve">
<source>%@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="App is locked!" xml:space="preserve">
<source>App is locked!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cancel" xml:space="preserve">
<source>Cancel</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cannot access keychain to save database password" xml:space="preserve">
<source>Cannot access keychain to save database password</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cannot forward message" xml:space="preserve">
<source>Cannot forward message</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Comment" xml:space="preserve">
<source>Comment</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Currently maximum supported file size is %@." xml:space="preserve">
<source>Currently maximum supported file size is %@.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database downgrade required" xml:space="preserve">
<source>Database downgrade required</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database encrypted!" xml:space="preserve">
<source>Database encrypted!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database error" xml:space="preserve">
<source>Database error</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database passphrase is required to open chat." xml:space="preserve">
<source>Database passphrase is required to open chat.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database upgrade required" xml:space="preserve">
<source>Database upgrade required</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error preparing file" xml:space="preserve">
<source>Error preparing file</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error preparing message" xml:space="preserve">
<source>Error preparing message</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error: %@" xml:space="preserve">
<source>Error: %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="File error" xml:space="preserve">
<source>File error</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incompatible database version" xml:space="preserve">
<source>Incompatible database version</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Invalid migration confirmation" xml:space="preserve">
<source>Invalid migration confirmation</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Keychain error" xml:space="preserve">
<source>Keychain error</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Large file!" xml:space="preserve">
<source>Large file!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No active profile" xml:space="preserve">
<source>No active profile</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open the app to downgrade the database." xml:space="preserve">
<source>Open the app to downgrade the database.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open the app to upgrade the database." xml:space="preserve">
<source>Open the app to upgrade the database.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Passphrase" xml:space="preserve">
<source>Passphrase</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Please create a profile in the SimpleX app" xml:space="preserve">
<source>Please create a profile in the SimpleX app</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
<source>Selected chat preferences prohibit this message.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sending a message takes longer than expected." xml:space="preserve">
<source>Sending a message takes longer than expected.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sending message…" xml:space="preserve">
<source>Sending message…</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Share" xml:space="preserve">
<source>Share</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Slow network?" xml:space="preserve">
<source>Slow network?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unknown database error: %@" xml:space="preserve">
<source>Unknown database error: %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unsupported format" xml:space="preserve">
<source>Unsupported format</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Wait" xml:space="preserve">
<source>Wait</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Wrong database passphrase" xml:space="preserve">
<source>Wrong database passphrase</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
</body>
</file>
</xliff>
@@ -1,6 +1,9 @@
/* Bundle display name */
"CFBundleDisplayName" = "SimpleX NSE";
/* Bundle name */
"CFBundleName" = "SimpleX NSE";
/* Copyright (human-readable) */
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";
@@ -0,0 +1,7 @@
/*
Localizable.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -0,0 +1,7 @@
/*
InfoPlist.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -0,0 +1,7 @@
/*
Localizable.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -749,6 +749,10 @@
<target>Povolit odesílání mizících zpráv.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Allow sharing" xml:space="preserve">
<source>Allow sharing</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Allow to irreversibly delete sent messages. (24 hours)" xml:space="preserve">
<source>Allow to irreversibly delete sent messages. (24 hours)</source>
<target>Povolit nevratné smazání odeslaných zpráv. (24 hodin)</target>
@@ -1019,6 +1023,10 @@
<source>Blocked by admin</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Blur media" xml:space="preserve">
<source>Blur media</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
<source>Both you and your contact can add message reactions.</source>
<target>Vy i váš kontakt můžete přidávat reakce na zprávy.</target>
@@ -1184,6 +1192,10 @@
<target>Databáze chatu odstraněna</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat database exported" xml:space="preserve">
<source>Chat database exported</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat database imported" xml:space="preserve">
<source>Chat database imported</source>
<target>Importovaná databáze chatu</target>
@@ -1448,6 +1460,10 @@ This is your own one-time link!</source>
<target>Chyba spojení (AUTH)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection notifications" xml:space="preserve">
<source>Connection notifications</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection request sent!" xml:space="preserve">
<source>Connection request sent!</source>
<target>Požadavek na připojení byl odeslán!</target>
@@ -1779,6 +1795,10 @@ This is your own one-time link!</source>
<target>Smazat</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Delete %lld messages of members?" xml:space="preserve">
<source>Delete %lld messages of members?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete %lld messages?" xml:space="preserve">
<source>Delete %lld messages?</source>
<note>No comment provided by engineer.</note>
@@ -2011,10 +2031,18 @@ This cannot be undone!</source>
<source>Desktop devices</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Destination server error: %@" xml:space="preserve">
<source>Destination server error: %@</source>
<note>snd error text</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Detailed statistics" xml:space="preserve">
<source>Detailed statistics</source>
<note>No comment provided by engineer.</note>
@@ -2078,6 +2106,10 @@ This cannot be undone!</source>
<target>Vypnout pro všechny</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Disabled" xml:space="preserve">
<source>Disabled</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Disappearing message" xml:space="preserve">
<source>Disappearing message</source>
<target>Mizící zpráva</target>
@@ -2289,6 +2321,10 @@ This cannot be undone!</source>
<target>Povolit sebedestrukční heslo</target>
<note>set passcode view</note>
</trans-unit>
<trans-unit id="Enabled" xml:space="preserve">
<source>Enabled</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enabled for" xml:space="preserve">
<source>Enabled for</source>
<note>No comment provided by engineer.</note>
@@ -2451,6 +2487,10 @@ This cannot be undone!</source>
<target>Chyba změny nastavení</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error connecting to forwarding server %@. Please try later." xml:space="preserve">
<source>Error connecting to forwarding server %@. Please try later.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating address" xml:space="preserve">
<source>Error creating address</source>
<target>Chyba při vytváření adresy</target>
@@ -2928,6 +2968,18 @@ This cannot be undone!</source>
<source>Forwarded from</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarding server address is incompatible with network settings: %@." xml:space="preserve">
<source>Forwarding server address is incompatible with network settings: %@.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarding server version is incompatible with network settings: %@." xml:space="preserve">
<source>Forwarding server version is incompatible with network settings: %@.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarding server: %@&#10;Destination server error: %@" xml:space="preserve">
<source>Forwarding server: %1$@
Destination server error: %2$@</source>
@@ -3708,6 +3760,14 @@ This is your link for group %@!</source>
<target>Max 30 vteřin, přijato okamžitě.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Media &amp; file servers" xml:space="preserve">
<source>Media &amp; file servers</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Medium" xml:space="preserve">
<source>Medium</source>
<note>blur media</note>
</trans-unit>
<trans-unit id="Member" xml:space="preserve">
<source>Member</source>
<target>Člen</target>
@@ -3786,12 +3846,8 @@ This is your link for group %@!</source>
<source>Message reception</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message routing fallback" xml:space="preserve">
<source>Message routing fallback</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message routing mode" xml:space="preserve">
<source>Message routing mode</source>
<trans-unit id="Message servers" xml:space="preserve">
<source>Message servers</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message source remains private." xml:space="preserve">
@@ -4099,6 +4155,10 @@ This is your link for group %@!</source>
<source>Not compatible!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Nothing selected" xml:space="preserve">
<source>Nothing selected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>Oznámení</target>
@@ -4125,7 +4185,7 @@ This is your link for group %@!</source>
<trans-unit id="Off" xml:space="preserve">
<source>Off</source>
<target>Vypnout</target>
<note>No comment provided by engineer.</note>
<note>blur media</note>
</trans-unit>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
@@ -4147,14 +4207,18 @@ This is your link for group %@!</source>
<target>Jednorázový zvací odkaz</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Onion hosts will be required for connection. Requires enabling VPN." xml:space="preserve">
<source>Onion hosts will be required for connection. Requires enabling VPN.</source>
<target>Pro připojení budou vyžadováni Onion hostitelé. Vyžaduje povolení sítě VPN.</target>
<trans-unit id="Onion hosts will be **required** for connection.&#10;Requires compatible VPN." xml:space="preserve">
<source>Onion hosts will be **required** for connection.
Requires compatible VPN.</source>
<target>Pro připojení budou vyžadováni Onion hostitelé.
Vyžaduje povolení sítě VPN.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Onion hosts will be used when available. Requires enabling VPN." xml:space="preserve">
<source>Onion hosts will be used when available. Requires enabling VPN.</source>
<target>Onion hostitelé budou použiti, pokud jsou k dispozici. Vyžaduje povolení sítě VPN.</target>
<trans-unit id="Onion hosts will be used when available.&#10;Requires compatible VPN." xml:space="preserve">
<source>Onion hosts will be used when available.
Requires compatible VPN.</source>
<target>Onion hostitelé budou použiti, pokud jsou k dispozici.
Vyžaduje povolení sítě VPN.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Onion hosts will not be used." xml:space="preserve">
@@ -4460,10 +4524,6 @@ Error: %@</source>
<target>Heslo uložte bezpečně, v případě jeho ztráty jej NEBUDE možné změnit.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Please try later." xml:space="preserve">
<source>Please try later.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Polish interface" xml:space="preserve">
<source>Polish interface</source>
<target>Polské rozhraní</target>
@@ -4985,11 +5045,6 @@ Enable in *Network &amp; servers* settings.</source>
<target>Odhalit</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Revert" xml:space="preserve">
<source>Revert</source>
<target>Vrátit</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>Odvolat</target>
@@ -5019,11 +5074,6 @@ Enable in *Network &amp; servers* settings.</source>
<source>SMP server</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="SMP servers" xml:space="preserve">
<source>SMP servers</source>
<target>SMP servery</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Safely receive files" xml:space="preserve">
<source>Safely receive files</source>
<note>No comment provided by engineer.</note>
@@ -5052,6 +5102,10 @@ Enable in *Network &amp; servers* settings.</source>
<target>Uložit a upozornit členy skupiny</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Save and reconnect" xml:space="preserve">
<source>Save and reconnect</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Save and update group profile" xml:space="preserve">
<source>Save and update group profile</source>
<target>Uložit a aktualizovat profil skupiny</target>
@@ -5200,6 +5254,10 @@ Enable in *Network &amp; servers* settings.</source>
<trans-unit id="Select" xml:space="preserve">
<source>Select</source>
<target>Vybrat</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Selected %lld" xml:space="preserve">
<source>Selected %lld</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
@@ -5437,10 +5495,6 @@ Enable in *Network &amp; servers* settings.</source>
<source>Server version is incompatible with network settings.</source>
<note>srv error text</note>
</trans-unit>
<trans-unit id="Server version is incompatible with network settings: %@." xml:space="preserve">
<source>Server version is incompatible with network settings: %@.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Server version is incompatible with your app: %@." xml:space="preserve">
<source>Server version is incompatible with your app: %@.</source>
<note>No comment provided by engineer.</note>
@@ -5548,6 +5602,10 @@ Enable in *Network &amp; servers* settings.</source>
<source>Share this 1-time invite link</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Share to SimpleX" xml:space="preserve">
<source>Share to SimpleX</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Share with contacts" xml:space="preserve">
<source>Share with contacts</source>
<target>Sdílet s kontakty</target>
@@ -5690,11 +5748,23 @@ Enable in *Network &amp; servers* settings.</source>
<target>Malé skupiny (max. 20)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Soft" xml:space="preserve">
<source>Soft</source>
<note>blur media</note>
</trans-unit>
<trans-unit id="Some file(s) were not exported:" xml:space="preserve">
<source>Some file(s) were not exported:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Some non-fatal errors occurred during import - you may see Chat console for more details." xml:space="preserve">
<source>Some non-fatal errors occurred during import - you may see Chat console for more details.</source>
<target>Během importu došlo k nezávažným chybám - podrobnosti naleznete v chat konzoli.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Some non-fatal errors occurred during import:" xml:space="preserve">
<source>Some non-fatal errors occurred during import:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Somebody" xml:space="preserve">
<source>Somebody</source>
<target>Někdo</target>
@@ -5784,6 +5854,10 @@ Enable in *Network &amp; servers* settings.</source>
<source>Stopping chat</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Strong" xml:space="preserve">
<source>Strong</source>
<note>blur media</note>
</trans-unit>
<trans-unit id="Submit" xml:space="preserve">
<source>Submit</source>
<target>Odeslat</target>
@@ -5816,6 +5890,10 @@ Enable in *Network &amp; servers* settings.</source>
<target>Ověření systému</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="TCP connection" xml:space="preserve">
<source>TCP connection</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="TCP connection timeout" xml:space="preserve">
<source>TCP connection timeout</source>
<target>Časový limit připojení TCP</target>
@@ -5982,6 +6060,14 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován
<target>Zpráva bude pro všechny členy označena jako moderovaná.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The messages will be deleted for all members." xml:space="preserve">
<source>The messages will be deleted for all members.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The next generation of private messaging" xml:space="preserve">
<source>The next generation of private messaging</source>
<target>Nová generace soukromých zpráv</target>
@@ -6317,11 +6403,6 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
<target>Aktualizovat</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Update .onion hosts setting?" xml:space="preserve">
<source>Update .onion hosts setting?</source>
<target>Aktualizovat nastavení hostitelů .onion?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Update database passphrase" xml:space="preserve">
<source>Update database passphrase</source>
<target>Aktualizovat přístupovou frázi databáze</target>
@@ -6332,9 +6413,8 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
<target>Aktualizovat nastavení sítě?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Update transport isolation mode?" xml:space="preserve">
<source>Update transport isolation mode?</source>
<target>Aktualizovat režim dopravní izolace?</target>
<trans-unit id="Update settings?" xml:space="preserve">
<source>Update settings?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Updating settings will re-connect the client to all servers." xml:space="preserve">
@@ -6342,11 +6422,6 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
<target>Aktualizací nastavení se klient znovu připojí ke všem serverům.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Updating this setting will re-connect the client to all servers." xml:space="preserve">
<source>Updating this setting will re-connect the client to all servers.</source>
<target>Aktualizace tohoto nastavení znovu připojí klienta ke všem serverům.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Upgrade and open chat" xml:space="preserve">
<source>Upgrade and open chat</source>
<target>Zvýšit a otevřít chat</target>
@@ -6446,11 +6521,6 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
<source>User selection</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Using .onion hosts requires compatible VPN provider." xml:space="preserve">
<source>Using .onion hosts requires compatible VPN provider.</source>
<target>Použití hostitelů .onion vyžaduje kompatibilního poskytovatele VPN.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Using SimpleX Chat servers." xml:space="preserve">
<source>Using SimpleX Chat servers.</source>
<target>Používat servery SimpleX Chat.</target>
@@ -6687,11 +6757,6 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
<source>XFTP server</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="XFTP servers" xml:space="preserve">
<source>XFTP servers</source>
<target>XFTP servery</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You" xml:space="preserve">
<source>You</source>
<target>Vy</target>
@@ -6890,6 +6955,14 @@ Repeat connection request?</source>
<target>Připojili jste se k této skupině. Připojení k pozvání člena skupiny.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You may migrate the exported database." xml:space="preserve">
<source>You may migrate the exported database.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You may save the exported archive." xml:space="preserve">
<source>You may save the exported archive.</source>
<note>No comment provided by engineer.</note>
</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">
<source>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.</source>
<target>Nejnovější verzi databáze chatu musíte používat POUZE v jednom zařízení, jinak se může stát, že přestanete přijímat zprávy od některých kontaktů.</target>
@@ -7835,8 +7908,8 @@ last received msg: %2$@</source>
<target>neznámý</target>
<note>connection info</note>
</trans-unit>
<trans-unit id="unknown relays" xml:space="preserve">
<source>unknown relays</source>
<trans-unit id="unknown servers" xml:space="preserve">
<source>unknown servers</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="unknown status" xml:space="preserve">
@@ -8051,4 +8124,178 @@ last received msg: %2$@</source>
</trans-unit>
</body>
</file>
<file original="SimpleX SE/en.lproj/InfoPlist.strings" source-language="en" target-language="cs" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
</header>
<body>
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
<source>SimpleX SE</source>
<note>Bundle display name</note>
</trans-unit>
<trans-unit id="CFBundleName" xml:space="preserve">
<source>SimpleX SE</source>
<note>Bundle name</note>
</trans-unit>
<trans-unit id="NSHumanReadableCopyright" xml:space="preserve">
<source>Copyright © 2024 SimpleX Chat. All rights reserved.</source>
<note>Copyright (human-readable)</note>
</trans-unit>
</body>
</file>
<file original="SimpleX SE/en.lproj/Localizable.strings" source-language="en" target-language="cs" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
</header>
<body>
<trans-unit id="%@" xml:space="preserve">
<source>%@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="App is locked!" xml:space="preserve">
<source>App is locked!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cancel" xml:space="preserve">
<source>Cancel</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cannot access keychain to save database password" xml:space="preserve">
<source>Cannot access keychain to save database password</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cannot forward message" xml:space="preserve">
<source>Cannot forward message</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Comment" xml:space="preserve">
<source>Comment</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Currently maximum supported file size is %@." xml:space="preserve">
<source>Currently maximum supported file size is %@.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database downgrade required" xml:space="preserve">
<source>Database downgrade required</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database encrypted!" xml:space="preserve">
<source>Database encrypted!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database error" xml:space="preserve">
<source>Database error</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database passphrase is required to open chat." xml:space="preserve">
<source>Database passphrase is required to open chat.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database upgrade required" xml:space="preserve">
<source>Database upgrade required</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error preparing file" xml:space="preserve">
<source>Error preparing file</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error preparing message" xml:space="preserve">
<source>Error preparing message</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error: %@" xml:space="preserve">
<source>Error: %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="File error" xml:space="preserve">
<source>File error</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incompatible database version" xml:space="preserve">
<source>Incompatible database version</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Invalid migration confirmation" xml:space="preserve">
<source>Invalid migration confirmation</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Keychain error" xml:space="preserve">
<source>Keychain error</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Large file!" xml:space="preserve">
<source>Large file!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No active profile" xml:space="preserve">
<source>No active profile</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open the app to downgrade the database." xml:space="preserve">
<source>Open the app to downgrade the database.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open the app to upgrade the database." xml:space="preserve">
<source>Open the app to upgrade the database.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Passphrase" xml:space="preserve">
<source>Passphrase</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Please create a profile in the SimpleX app" xml:space="preserve">
<source>Please create a profile in the SimpleX app</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
<source>Selected chat preferences prohibit this message.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sending a message takes longer than expected." xml:space="preserve">
<source>Sending a message takes longer than expected.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sending message…" xml:space="preserve">
<source>Sending message…</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Share" xml:space="preserve">
<source>Share</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Slow network?" xml:space="preserve">
<source>Slow network?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unknown database error: %@" xml:space="preserve">
<source>Unknown database error: %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unsupported format" xml:space="preserve">
<source>Unsupported format</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Wait" xml:space="preserve">
<source>Wait</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Wrong database passphrase" xml:space="preserve">
<source>Wrong database passphrase</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
</body>
</file>
</xliff>
@@ -1,6 +1,9 @@
/* Bundle display name */
"CFBundleDisplayName" = "SimpleX NSE";
/* Bundle name */
"CFBundleName" = "SimpleX NSE";
/* Copyright (human-readable) */
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";
@@ -0,0 +1,7 @@
/*
Localizable.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -0,0 +1,7 @@
/*
InfoPlist.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -0,0 +1,7 @@
/*
Localizable.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -590,6 +590,7 @@
</trans-unit>
<trans-unit id="Active connections" xml:space="preserve">
<source>Active connections</source>
<target>Aktive Verbindungen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." xml:space="preserve">
@@ -684,7 +685,7 @@
</trans-unit>
<trans-unit id="All chats and messages will be deleted - this cannot be undone!" xml:space="preserve">
<source>All chats and messages will be deleted - this cannot be undone!</source>
<target>Alle Chats und Nachrichten werden gelöscht. Dies kann nicht rückgängig gemacht werden!</target>
<target>Es werden alle Chats und Nachrichten gelöscht. Dies kann nicht rückgängig gemacht werden!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="All data is erased when it is entered." xml:space="preserve">
@@ -694,7 +695,7 @@
</trans-unit>
<trans-unit id="All data is private to your device." xml:space="preserve">
<source>All data is private to your device.</source>
<target>Alle Daten sind auf Ihrem Gerät geschützt.</target>
<target>Alle Daten werden nur auf Ihrem Gerät gespeichert.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="All group members will remain connected." xml:space="preserve">
@@ -709,7 +710,7 @@
</trans-unit>
<trans-unit id="All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." xml:space="preserve">
<source>All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you.</source>
<target>Alle Nachrichten werden gelöscht . Dies kann nicht rückgängig gemacht werden! Die Nachrichten werden NUR bei Ihnen gelöscht.</target>
<target>Es werden alle Nachrichten gelöscht. Dies kann nicht rückgängig gemacht werden! Die Nachrichten werden NUR bei Ihnen gelöscht.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="All new messages from %@ will be hidden!" xml:space="preserve">
@@ -782,6 +783,11 @@
<target>Das Senden von verschwindenden Nachrichten erlauben.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Allow sharing" xml:space="preserve">
<source>Allow sharing</source>
<target>Teilen erlauben</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Allow to irreversibly delete sent messages. (24 hours)" xml:space="preserve">
<source>Allow to irreversibly delete sent messages. (24 hours)</source>
<target>Unwiederbringliches löschen von gesendeten Nachrichten erlauben. (24 Stunden)</target>
@@ -1072,6 +1078,11 @@
<target>wurde vom Administrator blockiert</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Blur media" xml:space="preserve">
<source>Blur media</source>
<target>Medium unscharf machen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
<source>Both you and your contact can add message reactions.</source>
<target>Sowohl Sie, als auch Ihr Kontakt können Reaktionen auf Nachrichten geben.</target>
@@ -1243,6 +1254,10 @@
<target>Chat-Datenbank gelöscht</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat database exported" xml:space="preserve">
<source>Chat database exported</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat database imported" xml:space="preserve">
<source>Chat database imported</source>
<target>Chat-Datenbank importiert</target>
@@ -1375,6 +1390,7 @@
</trans-unit>
<trans-unit id="Configured %@ servers" xml:space="preserve">
<source>Configured %@ servers</source>
<target>Konfigurierte %@ Server</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Confirm" xml:space="preserve">
@@ -1536,6 +1552,11 @@ Das ist Ihr eigener Einmal-Link!</target>
<target>Verbindungsfehler (AUTH)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection notifications" xml:space="preserve">
<source>Connection notifications</source>
<target>Verbindungsbenachrichtigungen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection request sent!" xml:space="preserve">
<source>Connection request sent!</source>
<target>Verbindungsanfrage wurde gesendet!</target>
@@ -1884,6 +1905,11 @@ Das ist Ihr eigener Einmal-Link!</target>
<target>Löschen</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Delete %lld messages of members?" xml:space="preserve">
<source>Delete %lld messages of members?</source>
<target>%lld Nachrichten der Mitglieder löschen?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete %lld messages?" xml:space="preserve">
<source>Delete %lld messages?</source>
<target>%lld Nachrichten löschen?</target>
@@ -2126,11 +2152,21 @@ Dies kann nicht rückgängig gemacht werden!</target>
<target>Desktop-Geräte</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<target>Adresse des Zielservers von %@ ist nicht kompatibel mit den Einstellungen des Weiterleitungsservers %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Destination server error: %@" xml:space="preserve">
<source>Destination server error: %@</source>
<target>Zielserver-Fehler: %@</target>
<note>snd error text</note>
</trans-unit>
<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>
<target>Die Version des Zielservers %@ ist nicht kompatibel mit dem Weiterleitungsserver %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Detailed statistics" xml:space="preserve">
<source>Detailed statistics</source>
<target>Detaillierte Statistiken</target>
@@ -2196,6 +2232,11 @@ Dies kann nicht rückgängig gemacht werden!</target>
<target>Für Alle deaktivieren</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Disabled" xml:space="preserve">
<source>Disabled</source>
<target>Deaktiviert</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Disappearing message" xml:space="preserve">
<source>Disappearing message</source>
<target>Verschwindende Nachricht</target>
@@ -2421,6 +2462,11 @@ Dies kann nicht rückgängig gemacht werden!</target>
<target>Selbstzerstörungs-Zugangscode aktivieren</target>
<note>set passcode view</note>
</trans-unit>
<trans-unit id="Enabled" xml:space="preserve">
<source>Enabled</source>
<target>Aktiviert</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enabled for" xml:space="preserve">
<source>Enabled for</source>
<target>Aktiviert für</target>
@@ -2591,6 +2637,11 @@ Dies kann nicht rückgängig gemacht werden!</target>
<target>Fehler beim Ändern der Einstellung</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error connecting to forwarding server %@. Please try later." xml:space="preserve">
<source>Error connecting to forwarding server %@. Please try later.</source>
<target>Fehler beim Verbinden mit dem Weiterleitungsserver %@. Bitte versuchen Sie es später erneut.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating address" xml:space="preserve">
<source>Error creating address</source>
<target>Fehler beim Erstellen der Adresse</target>
@@ -3097,6 +3148,21 @@ Dies kann nicht rückgängig gemacht werden!</target>
<target>Weitergeleitet aus</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<target>Weiterleitungsserver %@ konnte sich nicht mit dem Zielserver %@ verbinden. Bitte versuchen Sie es später erneut.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarding server address is incompatible with network settings: %@." xml:space="preserve">
<source>Forwarding server address is incompatible with network settings: %@.</source>
<target>Adresse des Weiterleitungsservers ist nicht kompatibel mit den Netzwerkeinstellungen: %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarding server version is incompatible with network settings: %@." xml:space="preserve">
<source>Forwarding server version is incompatible with network settings: %@.</source>
<target>Version des Weiterleitungsservers ist nicht kompatibel mit den Netzwerkeinstellungen: %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarding server: %@&#10;Destination server error: %@" xml:space="preserve">
<source>Forwarding server: %1$@
Destination server error: %2$@</source>
@@ -3523,12 +3589,12 @@ Fehler: %2$@</target>
</trans-unit>
<trans-unit id="Incompatible database version" xml:space="preserve">
<source>Incompatible database version</source>
<target>Inkompatible Datenbank-Version</target>
<target>Datenbank-Version nicht kompatibel</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incompatible version" xml:space="preserve">
<source>Incompatible version</source>
<target>Inkompatible Version</target>
<target>Version nicht kompatibel</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incorrect passcode" xml:space="preserve">
@@ -3916,6 +3982,15 @@ Das ist Ihr Link für die Gruppe %@!</target>
<target>Max. 30 Sekunden, sofort erhalten.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Media &amp; file servers" xml:space="preserve">
<source>Media &amp; file servers</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Medium" xml:space="preserve">
<source>Medium</source>
<target>Medium</target>
<note>blur media</note>
</trans-unit>
<trans-unit id="Member" xml:space="preserve">
<source>Member</source>
<target>Mitglied</target>
@@ -3998,16 +4073,11 @@ Das ist Ihr Link für die Gruppe %@!</target>
</trans-unit>
<trans-unit id="Message reception" xml:space="preserve">
<source>Message reception</source>
<target>Nachrichtenempfang</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message routing fallback" xml:space="preserve">
<source>Message routing fallback</source>
<target>Fallback für das Nachrichten-Routing</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message routing mode" xml:space="preserve">
<source>Message routing mode</source>
<target>Modus für das Nachrichten-Routing</target>
<trans-unit id="Message servers" xml:space="preserve">
<source>Message servers</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message source remains private." xml:space="preserve">
@@ -4340,6 +4410,11 @@ Das ist Ihr Link für die Gruppe %@!</target>
<target>Nicht kompatibel!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Nothing selected" xml:space="preserve">
<source>Nothing selected</source>
<target>Nichts ausgewählt</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>Benachrichtigungen</target>
@@ -4367,7 +4442,7 @@ Das ist Ihr Link für die Gruppe %@!</target>
<trans-unit id="Off" xml:space="preserve">
<source>Off</source>
<target>Aus</target>
<note>No comment provided by engineer.</note>
<note>blur media</note>
</trans-unit>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
@@ -4389,14 +4464,18 @@ Das ist Ihr Link für die Gruppe %@!</target>
<target>Einmal-Einladungslink</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Onion hosts will be required for connection. Requires enabling VPN." xml:space="preserve">
<source>Onion hosts will be required for connection. Requires enabling VPN.</source>
<target>Für diese Verbindung werden Onion-Hosts benötigt. Dies erfordert die Aktivierung eines VPNs.</target>
<trans-unit id="Onion hosts will be **required** for connection.&#10;Requires compatible VPN." xml:space="preserve">
<source>Onion hosts will be **required** for connection.
Requires compatible VPN.</source>
<target>Für diese Verbindung werden Onion-Hosts benötigt.
Dies erfordert die Aktivierung eines VPNs.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Onion hosts will be used when available. Requires enabling VPN." xml:space="preserve">
<source>Onion hosts will be used when available. Requires enabling VPN.</source>
<target>Wenn Onion-Hosts verfügbar sind, werden sie verwendet. Dies erfordert die Aktivierung eines VPNs.</target>
<trans-unit id="Onion hosts will be used when available.&#10;Requires compatible VPN." xml:space="preserve">
<source>Onion hosts will be used when available.
Requires compatible VPN.</source>
<target>Wenn Onion-Hosts verfügbar sind, werden sie verwendet.
Dies erfordert die Aktivierung eines VPNs.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Onion hosts will not be used." xml:space="preserve">
@@ -4551,6 +4630,7 @@ Das ist Ihr Link für die Gruppe %@!</target>
</trans-unit>
<trans-unit id="Other %@ servers" xml:space="preserve">
<source>Other %@ servers</source>
<target>Andere %@ Server</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="PING count" xml:space="preserve">
@@ -4722,10 +4802,6 @@ Fehler: %@</target>
<target>Bitte bewahren Sie das Passwort sicher auf, Sie können es NICHT mehr ändern, wenn Sie es vergessen haben oder verlieren.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Please try later." xml:space="preserve">
<source>Please try later.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Polish interface" xml:space="preserve">
<source>Polish interface</source>
<target>Polnische Bedienoberfläche</target>
@@ -4798,6 +4874,7 @@ Fehler: %@</target>
</trans-unit>
<trans-unit id="Private routing error" xml:space="preserve">
<source>Private routing error</source>
<target>Fehler beim privaten Routing</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile and server connections" xml:space="preserve">
@@ -4919,7 +4996,7 @@ Aktivieren Sie es in den *Netzwerk &amp; Server* Einstellungen.</target>
</trans-unit>
<trans-unit id="Proxied" xml:space="preserve">
<source>Proxied</source>
<target>Proxy</target>
<target>Proxied</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Proxied servers" xml:space="preserve">
@@ -5287,11 +5364,6 @@ Aktivieren Sie es in den *Netzwerk &amp; Server* Einstellungen.</target>
<target>Aufdecken</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Revert" xml:space="preserve">
<source>Revert</source>
<target>Zurückkehren</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>Widerrufen</target>
@@ -5322,11 +5394,6 @@ Aktivieren Sie es in den *Netzwerk &amp; Server* Einstellungen.</target>
<target>SMP-Server</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="SMP servers" xml:space="preserve">
<source>SMP servers</source>
<target>SMP-Server</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Safely receive files" xml:space="preserve">
<source>Safely receive files</source>
<target>Dateien sicher empfangen</target>
@@ -5357,6 +5424,10 @@ Aktivieren Sie es in den *Netzwerk &amp; Server* Einstellungen.</target>
<target>Speichern und Gruppenmitglieder benachrichtigen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Save and reconnect" xml:space="preserve">
<source>Save and reconnect</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Save and update group profile" xml:space="preserve">
<source>Save and update group profile</source>
<target>Gruppen-Profil sichern und aktualisieren</target>
@@ -5515,6 +5586,11 @@ Aktivieren Sie es in den *Netzwerk &amp; Server* Einstellungen.</target>
<trans-unit id="Select" xml:space="preserve">
<source>Select</source>
<target>Auswählen</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Selected %lld" xml:space="preserve">
<source>Selected %lld</source>
<target>%lld ausgewählt</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
@@ -5734,11 +5810,12 @@ Aktivieren Sie es in den *Netzwerk &amp; Server* Einstellungen.</target>
</trans-unit>
<trans-unit id="Server address is incompatible with network settings." xml:space="preserve">
<source>Server address is incompatible with network settings.</source>
<target>Die Server-Adresse ist nicht mit den Netzwerk-Einstellungen kompatibel.</target>
<target>Die Server-Adresse ist nicht mit den Netzwerkeinstellungen kompatibel.</target>
<note>srv error text.</note>
</trans-unit>
<trans-unit id="Server address is incompatible with network settings: %@." xml:space="preserve">
<source>Server address is incompatible with network settings: %@.</source>
<target>Die Server-Adresse ist nicht mit den Netzwerkeinstellungen kompatibel: %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Server requires authorization to create queues, check password" xml:space="preserve">
@@ -5763,15 +5840,12 @@ Aktivieren Sie es in den *Netzwerk &amp; Server* Einstellungen.</target>
</trans-unit>
<trans-unit id="Server version is incompatible with network settings." xml:space="preserve">
<source>Server version is incompatible with network settings.</source>
<target>Die Server-Version ist nicht mit den Netzwerk-Einstellungen kompatibel.</target>
<target>Die Server-Version ist nicht mit den Netzwerkeinstellungen kompatibel.</target>
<note>srv error text</note>
</trans-unit>
<trans-unit id="Server version is incompatible with network settings: %@." xml:space="preserve">
<source>Server version is incompatible with network settings: %@.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Server version is incompatible with your app: %@." xml:space="preserve">
<source>Server version is incompatible with your app: %@.</source>
<target>Die Server-Version ist nicht mit Ihrer App kompatibel: %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Servers" xml:space="preserve">
@@ -5884,6 +5958,11 @@ Aktivieren Sie es in den *Netzwerk &amp; Server* Einstellungen.</target>
<target>Teilen Sie diesen Einmal-Einladungslink</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Share to SimpleX" xml:space="preserve">
<source>Share to SimpleX</source>
<target>Mit SimpleX teilen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Share with contacts" xml:space="preserve">
<source>Share with contacts</source>
<target>Mit Kontakten teilen</target>
@@ -5916,6 +5995,7 @@ Aktivieren Sie es in den *Netzwerk &amp; Server* Einstellungen.</target>
</trans-unit>
<trans-unit id="Show percentage" xml:space="preserve">
<source>Show percentage</source>
<target>Prozentualen Anteil anzeigen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Show preview" xml:space="preserve">
@@ -6033,11 +6113,24 @@ Aktivieren Sie es in den *Netzwerk &amp; Server* Einstellungen.</target>
<target>Kleine Gruppen (max. 20)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Soft" xml:space="preserve">
<source>Soft</source>
<target>Weich</target>
<note>blur media</note>
</trans-unit>
<trans-unit id="Some file(s) were not exported:" xml:space="preserve">
<source>Some file(s) were not exported:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Some non-fatal errors occurred during import - you may see Chat console for more details." xml:space="preserve">
<source>Some non-fatal errors occurred during import - you may see Chat console for more details.</source>
<target>Während des Imports sind einige nicht schwerwiegende Fehler aufgetreten - in der Chat-Konsole finden Sie weitere Einzelheiten.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Some non-fatal errors occurred during import:" xml:space="preserve">
<source>Some non-fatal errors occurred during import:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Somebody" xml:space="preserve">
<source>Somebody</source>
<target>Jemand</target>
@@ -6133,6 +6226,11 @@ Aktivieren Sie es in den *Netzwerk &amp; Server* Einstellungen.</target>
<target>Chat wird beendet</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Strong" xml:space="preserve">
<source>Strong</source>
<target>Hart</target>
<note>blur media</note>
</trans-unit>
<trans-unit id="Submit" xml:space="preserve">
<source>Submit</source>
<target>Bestätigen</target>
@@ -6168,6 +6266,10 @@ Aktivieren Sie es in den *Netzwerk &amp; Server* Einstellungen.</target>
<target>System-Authentifizierung</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="TCP connection" xml:space="preserve">
<source>TCP connection</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="TCP connection timeout" xml:space="preserve">
<source>TCP connection timeout</source>
<target>Timeout der TCP-Verbindung</target>
@@ -6340,6 +6442,16 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro
<target>Diese Nachricht wird für alle Mitglieder als moderiert gekennzeichnet.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The messages will be deleted for all members." xml:space="preserve">
<source>The messages will be deleted for all members.</source>
<target>Die Nachrichten werden für alle Mitglieder gelöscht werden.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<target>Die Nachrichten werden für alle Mitglieder als moderiert gekennzeichnet werden.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The next generation of private messaging" xml:space="preserve">
<source>The next generation of private messaging</source>
<target>Die nächste Generation von privatem Messaging</target>
@@ -6392,12 +6504,12 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro
</trans-unit>
<trans-unit id="This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." xml:space="preserve">
<source>This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain.</source>
<target>Diese Aktion kann nicht rückgängig gemacht werden! Alle empfangenen und gesendeten Dateien und Medien werden gelöscht. Bilder mit niedriger Auflösung bleiben erhalten.</target>
<target>Diese Aktion kann nicht rückgängig gemacht werden! Es werden alle empfangenen und gesendeten Dateien und Medien gelöscht. Bilder mit niedriger Auflösung bleiben erhalten.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." xml:space="preserve">
<source>This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes.</source>
<target>Diese Aktion kann nicht rückgängig gemacht werden! Alle empfangenen und gesendeten Nachrichten, die über den ausgewählten Zeitraum hinaus gehen, werden gelöscht. Dieser Vorgang kann mehrere Minuten dauern.</target>
<target>Diese Aktion kann nicht rückgängig gemacht werden! Es werden alle empfangenen und gesendeten Nachrichten, die über den ausgewählten Zeitraum hinaus gehen, gelöscht. Dieser Vorgang kann mehrere Minuten dauern.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." xml:space="preserve">
@@ -6699,11 +6811,6 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
<target>Aktualisieren</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Update .onion hosts setting?" xml:space="preserve">
<source>Update .onion hosts setting?</source>
<target>Einstellung für .onion-Hosts aktualisieren?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Update database passphrase" xml:space="preserve">
<source>Update database passphrase</source>
<target>Datenbank-Passwort aktualisieren</target>
@@ -6714,9 +6821,8 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
<target>Netzwerkeinstellungen aktualisieren?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Update transport isolation mode?" xml:space="preserve">
<source>Update transport isolation mode?</source>
<target>Transport-Isolations-Modus aktualisieren?</target>
<trans-unit id="Update settings?" xml:space="preserve">
<source>Update settings?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Updating settings will re-connect the client to all servers." xml:space="preserve">
@@ -6724,11 +6830,6 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
<target>Die Aktualisierung der Einstellungen wird den Client wieder mit allen Servern verbinden.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Updating this setting will re-connect the client to all servers." xml:space="preserve">
<source>Updating this setting will re-connect the client to all servers.</source>
<target>Die Aktualisierung dieser Einstellung wird den Client wieder mit allen Servern verbinden.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Upgrade and open chat" xml:space="preserve">
<source>Upgrade and open chat</source>
<target>Aktualisieren und den Chat öffnen</target>
@@ -6839,11 +6940,6 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
<target>Benutzer-Auswahl</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Using .onion hosts requires compatible VPN provider." xml:space="preserve">
<source>Using .onion hosts requires compatible VPN provider.</source>
<target>Für die Nutzung von .onion-Hosts sind kompatible VPN-Anbieter erforderlich.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Using SimpleX Chat servers." xml:space="preserve">
<source>Using SimpleX Chat servers.</source>
<target>Verwendung von SimpleX-Chat-Servern.</target>
@@ -7104,11 +7200,6 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
<target>XFTP-Server</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="XFTP servers" xml:space="preserve">
<source>XFTP servers</source>
<target>XFTP-Server</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You" xml:space="preserve">
<source>You</source>
<target>Profil</target>
@@ -7323,6 +7414,14 @@ Verbindungsanfrage wiederholen?</target>
<target>Sie sind dieser Gruppe beigetreten. Sie werden mit dem einladenden Gruppenmitglied verbunden.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You may migrate the exported database." xml:space="preserve">
<source>You may migrate the exported database.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You may save the exported archive." xml:space="preserve">
<source>You may save the exported archive.</source>
<note>No comment provided by engineer.</note>
</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">
<source>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.</source>
<target>Sie dürfen die neueste Version Ihrer Chat-Datenbank NUR auf einem Gerät verwenden, andernfalls erhalten Sie möglicherweise keine Nachrichten mehr von einigen Ihrer Kontakte.</target>
@@ -8303,8 +8402,8 @@ Zuletzt empfangene Nachricht: %2$@</target>
<target>Unbekannt</target>
<note>connection info</note>
</trans-unit>
<trans-unit id="unknown relays" xml:space="preserve">
<source>unknown relays</source>
<trans-unit id="unknown servers" xml:space="preserve">
<source>unknown servers</source>
<target>Unbekannte Relais</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -8529,4 +8628,214 @@ Zuletzt empfangene Nachricht: %2$@</target>
</trans-unit>
</body>
</file>
<file original="SimpleX SE/en.lproj/InfoPlist.strings" source-language="en" target-language="de" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
</header>
<body>
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
<source>SimpleX SE</source>
<target>SimpleX SE</target>
<note>Bundle display name</note>
</trans-unit>
<trans-unit id="CFBundleName" xml:space="preserve">
<source>SimpleX SE</source>
<target>SimpleX SE</target>
<note>Bundle name</note>
</trans-unit>
<trans-unit id="NSHumanReadableCopyright" xml:space="preserve">
<source>Copyright © 2024 SimpleX Chat. All rights reserved.</source>
<target>Copyright © 2024 SimpleX Chat. Alle Rechte vorbehalten.</target>
<note>Copyright (human-readable)</note>
</trans-unit>
</body>
</file>
<file original="SimpleX SE/en.lproj/Localizable.strings" source-language="en" target-language="de" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
</header>
<body>
<trans-unit id="%@" xml:space="preserve">
<source>%@</source>
<target>%@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="App is locked!" xml:space="preserve">
<source>App is locked!</source>
<target>Die App ist gesperrt!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cancel" xml:space="preserve">
<source>Cancel</source>
<target>Abbrechen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cannot access keychain to save database password" xml:space="preserve">
<source>Cannot access keychain to save database password</source>
<target>Es ist nicht möglich, auf den Schlüsselbund zuzugreifen, um das Datenbankpasswort zu speichern</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cannot forward message" xml:space="preserve">
<source>Cannot forward message</source>
<target>Nachricht kann nicht weitergeleitet werden</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Comment" xml:space="preserve">
<source>Comment</source>
<target>Kommentieren</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Currently maximum supported file size is %@." xml:space="preserve">
<source>Currently maximum supported file size is %@.</source>
<target>Die maximale erlaubte Dateigröße beträgt aktuell %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database downgrade required" xml:space="preserve">
<source>Database downgrade required</source>
<target>Datenbank-Herabstufung erforderlich</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database encrypted!" xml:space="preserve">
<source>Database encrypted!</source>
<target>Datenbank verschlüsselt!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database error" xml:space="preserve">
<source>Database error</source>
<target>Datenbankfehler</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<target>Das Datenbank-Passwort unterscheidet sich vom im Schlüsselbund gespeicherten.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database passphrase is required to open chat." xml:space="preserve">
<source>Database passphrase is required to open chat.</source>
<target>Ein Datenbank-Passwort ist erforderlich, um den Chat zu öffnen.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database upgrade required" xml:space="preserve">
<source>Database upgrade required</source>
<target>Datenbank-Aktualisierung erforderlich</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error preparing file" xml:space="preserve">
<source>Error preparing file</source>
<target>Fehler beim Vorbereiten der Datei</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error preparing message" xml:space="preserve">
<source>Error preparing message</source>
<target>Fehler beim Vorbereiten der Nachricht</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error: %@" xml:space="preserve">
<source>Error: %@</source>
<target>Fehler: %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="File error" xml:space="preserve">
<source>File error</source>
<target>Dateifehler</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incompatible database version" xml:space="preserve">
<source>Incompatible database version</source>
<target>Datenbank-Version nicht kompatibel</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Invalid migration confirmation" xml:space="preserve">
<source>Invalid migration confirmation</source>
<target>Migrations-Bestätigung ungültig</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Keychain error" xml:space="preserve">
<source>Keychain error</source>
<target>Schlüsselbund-Fehler</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Large file!" xml:space="preserve">
<source>Large file!</source>
<target>Große Datei!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No active profile" xml:space="preserve">
<source>No active profile</source>
<target>Kein aktives Profil</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<target>OK</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open the app to downgrade the database." xml:space="preserve">
<source>Open the app to downgrade the database.</source>
<target>Öffne die App, um die Datenbank herabzustufen.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open the app to upgrade the database." xml:space="preserve">
<source>Open the app to upgrade the database.</source>
<target>Öffne die App, um die Datenbank zu aktualisieren.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Passphrase" xml:space="preserve">
<source>Passphrase</source>
<target>Passwort</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Please create a profile in the SimpleX app" xml:space="preserve">
<source>Please create a profile in the SimpleX app</source>
<target>Bitte erstelle ein Profil in der SimpleX-App</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
<source>Selected chat preferences prohibit this message.</source>
<target>Diese Nachricht ist wegen der gewählten Chat-Einstellungen nicht erlaubt.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sending a message takes longer than expected." xml:space="preserve">
<source>Sending a message takes longer than expected.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sending message…" xml:space="preserve">
<source>Sending message…</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Share" xml:space="preserve">
<source>Share</source>
<target>Teilen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Slow network?" xml:space="preserve">
<source>Slow network?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unknown database error: %@" xml:space="preserve">
<source>Unknown database error: %@</source>
<target>Unbekannter Datenbankfehler: %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unsupported format" xml:space="preserve">
<source>Unsupported format</source>
<target>Nicht unterstütztes Format</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Wait" xml:space="preserve">
<source>Wait</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Wrong database passphrase" xml:space="preserve">
<source>Wrong database passphrase</source>
<target>Falsches Datenbank-Passwort</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<target>Du kannst das Teilen in den Einstellungen zu Datenschutz &amp; Sicherheit bzw. SimpleX-Sperre erlauben.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
</body>
</file>
</xliff>
@@ -1,6 +1,9 @@
/* Bundle display name */
"CFBundleDisplayName" = "SimpleX NSE";
/* Bundle name */
"CFBundleName" = "SimpleX NSE";
/* Copyright (human-readable) */
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";
@@ -0,0 +1,7 @@
/*
Localizable.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -0,0 +1,7 @@
/*
InfoPlist.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -0,0 +1,7 @@
/*
Localizable.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -783,6 +783,11 @@
<target>Allow sending disappearing messages.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Allow sharing" xml:space="preserve">
<source>Allow sharing</source>
<target>Allow sharing</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Allow to irreversibly delete sent messages. (24 hours)" xml:space="preserve">
<source>Allow to irreversibly delete sent messages. (24 hours)</source>
<target>Allow to irreversibly delete sent messages. (24 hours)</target>
@@ -1073,6 +1078,11 @@
<target>Blocked by admin</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Blur media" xml:space="preserve">
<source>Blur media</source>
<target>Blur media</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
<source>Both you and your contact can add message reactions.</source>
<target>Both you and your contact can add message reactions.</target>
@@ -1244,6 +1254,11 @@
<target>Chat database deleted</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat database exported" xml:space="preserve">
<source>Chat database exported</source>
<target>Chat database exported</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat database imported" xml:space="preserve">
<source>Chat database imported</source>
<target>Chat database imported</target>
@@ -1538,6 +1553,11 @@ This is your own one-time link!</target>
<target>Connection error (AUTH)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection notifications" xml:space="preserve">
<source>Connection notifications</source>
<target>Connection notifications</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection request sent!" xml:space="preserve">
<source>Connection request sent!</source>
<target>Connection request sent!</target>
@@ -1886,6 +1906,11 @@ This is your own one-time link!</target>
<target>Delete</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Delete %lld messages of members?" xml:space="preserve">
<source>Delete %lld messages of members?</source>
<target>Delete %lld messages of members?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete %lld messages?" xml:space="preserve">
<source>Delete %lld messages?</source>
<target>Delete %lld messages?</target>
@@ -2128,11 +2153,21 @@ This cannot be undone!</target>
<target>Desktop devices</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<target>Destination server address of %@ is incompatible with forwarding server %@ settings.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Destination server error: %@" xml:space="preserve">
<source>Destination server error: %@</source>
<target>Destination server error: %@</target>
<note>snd error text</note>
</trans-unit>
<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>
<target>Destination server version of %@ is incompatible with forwarding server %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Detailed statistics" xml:space="preserve">
<source>Detailed statistics</source>
<target>Detailed statistics</target>
@@ -2198,6 +2233,11 @@ This cannot be undone!</target>
<target>Disable for all</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Disabled" xml:space="preserve">
<source>Disabled</source>
<target>Disabled</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Disappearing message" xml:space="preserve">
<source>Disappearing message</source>
<target>Disappearing message</target>
@@ -2423,6 +2463,11 @@ This cannot be undone!</target>
<target>Enable self-destruct passcode</target>
<note>set passcode view</note>
</trans-unit>
<trans-unit id="Enabled" xml:space="preserve">
<source>Enabled</source>
<target>Enabled</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enabled for" xml:space="preserve">
<source>Enabled for</source>
<target>Enabled for</target>
@@ -2593,6 +2638,11 @@ This cannot be undone!</target>
<target>Error changing setting</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error connecting to forwarding server %@. Please try later." xml:space="preserve">
<source>Error connecting to forwarding server %@. Please try later.</source>
<target>Error connecting to forwarding server %@. Please try later.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating address" xml:space="preserve">
<source>Error creating address</source>
<target>Error creating address</target>
@@ -3099,6 +3149,21 @@ This cannot be undone!</target>
<target>Forwarded from</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<target>Forwarding server %@ failed to connect to destination server %@. Please try later.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarding server address is incompatible with network settings: %@." xml:space="preserve">
<source>Forwarding server address is incompatible with network settings: %@.</source>
<target>Forwarding server address is incompatible with network settings: %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarding server version is incompatible with network settings: %@." xml:space="preserve">
<source>Forwarding server version is incompatible with network settings: %@.</source>
<target>Forwarding server version is incompatible with network settings: %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarding server: %@&#10;Destination server error: %@" xml:space="preserve">
<source>Forwarding server: %1$@
Destination server error: %2$@</source>
@@ -3918,6 +3983,16 @@ This is your link for group %@!</target>
<target>Max 30 seconds, received instantly.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Media &amp; file servers" xml:space="preserve">
<source>Media &amp; file servers</source>
<target>Media &amp; file servers</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Medium" xml:space="preserve">
<source>Medium</source>
<target>Medium</target>
<note>blur media</note>
</trans-unit>
<trans-unit id="Member" xml:space="preserve">
<source>Member</source>
<target>Member</target>
@@ -4003,14 +4078,9 @@ This is your link for group %@!</target>
<target>Message reception</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message routing fallback" xml:space="preserve">
<source>Message routing fallback</source>
<target>Message routing fallback</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message routing mode" xml:space="preserve">
<source>Message routing mode</source>
<target>Message routing mode</target>
<trans-unit id="Message servers" xml:space="preserve">
<source>Message servers</source>
<target>Message servers</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message source remains private." xml:space="preserve">
@@ -4343,6 +4413,11 @@ This is your link for group %@!</target>
<target>Not compatible!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Nothing selected" xml:space="preserve">
<source>Nothing selected</source>
<target>Nothing selected</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>Notifications</target>
@@ -4370,7 +4445,7 @@ This is your link for group %@!</target>
<trans-unit id="Off" xml:space="preserve">
<source>Off</source>
<target>Off</target>
<note>No comment provided by engineer.</note>
<note>blur media</note>
</trans-unit>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
@@ -4392,14 +4467,18 @@ This is your link for group %@!</target>
<target>One-time invitation link</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Onion hosts will be required for connection. Requires enabling VPN." xml:space="preserve">
<source>Onion hosts will be required for connection. Requires enabling VPN.</source>
<target>Onion hosts will be required for connection. Requires enabling VPN.</target>
<trans-unit id="Onion hosts will be **required** for connection.&#10;Requires compatible VPN." xml:space="preserve">
<source>Onion hosts will be **required** for connection.
Requires compatible VPN.</source>
<target>Onion hosts will be **required** for connection.
Requires compatible VPN.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Onion hosts will be used when available. Requires enabling VPN." xml:space="preserve">
<source>Onion hosts will be used when available. Requires enabling VPN.</source>
<target>Onion hosts will be used when available. Requires enabling VPN.</target>
<trans-unit id="Onion hosts will be used when available.&#10;Requires compatible VPN." xml:space="preserve">
<source>Onion hosts will be used when available.
Requires compatible VPN.</source>
<target>Onion hosts will be used when available.
Requires compatible VPN.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Onion hosts will not be used." xml:space="preserve">
@@ -4726,11 +4805,6 @@ Error: %@</target>
<target>Please store passphrase securely, you will NOT be able to change it if you lose it.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Please try later." xml:space="preserve">
<source>Please try later.</source>
<target>Please try later.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Polish interface" xml:space="preserve">
<source>Polish interface</source>
<target>Polish interface</target>
@@ -5293,11 +5367,6 @@ Enable in *Network &amp; servers* settings.</target>
<target>Reveal</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Revert" xml:space="preserve">
<source>Revert</source>
<target>Revert</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>Revoke</target>
@@ -5328,11 +5397,6 @@ Enable in *Network &amp; servers* settings.</target>
<target>SMP server</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="SMP servers" xml:space="preserve">
<source>SMP servers</source>
<target>SMP servers</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Safely receive files" xml:space="preserve">
<source>Safely receive files</source>
<target>Safely receive files</target>
@@ -5363,6 +5427,11 @@ Enable in *Network &amp; servers* settings.</target>
<target>Save and notify group members</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Save and reconnect" xml:space="preserve">
<source>Save and reconnect</source>
<target>Save and reconnect</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Save and update group profile" xml:space="preserve">
<source>Save and update group profile</source>
<target>Save and update group profile</target>
@@ -5521,6 +5590,11 @@ Enable in *Network &amp; servers* settings.</target>
<trans-unit id="Select" xml:space="preserve">
<source>Select</source>
<target>Select</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Selected %lld" xml:space="preserve">
<source>Selected %lld</source>
<target>Selected %lld</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
@@ -5773,11 +5847,6 @@ Enable in *Network &amp; servers* settings.</target>
<target>Server version is incompatible with network settings.</target>
<note>srv error text</note>
</trans-unit>
<trans-unit id="Server version is incompatible with network settings: %@." xml:space="preserve">
<source>Server version is incompatible with network settings: %@.</source>
<target>Server version is incompatible with network settings: %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Server version is incompatible with your app: %@." xml:space="preserve">
<source>Server version is incompatible with your app: %@.</source>
<target>Server version is incompatible with your app: %@.</target>
@@ -5893,6 +5962,11 @@ Enable in *Network &amp; servers* settings.</target>
<target>Share this 1-time invite link</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Share to SimpleX" xml:space="preserve">
<source>Share to SimpleX</source>
<target>Share to SimpleX</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Share with contacts" xml:space="preserve">
<source>Share with contacts</source>
<target>Share with contacts</target>
@@ -6043,11 +6117,26 @@ Enable in *Network &amp; servers* settings.</target>
<target>Small groups (max 20)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Soft" xml:space="preserve">
<source>Soft</source>
<target>Soft</target>
<note>blur media</note>
</trans-unit>
<trans-unit id="Some file(s) were not exported:" xml:space="preserve">
<source>Some file(s) were not exported:</source>
<target>Some file(s) were not exported:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Some non-fatal errors occurred during import - you may see Chat console for more details." xml:space="preserve">
<source>Some non-fatal errors occurred during import - you may see Chat console for more details.</source>
<target>Some non-fatal errors occurred during import - you may see Chat console for more details.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Some non-fatal errors occurred during import:" xml:space="preserve">
<source>Some non-fatal errors occurred during import:</source>
<target>Some non-fatal errors occurred during import:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Somebody" xml:space="preserve">
<source>Somebody</source>
<target>Somebody</target>
@@ -6143,6 +6232,11 @@ Enable in *Network &amp; servers* settings.</target>
<target>Stopping chat</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Strong" xml:space="preserve">
<source>Strong</source>
<target>Strong</target>
<note>blur media</note>
</trans-unit>
<trans-unit id="Submit" xml:space="preserve">
<source>Submit</source>
<target>Submit</target>
@@ -6178,6 +6272,11 @@ Enable in *Network &amp; servers* settings.</target>
<target>System authentication</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="TCP connection" xml:space="preserve">
<source>TCP connection</source>
<target>TCP connection</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="TCP connection timeout" xml:space="preserve">
<source>TCP connection timeout</source>
<target>TCP connection timeout</target>
@@ -6350,6 +6449,16 @@ It can happen because of some bug or when the connection is compromised.</target
<target>The message will be marked as moderated for all members.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The messages will be deleted for all members." xml:space="preserve">
<source>The messages will be deleted for all members.</source>
<target>The messages will be deleted for all members.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<target>The messages will be marked as moderated for all members.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The next generation of private messaging" xml:space="preserve">
<source>The next generation of private messaging</source>
<target>The next generation of private messaging</target>
@@ -6709,11 +6818,6 @@ To connect, please ask your contact to create another connection link and check
<target>Update</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Update .onion hosts setting?" xml:space="preserve">
<source>Update .onion hosts setting?</source>
<target>Update .onion hosts setting?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Update database passphrase" xml:space="preserve">
<source>Update database passphrase</source>
<target>Update database passphrase</target>
@@ -6724,9 +6828,9 @@ To connect, please ask your contact to create another connection link and check
<target>Update network settings?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Update transport isolation mode?" xml:space="preserve">
<source>Update transport isolation mode?</source>
<target>Update transport isolation mode?</target>
<trans-unit id="Update settings?" xml:space="preserve">
<source>Update settings?</source>
<target>Update settings?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Updating settings will re-connect the client to all servers." xml:space="preserve">
@@ -6734,11 +6838,6 @@ To connect, please ask your contact to create another connection link and check
<target>Updating settings will re-connect the client to all servers.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Updating this setting will re-connect the client to all servers." xml:space="preserve">
<source>Updating this setting will re-connect the client to all servers.</source>
<target>Updating this setting will re-connect the client to all servers.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Upgrade and open chat" xml:space="preserve">
<source>Upgrade and open chat</source>
<target>Upgrade and open chat</target>
@@ -6849,11 +6948,6 @@ To connect, please ask your contact to create another connection link and check
<target>User selection</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Using .onion hosts requires compatible VPN provider." xml:space="preserve">
<source>Using .onion hosts requires compatible VPN provider.</source>
<target>Using .onion hosts requires compatible VPN provider.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Using SimpleX Chat servers." xml:space="preserve">
<source>Using SimpleX Chat servers.</source>
<target>Using SimpleX Chat servers.</target>
@@ -7114,11 +7208,6 @@ To connect, please ask your contact to create another connection link and check
<target>XFTP server</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="XFTP servers" xml:space="preserve">
<source>XFTP servers</source>
<target>XFTP servers</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You" xml:space="preserve">
<source>You</source>
<target>You</target>
@@ -7333,6 +7422,16 @@ Repeat connection request?</target>
<target>You joined this group. Connecting to inviting group member.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You may migrate the exported database." xml:space="preserve">
<source>You may migrate the exported database.</source>
<target>You may migrate the exported database.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You may save the exported archive." xml:space="preserve">
<source>You may save the exported archive.</source>
<target>You may save the exported archive.</target>
<note>No comment provided by engineer.</note>
</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">
<source>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.</source>
<target>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.</target>
@@ -8313,9 +8412,9 @@ last received msg: %2$@</target>
<target>unknown</target>
<note>connection info</note>
</trans-unit>
<trans-unit id="unknown relays" xml:space="preserve">
<source>unknown relays</source>
<target>unknown relays</target>
<trans-unit id="unknown servers" xml:space="preserve">
<source>unknown servers</source>
<target>unknown servers</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="unknown status" xml:space="preserve">
@@ -8539,4 +8638,218 @@ last received msg: %2$@</target>
</trans-unit>
</body>
</file>
<file original="SimpleX SE/en.lproj/InfoPlist.strings" source-language="en" target-language="en" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
</header>
<body>
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
<source>SimpleX SE</source>
<target>SimpleX SE</target>
<note>Bundle display name</note>
</trans-unit>
<trans-unit id="CFBundleName" xml:space="preserve">
<source>SimpleX SE</source>
<target>SimpleX SE</target>
<note>Bundle name</note>
</trans-unit>
<trans-unit id="NSHumanReadableCopyright" xml:space="preserve">
<source>Copyright © 2024 SimpleX Chat. All rights reserved.</source>
<target>Copyright © 2024 SimpleX Chat. All rights reserved.</target>
<note>Copyright (human-readable)</note>
</trans-unit>
</body>
</file>
<file original="SimpleX SE/en.lproj/Localizable.strings" source-language="en" target-language="en" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
</header>
<body>
<trans-unit id="%@" xml:space="preserve">
<source>%@</source>
<target>%@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="App is locked!" xml:space="preserve">
<source>App is locked!</source>
<target>App is locked!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cancel" xml:space="preserve">
<source>Cancel</source>
<target>Cancel</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cannot access keychain to save database password" xml:space="preserve">
<source>Cannot access keychain to save database password</source>
<target>Cannot access keychain to save database password</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cannot forward message" xml:space="preserve">
<source>Cannot forward message</source>
<target>Cannot forward message</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Comment" xml:space="preserve">
<source>Comment</source>
<target>Comment</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Currently maximum supported file size is %@." xml:space="preserve">
<source>Currently maximum supported file size is %@.</source>
<target>Currently maximum supported file size is %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database downgrade required" xml:space="preserve">
<source>Database downgrade required</source>
<target>Database downgrade required</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database encrypted!" xml:space="preserve">
<source>Database encrypted!</source>
<target>Database encrypted!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database error" xml:space="preserve">
<source>Database error</source>
<target>Database error</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<target>Database passphrase is different from saved in the keychain.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database passphrase is required to open chat." xml:space="preserve">
<source>Database passphrase is required to open chat.</source>
<target>Database passphrase is required to open chat.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database upgrade required" xml:space="preserve">
<source>Database upgrade required</source>
<target>Database upgrade required</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error preparing file" xml:space="preserve">
<source>Error preparing file</source>
<target>Error preparing file</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error preparing message" xml:space="preserve">
<source>Error preparing message</source>
<target>Error preparing message</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error: %@" xml:space="preserve">
<source>Error: %@</source>
<target>Error: %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="File error" xml:space="preserve">
<source>File error</source>
<target>File error</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incompatible database version" xml:space="preserve">
<source>Incompatible database version</source>
<target>Incompatible database version</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Invalid migration confirmation" xml:space="preserve">
<source>Invalid migration confirmation</source>
<target>Invalid migration confirmation</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Keychain error" xml:space="preserve">
<source>Keychain error</source>
<target>Keychain error</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Large file!" xml:space="preserve">
<source>Large file!</source>
<target>Large file!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No active profile" xml:space="preserve">
<source>No active profile</source>
<target>No active profile</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<target>Ok</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open the app to downgrade the database." xml:space="preserve">
<source>Open the app to downgrade the database.</source>
<target>Open the app to downgrade the database.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open the app to upgrade the database." xml:space="preserve">
<source>Open the app to upgrade the database.</source>
<target>Open the app to upgrade the database.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Passphrase" xml:space="preserve">
<source>Passphrase</source>
<target>Passphrase</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Please create a profile in the SimpleX app" xml:space="preserve">
<source>Please create a profile in the SimpleX app</source>
<target>Please create a profile in the SimpleX app</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
<source>Selected chat preferences prohibit this message.</source>
<target>Selected chat preferences prohibit this message.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sending a message takes longer than expected." xml:space="preserve">
<source>Sending a message takes longer than expected.</source>
<target>Sending a message takes longer than expected.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sending message…" xml:space="preserve">
<source>Sending message…</source>
<target>Sending message…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Share" xml:space="preserve">
<source>Share</source>
<target>Share</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Slow network?" xml:space="preserve">
<source>Slow network?</source>
<target>Slow network?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unknown database error: %@" xml:space="preserve">
<source>Unknown database error: %@</source>
<target>Unknown database error: %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unsupported format" xml:space="preserve">
<source>Unsupported format</source>
<target>Unsupported format</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Wait" xml:space="preserve">
<source>Wait</source>
<target>Wait</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Wrong database passphrase" xml:space="preserve">
<source>Wrong database passphrase</source>
<target>Wrong database passphrase</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<target>You can allow sharing in Privacy &amp; Security / SimpleX Lock settings.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
</body>
</file>
</xliff>
@@ -1,6 +1,9 @@
/* Bundle display name */
"CFBundleDisplayName" = "SimpleX NSE";
/* Bundle name */
"CFBundleName" = "SimpleX NSE";
/* Copyright (human-readable) */
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";
@@ -0,0 +1,7 @@
/*
Localizable.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -0,0 +1,7 @@
/*
InfoPlist.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -0,0 +1,7 @@
/*
Localizable.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,9 @@
/* Bundle display name */
"CFBundleDisplayName" = "SimpleX NSE";
/* Bundle name */
"CFBundleName" = "SimpleX NSE";
/* Copyright (human-readable) */
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";
@@ -0,0 +1,7 @@
/*
Localizable.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -0,0 +1,7 @@
/*
InfoPlist.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -0,0 +1,7 @@
/*
Localizable.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -744,6 +744,10 @@
<target>Salli katoavien viestien lähettäminen.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Allow sharing" xml:space="preserve">
<source>Allow sharing</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Allow to irreversibly delete sent messages. (24 hours)" xml:space="preserve">
<source>Allow to irreversibly delete sent messages. (24 hours)</source>
<target>Salli lähetettyjen viestien peruuttamaton poistaminen. (24 tuntia)</target>
@@ -1013,6 +1017,10 @@
<source>Blocked by admin</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Blur media" xml:space="preserve">
<source>Blur media</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
<source>Both you and your contact can add message reactions.</source>
<target>Sekä sinä että kontaktisi voivat käyttää viestireaktioita.</target>
@@ -1177,6 +1185,10 @@
<target>Chat-tietokanta poistettu</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat database exported" xml:space="preserve">
<source>Chat database exported</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat database imported" xml:space="preserve">
<source>Chat database imported</source>
<target>Chat-tietokanta tuotu</target>
@@ -1441,6 +1453,10 @@ This is your own one-time link!</source>
<target>Yhteysvirhe (AUTH)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection notifications" xml:space="preserve">
<source>Connection notifications</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection request sent!" xml:space="preserve">
<source>Connection request sent!</source>
<target>Yhteyspyyntö lähetetty!</target>
@@ -1772,6 +1788,10 @@ This is your own one-time link!</source>
<target>Poista</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Delete %lld messages of members?" xml:space="preserve">
<source>Delete %lld messages of members?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete %lld messages?" xml:space="preserve">
<source>Delete %lld messages?</source>
<note>No comment provided by engineer.</note>
@@ -2004,10 +2024,18 @@ This cannot be undone!</source>
<source>Desktop devices</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Destination server error: %@" xml:space="preserve">
<source>Destination server error: %@</source>
<note>snd error text</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Detailed statistics" xml:space="preserve">
<source>Detailed statistics</source>
<note>No comment provided by engineer.</note>
@@ -2071,6 +2099,10 @@ This cannot be undone!</source>
<target>Poista käytöstä kaikilta</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Disabled" xml:space="preserve">
<source>Disabled</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Disappearing message" xml:space="preserve">
<source>Disappearing message</source>
<target>Tuhoutuva viesti</target>
@@ -2282,6 +2314,10 @@ This cannot be undone!</source>
<target>Ota itsetuhoava pääsykoodi käyttöön</target>
<note>set passcode view</note>
</trans-unit>
<trans-unit id="Enabled" xml:space="preserve">
<source>Enabled</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enabled for" xml:space="preserve">
<source>Enabled for</source>
<note>No comment provided by engineer.</note>
@@ -2443,6 +2479,10 @@ This cannot be undone!</source>
<target>Virhe asetuksen muuttamisessa</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error connecting to forwarding server %@. Please try later." xml:space="preserve">
<source>Error connecting to forwarding server %@. Please try later.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating address" xml:space="preserve">
<source>Error creating address</source>
<target>Virhe osoitteen luomisessa</target>
@@ -2918,6 +2958,18 @@ This cannot be undone!</source>
<source>Forwarded from</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarding server address is incompatible with network settings: %@." xml:space="preserve">
<source>Forwarding server address is incompatible with network settings: %@.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarding server version is incompatible with network settings: %@." xml:space="preserve">
<source>Forwarding server version is incompatible with network settings: %@.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarding server: %@&#10;Destination server error: %@" xml:space="preserve">
<source>Forwarding server: %1$@
Destination server error: %2$@</source>
@@ -3698,6 +3750,14 @@ This is your link for group %@!</source>
<target>Enintään 30 sekuntia, vastaanotetaan välittömästi.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Media &amp; file servers" xml:space="preserve">
<source>Media &amp; file servers</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Medium" xml:space="preserve">
<source>Medium</source>
<note>blur media</note>
</trans-unit>
<trans-unit id="Member" xml:space="preserve">
<source>Member</source>
<target>Jäsen</target>
@@ -3776,12 +3836,8 @@ This is your link for group %@!</source>
<source>Message reception</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message routing fallback" xml:space="preserve">
<source>Message routing fallback</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message routing mode" xml:space="preserve">
<source>Message routing mode</source>
<trans-unit id="Message servers" xml:space="preserve">
<source>Message servers</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message source remains private." xml:space="preserve">
@@ -4088,6 +4144,10 @@ This is your link for group %@!</source>
<source>Not compatible!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Nothing selected" xml:space="preserve">
<source>Nothing selected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>Ilmoitukset</target>
@@ -4114,7 +4174,7 @@ This is your link for group %@!</source>
<trans-unit id="Off" xml:space="preserve">
<source>Off</source>
<target>Pois</target>
<note>No comment provided by engineer.</note>
<note>blur media</note>
</trans-unit>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
@@ -4136,14 +4196,18 @@ This is your link for group %@!</source>
<target>Kertakutsulinkki</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Onion hosts will be required for connection. Requires enabling VPN." xml:space="preserve">
<source>Onion hosts will be required for connection. Requires enabling VPN.</source>
<target>Yhteyden muodostamiseen tarvitaan Onion-isäntiä. Edellyttää VPN:n sallimista.</target>
<trans-unit id="Onion hosts will be **required** for connection.&#10;Requires compatible VPN." xml:space="preserve">
<source>Onion hosts will be **required** for connection.
Requires compatible VPN.</source>
<target>Yhteyden muodostamiseen tarvitaan Onion-isäntiä.
Edellyttää VPN:n sallimista.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Onion hosts will be used when available. Requires enabling VPN." xml:space="preserve">
<source>Onion hosts will be used when available. Requires enabling VPN.</source>
<target>Onion-isäntiä käytetään, kun niitä on saatavilla. Edellyttää VPN:n sallimista.</target>
<trans-unit id="Onion hosts will be used when available.&#10;Requires compatible VPN." xml:space="preserve">
<source>Onion hosts will be used when available.
Requires compatible VPN.</source>
<target>Onion-isäntiä käytetään, kun niitä on saatavilla.
Edellyttää VPN:n sallimista.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Onion hosts will not be used." xml:space="preserve">
@@ -4448,10 +4512,6 @@ Error: %@</source>
<target>Säilytä tunnuslause turvallisesti, ET voi muuttaa sitä, jos kadotat sen.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Please try later." xml:space="preserve">
<source>Please try later.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Polish interface" xml:space="preserve">
<source>Polish interface</source>
<target>Puolalainen käyttöliittymä</target>
@@ -4973,11 +5033,6 @@ Enable in *Network &amp; servers* settings.</source>
<target>Paljasta</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Revert" xml:space="preserve">
<source>Revert</source>
<target>Palauta</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>Peruuta</target>
@@ -5007,11 +5062,6 @@ Enable in *Network &amp; servers* settings.</source>
<source>SMP server</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="SMP servers" xml:space="preserve">
<source>SMP servers</source>
<target>SMP-palvelimet</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Safely receive files" xml:space="preserve">
<source>Safely receive files</source>
<note>No comment provided by engineer.</note>
@@ -5040,6 +5090,10 @@ Enable in *Network &amp; servers* settings.</source>
<target>Tallenna ja ilmoita ryhmän jäsenille</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Save and reconnect" xml:space="preserve">
<source>Save and reconnect</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Save and update group profile" xml:space="preserve">
<source>Save and update group profile</source>
<target>Tallenna ja päivitä ryhmäprofiili</target>
@@ -5188,6 +5242,10 @@ Enable in *Network &amp; servers* settings.</source>
<trans-unit id="Select" xml:space="preserve">
<source>Select</source>
<target>Valitse</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Selected %lld" xml:space="preserve">
<source>Selected %lld</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
@@ -5424,10 +5482,6 @@ Enable in *Network &amp; servers* settings.</source>
<source>Server version is incompatible with network settings.</source>
<note>srv error text</note>
</trans-unit>
<trans-unit id="Server version is incompatible with network settings: %@." xml:space="preserve">
<source>Server version is incompatible with network settings: %@.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Server version is incompatible with your app: %@." xml:space="preserve">
<source>Server version is incompatible with your app: %@.</source>
<note>No comment provided by engineer.</note>
@@ -5535,6 +5589,10 @@ Enable in *Network &amp; servers* settings.</source>
<source>Share this 1-time invite link</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Share to SimpleX" xml:space="preserve">
<source>Share to SimpleX</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Share with contacts" xml:space="preserve">
<source>Share with contacts</source>
<target>Jaa kontaktien kanssa</target>
@@ -5676,11 +5734,23 @@ Enable in *Network &amp; servers* settings.</source>
<target>Pienryhmät (max 20)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Soft" xml:space="preserve">
<source>Soft</source>
<note>blur media</note>
</trans-unit>
<trans-unit id="Some file(s) were not exported:" xml:space="preserve">
<source>Some file(s) were not exported:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Some non-fatal errors occurred during import - you may see Chat console for more details." xml:space="preserve">
<source>Some non-fatal errors occurred during import - you may see Chat console for more details.</source>
<target>Tuonnin aikana tapahtui joitakin ei-vakavia virheitä saatat nähdä Chat-konsolissa lisätietoja.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Some non-fatal errors occurred during import:" xml:space="preserve">
<source>Some non-fatal errors occurred during import:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Somebody" xml:space="preserve">
<source>Somebody</source>
<target>Joku</target>
@@ -5770,6 +5840,10 @@ Enable in *Network &amp; servers* settings.</source>
<source>Stopping chat</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Strong" xml:space="preserve">
<source>Strong</source>
<note>blur media</note>
</trans-unit>
<trans-unit id="Submit" xml:space="preserve">
<source>Submit</source>
<target>Lähetä</target>
@@ -5802,6 +5876,10 @@ Enable in *Network &amp; servers* settings.</source>
<target>Järjestelmän todennus</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="TCP connection" xml:space="preserve">
<source>TCP connection</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="TCP connection timeout" xml:space="preserve">
<source>TCP connection timeout</source>
<target>TCP-yhteyden aikakatkaisu</target>
@@ -5968,6 +6046,14 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.</t
<target>Viesti merkitään moderoiduksi kaikille jäsenille.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The messages will be deleted for all members." xml:space="preserve">
<source>The messages will be deleted for all members.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The next generation of private messaging" xml:space="preserve">
<source>The next generation of private messaging</source>
<target>Seuraavan sukupolven yksityisviestit</target>
@@ -6302,11 +6388,6 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
<target>Päivitä</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Update .onion hosts setting?" xml:space="preserve">
<source>Update .onion hosts setting?</source>
<target>Päivitä .onion-isäntien asetus?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Update database passphrase" xml:space="preserve">
<source>Update database passphrase</source>
<target>Päivitä tietokannan tunnuslause</target>
@@ -6317,9 +6398,8 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
<target>Päivitä verkkoasetukset?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Update transport isolation mode?" xml:space="preserve">
<source>Update transport isolation mode?</source>
<target>Päivitä kuljetuksen eristystila?</target>
<trans-unit id="Update settings?" xml:space="preserve">
<source>Update settings?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Updating settings will re-connect the client to all servers." xml:space="preserve">
@@ -6327,11 +6407,6 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
<target>Asetusten päivittäminen yhdistää asiakkaan uudelleen kaikkiin palvelimiin.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Updating this setting will re-connect the client to all servers." xml:space="preserve">
<source>Updating this setting will re-connect the client to all servers.</source>
<target>Tämän asetuksen päivittäminen yhdistää asiakkaan uudelleen kaikkiin palvelimiin.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Upgrade and open chat" xml:space="preserve">
<source>Upgrade and open chat</source>
<target>Päivitä ja avaa keskustelu</target>
@@ -6431,11 +6506,6 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
<source>User selection</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Using .onion hosts requires compatible VPN provider." xml:space="preserve">
<source>Using .onion hosts requires compatible VPN provider.</source>
<target>.onion-isäntien käyttäminen vaatii yhteensopivan VPN-palveluntarjoajan.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Using SimpleX Chat servers." xml:space="preserve">
<source>Using SimpleX Chat servers.</source>
<target>Käyttää SimpleX Chat -palvelimia.</target>
@@ -6672,11 +6742,6 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
<source>XFTP server</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="XFTP servers" xml:space="preserve">
<source>XFTP servers</source>
<target>XFTP-palvelimet</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You" xml:space="preserve">
<source>You</source>
<target>Sinä</target>
@@ -6875,6 +6940,14 @@ Repeat connection request?</source>
<target>Liityit tähän ryhmään. Muodostetaan yhteyttä ryhmän jäsenten kutsumiseksi.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You may migrate the exported database." xml:space="preserve">
<source>You may migrate the exported database.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You may save the exported archive." xml:space="preserve">
<source>You may save the exported archive.</source>
<note>No comment provided by engineer.</note>
</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">
<source>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.</source>
<target>Sinun tulee käyttää keskustelujen-tietokannan uusinta versiota AINOSTAAN yhdessä laitteessa, muuten saatat lakata vastaanottamasta viestejä joiltakin kontakteilta.</target>
@@ -7819,8 +7892,8 @@ last received msg: %2$@</source>
<target>tuntematon</target>
<note>connection info</note>
</trans-unit>
<trans-unit id="unknown relays" xml:space="preserve">
<source>unknown relays</source>
<trans-unit id="unknown servers" xml:space="preserve">
<source>unknown servers</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="unknown status" xml:space="preserve">
@@ -8035,4 +8108,178 @@ last received msg: %2$@</source>
</trans-unit>
</body>
</file>
<file original="SimpleX SE/en.lproj/InfoPlist.strings" source-language="en" target-language="fi" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
</header>
<body>
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
<source>SimpleX SE</source>
<note>Bundle display name</note>
</trans-unit>
<trans-unit id="CFBundleName" xml:space="preserve">
<source>SimpleX SE</source>
<note>Bundle name</note>
</trans-unit>
<trans-unit id="NSHumanReadableCopyright" xml:space="preserve">
<source>Copyright © 2024 SimpleX Chat. All rights reserved.</source>
<note>Copyright (human-readable)</note>
</trans-unit>
</body>
</file>
<file original="SimpleX SE/en.lproj/Localizable.strings" source-language="en" target-language="fi" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
</header>
<body>
<trans-unit id="%@" xml:space="preserve">
<source>%@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="App is locked!" xml:space="preserve">
<source>App is locked!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cancel" xml:space="preserve">
<source>Cancel</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cannot access keychain to save database password" xml:space="preserve">
<source>Cannot access keychain to save database password</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cannot forward message" xml:space="preserve">
<source>Cannot forward message</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Comment" xml:space="preserve">
<source>Comment</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Currently maximum supported file size is %@." xml:space="preserve">
<source>Currently maximum supported file size is %@.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database downgrade required" xml:space="preserve">
<source>Database downgrade required</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database encrypted!" xml:space="preserve">
<source>Database encrypted!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database error" xml:space="preserve">
<source>Database error</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database passphrase is required to open chat." xml:space="preserve">
<source>Database passphrase is required to open chat.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database upgrade required" xml:space="preserve">
<source>Database upgrade required</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error preparing file" xml:space="preserve">
<source>Error preparing file</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error preparing message" xml:space="preserve">
<source>Error preparing message</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error: %@" xml:space="preserve">
<source>Error: %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="File error" xml:space="preserve">
<source>File error</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incompatible database version" xml:space="preserve">
<source>Incompatible database version</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Invalid migration confirmation" xml:space="preserve">
<source>Invalid migration confirmation</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Keychain error" xml:space="preserve">
<source>Keychain error</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Large file!" xml:space="preserve">
<source>Large file!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No active profile" xml:space="preserve">
<source>No active profile</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open the app to downgrade the database." xml:space="preserve">
<source>Open the app to downgrade the database.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open the app to upgrade the database." xml:space="preserve">
<source>Open the app to upgrade the database.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Passphrase" xml:space="preserve">
<source>Passphrase</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Please create a profile in the SimpleX app" xml:space="preserve">
<source>Please create a profile in the SimpleX app</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
<source>Selected chat preferences prohibit this message.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sending a message takes longer than expected." xml:space="preserve">
<source>Sending a message takes longer than expected.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sending message…" xml:space="preserve">
<source>Sending message…</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Share" xml:space="preserve">
<source>Share</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Slow network?" xml:space="preserve">
<source>Slow network?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unknown database error: %@" xml:space="preserve">
<source>Unknown database error: %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unsupported format" xml:space="preserve">
<source>Unsupported format</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Wait" xml:space="preserve">
<source>Wait</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Wrong database passphrase" xml:space="preserve">
<source>Wrong database passphrase</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
</body>
</file>
</xliff>
@@ -1,6 +1,9 @@
/* Bundle display name */
"CFBundleDisplayName" = "SimpleX NSE";
/* Bundle name */
"CFBundleName" = "SimpleX NSE";
/* Copyright (human-readable) */
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";
@@ -0,0 +1,7 @@
/*
Localizable.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -0,0 +1,7 @@
/*
InfoPlist.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -0,0 +1,7 @@
/*
Localizable.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,9 @@
/* Bundle display name */
"CFBundleDisplayName" = "SimpleX NSE";
/* Bundle name */
"CFBundleName" = "SimpleX NSE";
/* Copyright (human-readable) */
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";
@@ -0,0 +1,7 @@
/*
Localizable.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -0,0 +1,7 @@
/*
InfoPlist.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -0,0 +1,7 @@
/*
Localizable.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -5316,6 +5316,278 @@ SimpleX servers cannot see your profile.</source>
<target state="translated">%@ ו-%@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect automatically" xml:space="preserve" approved="no">
<source>Connect automatically</source>
<target state="translated">התבר אוטומטי</target>
</trans-unit>
<trans-unit id="Create profile" xml:space="preserve" approved="no">
<source>Create profile</source>
<target state="translated">צור פרופיל</target>
</trans-unit>
<trans-unit id="Created at: %@" xml:space="preserve" approved="no">
<source>Created at: %@</source>
<target state="translated">נוצר ב:%@</target>
</trans-unit>
<trans-unit id="Desktop devices" xml:space="preserve" approved="no">
<source>Desktop devices</source>
<target state="translated">מכשירי מחשב</target>
</trans-unit>
<trans-unit id="Discover via local network" xml:space="preserve" approved="no">
<source>Discover via local network</source>
<target state="translated">גלה באמצעות הרשת המקומית</target>
</trans-unit>
<trans-unit id="Forward" xml:space="preserve" approved="no">
<source>Forward</source>
<target state="translated">העבר</target>
</trans-unit>
<trans-unit id="Group already exists" xml:space="preserve" approved="no">
<source>Group already exists</source>
<target state="translated">קבוצה כבר קיימת</target>
</trans-unit>
<trans-unit id="Connected to desktop" xml:space="preserve" approved="no">
<source>Connected to desktop</source>
<target state="translated">מחובר למחשב</target>
</trans-unit>
<trans-unit id="Group already exists!" xml:space="preserve" approved="no">
<source>Group already exists!</source>
<target state="translated">קבוצה כבר קיימת!</target>
</trans-unit>
<trans-unit id="Confirm upload" xml:space="preserve" approved="no">
<source>Confirm upload</source>
<target state="translated">אשר ההעלאה</target>
</trans-unit>
<trans-unit id="Block for all" xml:space="preserve" approved="no">
<source>Block for all</source>
<target state="translated">חסום לכולם</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="Block member for all?" xml:space="preserve" approved="no">
<source>Block member for all?</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="Connect to desktop" xml:space="preserve" approved="no">
<source>Connect to desktop</source>
<target state="translated">חבר למחשב</target>
</trans-unit>
<trans-unit id="Created at" xml:space="preserve" approved="no">
<source>Created at</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="Block member" xml:space="preserve" approved="no">
<source>Block member</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="Creating link…" xml:space="preserve" approved="no">
<source>Creating link…</source>
<target state="translated">יוצר קישור…</target>
</trans-unit>
<trans-unit id="Files" xml:space="preserve" approved="no">
<source>Files</source>
<target state="translated">קבצים</target>
</trans-unit>
<trans-unit id="Disabled" xml:space="preserve" approved="no">
<source>Disabled</source>
<target state="translated">מושבת</target>
</trans-unit>
<trans-unit id="Enter passphrase" xml:space="preserve" approved="no">
<source>Enter passphrase</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="Background" xml:space="preserve" approved="no">
<source>Background</source>
<target state="translated">ברקע</target>
</trans-unit>
<trans-unit id="Black" xml:space="preserve" approved="no">
<source>Black</source>
<target state="translated">שחור</target>
</trans-unit>
<trans-unit id="Blur media" xml:space="preserve" approved="no">
<source>Blur media</source>
<target state="translated">טשטש מדיה</target>
</trans-unit>
<trans-unit id="Chat theme" xml:space="preserve" approved="no">
<source>Chat theme</source>
<target state="translated">צבע ערכת נושא</target>
</trans-unit>
<trans-unit id="Completed" xml:space="preserve" approved="no">
<source>Completed</source>
<target state="translated">הושלם</target>
</trans-unit>
<trans-unit id="Connected" xml:space="preserve" approved="no">
<source>Connected</source>
<target state="translated">מחובר</target>
</trans-unit>
<trans-unit id="Connection notifications" xml:space="preserve" approved="no">
<source>Connection notifications</source>
<target state="translated">התראות חיבור</target>
</trans-unit>
<trans-unit id="Connections" xml:space="preserve" approved="no">
<source>Connections</source>
<target state="translated">חיבורים</target>
</trans-unit>
<trans-unit id="Current profile" xml:space="preserve" approved="no">
<source>Current profile</source>
<target state="translated">פרופיל נוכחי</target>
</trans-unit>
<trans-unit id="Disconnect desktop?" xml:space="preserve" approved="no">
<source>Disconnect desktop?</source>
<target state="translated">להתנתק מהמחשב?</target>
</trans-unit>
<trans-unit id="Discover and join groups" xml:space="preserve" approved="no">
<source>Discover and join groups</source>
<target state="translated">גלה והצטרף לקבוצות</target>
</trans-unit>
<trans-unit id="Enabled" xml:space="preserve" approved="no">
<source>Enabled</source>
<target state="translated">מופעל</target>
</trans-unit>
<trans-unit id="Error opening chat" xml:space="preserve" approved="no">
<source>Error opening chat</source>
<target state="translated">שגיאה בפתיחת הצ'אט</target>
</trans-unit>
<trans-unit id="Good morning!" xml:space="preserve" approved="no">
<source>Good morning!</source>
<target state="translated">בוקר טוב!</target>
</trans-unit>
<trans-unit id="Connect to yourself?&#10;This is your own SimpleX address!" xml:space="preserve" approved="no">
<source>Connect to yourself?
This is your own SimpleX address!</source>
<target state="translated">להתחבר אליך?
זו כתובת הSimpleX שלך!</target>
</trans-unit>
<trans-unit id="Connect to yourself?" xml:space="preserve" approved="no">
<source>Connect to yourself?</source>
<target state="translated">להתחבר אליך?</target>
</trans-unit>
<trans-unit id="Connect to yourself?&#10;This is your own one-time link!" xml:space="preserve" approved="no">
<source>Connect to yourself?
This is your own one-time link!</source>
<target state="translated">להתחבר אליך?
זו כתובת ההזמנה החד-פעמי שלך!</target>
</trans-unit>
<trans-unit id="Connected desktop" xml:space="preserve" approved="no">
<source>Connected desktop</source>
<target state="translated">מחשב מחובר</target>
</trans-unit>
<trans-unit id="Connected servers" xml:space="preserve" approved="no">
<source>Connected servers</source>
<target state="translated">שרתים מחוברים</target>
</trans-unit>
<trans-unit id="Enter group name…" xml:space="preserve" approved="no">
<source>Enter group name…</source>
<target state="translated">הכנס שם לקבוצה…</target>
</trans-unit>
<trans-unit id="Enter this device name…" xml:space="preserve" approved="no">
<source>Enter this device name…</source>
<target state="translated">הכנס שם למכשיר הזה…</target>
</trans-unit>
<trans-unit id="Enter your name…" xml:space="preserve" approved="no">
<source>Enter your name…</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="Errors" xml:space="preserve" approved="no">
<source>Errors</source>
<target state="translated">שגיאות</target>
</trans-unit>
<trans-unit id="File status" xml:space="preserve" approved="no">
<source>File status</source>
<target state="translated">מצב הקובץ</target>
</trans-unit>
<trans-unit id="Connecting" xml:space="preserve" approved="no">
<source>Connecting</source>
<target state="translated">מתחבר</target>
</trans-unit>
<trans-unit id="Connecting to desktop" xml:space="preserve" approved="no">
<source>Connecting to desktop</source>
<target state="translated">מתחבר למחשב</target>
</trans-unit>
<trans-unit id="Deleted" xml:space="preserve" approved="no">
<source>Deleted</source>
<target state="translated">נמחק</target>
</trans-unit>
<trans-unit id="Deletion errors" xml:space="preserve" approved="no">
<source>Deletion errors</source>
<target state="translated">שגיאות במחיקה</target>
</trans-unit>
<trans-unit id="Details" xml:space="preserve" approved="no">
<source>Details</source>
<target state="translated">פרטים</target>
</trans-unit>
<trans-unit id="Forwarded" xml:space="preserve" approved="no">
<source>Forwarded</source>
<target state="translated">הועבר</target>
</trans-unit>
<trans-unit id="Found desktop" xml:space="preserve" approved="no">
<source>Found desktop</source>
<target state="translated">נמצא מחשב</target>
</trans-unit>
<trans-unit id="Good afternoon!" xml:space="preserve" approved="no">
<source>Good afternoon!</source>
<target state="translated">אחר צהריים טובים!</target>
</trans-unit>
<trans-unit id="Desktop address" xml:space="preserve" approved="no">
<source>Desktop address</source>
<target state="translated">כתובת מחשב</target>
</trans-unit>
<trans-unit id="Forwarded from" xml:space="preserve" approved="no">
<source>Forwarded from</source>
<target state="translated">הועבר מ</target>
</trans-unit>
<trans-unit id="History is not sent to new members." xml:space="preserve" approved="no">
<source>History is not sent to new members.</source>
<target state="translated">היסטוריה לא נשלחת לחברים חדשים.</target>
</trans-unit>
<trans-unit id="Created" xml:space="preserve" approved="no">
<source>Created</source>
<target state="translated">נוצר</target>
</trans-unit>
<trans-unit id="Copy error" xml:space="preserve" approved="no">
<source>Copy error</source>
<target state="translated">שגיאת העתקה</target>
</trans-unit>
<trans-unit id="Create group" xml:space="preserve" approved="no">
<source>Create group</source>
<target state="translated">צור קבוצה</target>
</trans-unit>
<trans-unit id="Enabled for" xml:space="preserve" approved="no">
<source>Enabled for</source>
<target state="translated">מופעל עבור</target>
</trans-unit>
<trans-unit id="Error creating message" xml:space="preserve" approved="no">
<source>Error creating message</source>
<target state="translated">שגיאה ביצירת הודעה</target>
</trans-unit>
<trans-unit id="File error" xml:space="preserve" approved="no">
<source>File error</source>
<target state="translated">שגיאה בקובץ</target>
</trans-unit>
</body>
</file>
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="he" datatype="plaintext">
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,9 @@
/* Bundle display name */
"CFBundleDisplayName" = "SimpleX NSE";
/* Bundle name */
"CFBundleName" = "SimpleX NSE";
/* Copyright (human-readable) */
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";
@@ -0,0 +1,7 @@
/*
Localizable.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -0,0 +1,7 @@
/*
InfoPlist.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -0,0 +1,7 @@
/*
Localizable.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -590,6 +590,7 @@
</trans-unit>
<trans-unit id="Active connections" xml:space="preserve">
<source>Active connections</source>
<target>Connessioni attive</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." xml:space="preserve">
@@ -782,6 +783,11 @@
<target>Permetti l'invio di messaggi a tempo.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Allow sharing" xml:space="preserve">
<source>Allow sharing</source>
<target>Consenti la condivisione</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Allow to irreversibly delete sent messages. (24 hours)" xml:space="preserve">
<source>Allow to irreversibly delete sent messages. (24 hours)</source>
<target>Permetti di eliminare irreversibilmente i messaggi inviati. (24 ore)</target>
@@ -1072,6 +1078,11 @@
<target>Bloccato dall'amministratore</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Blur media" xml:space="preserve">
<source>Blur media</source>
<target>Sfocatura file multimediali</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
<source>Both you and your contact can add message reactions.</source>
<target>Sia tu che il tuo contatto potete aggiungere reazioni ai messaggi.</target>
@@ -1243,6 +1254,10 @@
<target>Database della chat eliminato</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat database exported" xml:space="preserve">
<source>Chat database exported</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat database imported" xml:space="preserve">
<source>Chat database imported</source>
<target>Database della chat importato</target>
@@ -1375,6 +1390,7 @@
</trans-unit>
<trans-unit id="Configured %@ servers" xml:space="preserve">
<source>Configured %@ servers</source>
<target>Configurati %@ server</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Confirm" xml:space="preserve">
@@ -1536,6 +1552,11 @@ Questo è il tuo link una tantum!</target>
<target>Errore di connessione (AUTH)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection notifications" xml:space="preserve">
<source>Connection notifications</source>
<target>Notifiche di connessione</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection request sent!" xml:space="preserve">
<source>Connection request sent!</source>
<target>Richiesta di connessione inviata!</target>
@@ -1884,6 +1905,11 @@ Questo è il tuo link una tantum!</target>
<target>Elimina</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Delete %lld messages of members?" xml:space="preserve">
<source>Delete %lld messages of members?</source>
<target>Eliminare %lld messaggi dei membri?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete %lld messages?" xml:space="preserve">
<source>Delete %lld messages?</source>
<target>Eliminare %lld messaggi?</target>
@@ -2126,11 +2152,21 @@ Non è reversibile!</target>
<target>Dispositivi desktop</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<target>L'indirizzo del server di destinazione di %@ è incompatibile con le impostazioni del server di inoltro %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Destination server error: %@" xml:space="preserve">
<source>Destination server error: %@</source>
<target>Errore del server di destinazione: %@</target>
<note>snd error text</note>
</trans-unit>
<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>
<target>La versione del server di destinazione di %@ è incompatibile con il server di inoltro %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Detailed statistics" xml:space="preserve">
<source>Detailed statistics</source>
<target>Statistiche dettagliate</target>
@@ -2196,6 +2232,11 @@ Non è reversibile!</target>
<target>Disattiva per tutti</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Disabled" xml:space="preserve">
<source>Disabled</source>
<target>Disattivato</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Disappearing message" xml:space="preserve">
<source>Disappearing message</source>
<target>Messaggio a tempo</target>
@@ -2421,6 +2462,11 @@ Non è reversibile!</target>
<target>Attiva il codice di autodistruzione</target>
<note>set passcode view</note>
</trans-unit>
<trans-unit id="Enabled" xml:space="preserve">
<source>Enabled</source>
<target>Attivato</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enabled for" xml:space="preserve">
<source>Enabled for</source>
<target>Attivo per</target>
@@ -2591,6 +2637,11 @@ Non è reversibile!</target>
<target>Errore nella modifica dell'impostazione</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error connecting to forwarding server %@. Please try later." xml:space="preserve">
<source>Error connecting to forwarding server %@. Please try later.</source>
<target>Errore di connessione al server di inoltro %@. Riprova più tardi.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating address" xml:space="preserve">
<source>Error creating address</source>
<target>Errore nella creazione dell'indirizzo</target>
@@ -3097,6 +3148,21 @@ Non è reversibile!</target>
<target>Inoltrato da</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<target>Il server di inoltro %@ non è riuscito a connettersi al server di destinazione %@. Riprova più tardi.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarding server address is incompatible with network settings: %@." xml:space="preserve">
<source>Forwarding server address is incompatible with network settings: %@.</source>
<target>L'indirizzo del server di inoltro è incompatibile con le impostazioni di rete: %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarding server version is incompatible with network settings: %@." xml:space="preserve">
<source>Forwarding server version is incompatible with network settings: %@.</source>
<target>La versione del server di inoltro è incompatibile con le impostazioni di rete: %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarding server: %@&#10;Destination server error: %@" xml:space="preserve">
<source>Forwarding server: %1$@
Destination server error: %2$@</source>
@@ -3916,6 +3982,15 @@ Questo è il tuo link per il gruppo %@!</target>
<target>Max 30 secondi, ricevuto istantaneamente.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Media &amp; file servers" xml:space="preserve">
<source>Media &amp; file servers</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Medium" xml:space="preserve">
<source>Medium</source>
<target>Media</target>
<note>blur media</note>
</trans-unit>
<trans-unit id="Member" xml:space="preserve">
<source>Member</source>
<target>Membro</target>
@@ -3998,16 +4073,11 @@ Questo è il tuo link per il gruppo %@!</target>
</trans-unit>
<trans-unit id="Message reception" xml:space="preserve">
<source>Message reception</source>
<target>Ricezione messaggi</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message routing fallback" xml:space="preserve">
<source>Message routing fallback</source>
<target>Ripiego instradamento messaggio</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message routing mode" xml:space="preserve">
<source>Message routing mode</source>
<target>Modalità instradamento messaggio</target>
<trans-unit id="Message servers" xml:space="preserve">
<source>Message servers</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message source remains private." xml:space="preserve">
@@ -4340,6 +4410,11 @@ Questo è il tuo link per il gruppo %@!</target>
<target>Non compatibile!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Nothing selected" xml:space="preserve">
<source>Nothing selected</source>
<target>Nessuna selezione</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>Notifiche</target>
@@ -4367,7 +4442,7 @@ Questo è il tuo link per il gruppo %@!</target>
<trans-unit id="Off" xml:space="preserve">
<source>Off</source>
<target>Off</target>
<note>No comment provided by engineer.</note>
<note>blur media</note>
</trans-unit>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
@@ -4389,14 +4464,18 @@ Questo è il tuo link per il gruppo %@!</target>
<target>Link di invito una tantum</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Onion hosts will be required for connection. Requires enabling VPN." xml:space="preserve">
<source>Onion hosts will be required for connection. Requires enabling VPN.</source>
<target>Gli host Onion saranno necessari per la connessione. Richiede l'attivazione della VPN.</target>
<trans-unit id="Onion hosts will be **required** for connection.&#10;Requires compatible VPN." xml:space="preserve">
<source>Onion hosts will be **required** for connection.
Requires compatible VPN.</source>
<target>Gli host Onion saranno **necessari** per la connessione.
Richiede l'attivazione della VPN.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Onion hosts will be used when available. Requires enabling VPN." xml:space="preserve">
<source>Onion hosts will be used when available. Requires enabling VPN.</source>
<target>Gli host Onion verranno usati quando disponibili. Richiede l'attivazione della VPN.</target>
<trans-unit id="Onion hosts will be used when available.&#10;Requires compatible VPN." xml:space="preserve">
<source>Onion hosts will be used when available.
Requires compatible VPN.</source>
<target>Gli host Onion verranno usati quando disponibili.
Richiede l'attivazione della VPN.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Onion hosts will not be used." xml:space="preserve">
@@ -4551,6 +4630,7 @@ Questo è il tuo link per il gruppo %@!</target>
</trans-unit>
<trans-unit id="Other %@ servers" xml:space="preserve">
<source>Other %@ servers</source>
<target>Altri %@ server</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="PING count" xml:space="preserve">
@@ -4722,10 +4802,6 @@ Errore: %@</target>
<target>Conserva la password in modo sicuro, NON potrai cambiarla se la perdi.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Please try later." xml:space="preserve">
<source>Please try later.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Polish interface" xml:space="preserve">
<source>Polish interface</source>
<target>Interfaccia polacca</target>
@@ -4798,6 +4874,7 @@ Errore: %@</target>
</trans-unit>
<trans-unit id="Private routing error" xml:space="preserve">
<source>Private routing error</source>
<target>Errore di instradamento privato</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile and server connections" xml:space="preserve">
@@ -5287,11 +5364,6 @@ Attivalo nelle impostazioni *Rete e server*.</target>
<target>Rivela</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Revert" xml:space="preserve">
<source>Revert</source>
<target>Ripristina</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>Revoca</target>
@@ -5322,11 +5394,6 @@ Attivalo nelle impostazioni *Rete e server*.</target>
<target>Server SMP</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="SMP servers" xml:space="preserve">
<source>SMP servers</source>
<target>Server SMP</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Safely receive files" xml:space="preserve">
<source>Safely receive files</source>
<target>Ricevi i file in sicurezza</target>
@@ -5357,6 +5424,10 @@ Attivalo nelle impostazioni *Rete e server*.</target>
<target>Salva e avvisa i membri del gruppo</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Save and reconnect" xml:space="preserve">
<source>Save and reconnect</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Save and update group profile" xml:space="preserve">
<source>Save and update group profile</source>
<target>Salva e aggiorna il profilo del gruppo</target>
@@ -5515,6 +5586,11 @@ Attivalo nelle impostazioni *Rete e server*.</target>
<trans-unit id="Select" xml:space="preserve">
<source>Select</source>
<target>Seleziona</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Selected %lld" xml:space="preserve">
<source>Selected %lld</source>
<target>%lld selezionato</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
@@ -5739,6 +5815,7 @@ Attivalo nelle impostazioni *Rete e server*.</target>
</trans-unit>
<trans-unit id="Server address is incompatible with network settings: %@." xml:space="preserve">
<source>Server address is incompatible with network settings: %@.</source>
<target>L'indirizzo del server è incompatibile con le impostazioni di rete: %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Server requires authorization to create queues, check password" xml:space="preserve">
@@ -5766,12 +5843,9 @@ Attivalo nelle impostazioni *Rete e server*.</target>
<target>La versione del server non è compatibile con le impostazioni di rete.</target>
<note>srv error text</note>
</trans-unit>
<trans-unit id="Server version is incompatible with network settings: %@." xml:space="preserve">
<source>Server version is incompatible with network settings: %@.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Server version is incompatible with your app: %@." xml:space="preserve">
<source>Server version is incompatible with your app: %@.</source>
<target>La versione del server è incompatibile con la tua app: %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Servers" xml:space="preserve">
@@ -5884,6 +5958,11 @@ Attivalo nelle impostazioni *Rete e server*.</target>
<target>Condividi questo link di invito una tantum</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Share to SimpleX" xml:space="preserve">
<source>Share to SimpleX</source>
<target>Condividi in SimpleX</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Share with contacts" xml:space="preserve">
<source>Share with contacts</source>
<target>Condividi con i contatti</target>
@@ -5916,6 +5995,7 @@ Attivalo nelle impostazioni *Rete e server*.</target>
</trans-unit>
<trans-unit id="Show percentage" xml:space="preserve">
<source>Show percentage</source>
<target>Mostra percentuale</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Show preview" xml:space="preserve">
@@ -6033,11 +6113,24 @@ Attivalo nelle impostazioni *Rete e server*.</target>
<target>Piccoli gruppi (max 20)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Soft" xml:space="preserve">
<source>Soft</source>
<target>Leggera</target>
<note>blur media</note>
</trans-unit>
<trans-unit id="Some file(s) were not exported:" xml:space="preserve">
<source>Some file(s) were not exported:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Some non-fatal errors occurred during import - you may see Chat console for more details." xml:space="preserve">
<source>Some non-fatal errors occurred during import - you may see Chat console for more details.</source>
<target>Si sono verificati alcuni errori non gravi durante l'importazione: vedi la console della chat per i dettagli.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Some non-fatal errors occurred during import:" xml:space="preserve">
<source>Some non-fatal errors occurred during import:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Somebody" xml:space="preserve">
<source>Somebody</source>
<target>Qualcuno</target>
@@ -6133,6 +6226,11 @@ Attivalo nelle impostazioni *Rete e server*.</target>
<target>Arresto della chat</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Strong" xml:space="preserve">
<source>Strong</source>
<target>Forte</target>
<note>blur media</note>
</trans-unit>
<trans-unit id="Submit" xml:space="preserve">
<source>Submit</source>
<target>Invia</target>
@@ -6168,6 +6266,10 @@ Attivalo nelle impostazioni *Rete e server*.</target>
<target>Autenticazione di sistema</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="TCP connection" xml:space="preserve">
<source>TCP connection</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="TCP connection timeout" xml:space="preserve">
<source>TCP connection timeout</source>
<target>Scadenza connessione TCP</target>
@@ -6340,6 +6442,16 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.</ta
<target>Il messaggio sarà segnato come moderato per tutti i membri.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The messages will be deleted for all members." xml:space="preserve">
<source>The messages will be deleted for all members.</source>
<target>I messaggi verranno eliminati per tutti i membri.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<target>I messaggi verranno contrassegnati come moderati per tutti i membri.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The next generation of private messaging" xml:space="preserve">
<source>The next generation of private messaging</source>
<target>La nuova generazione di messaggistica privata</target>
@@ -6699,11 +6811,6 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
<target>Aggiorna</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Update .onion hosts setting?" xml:space="preserve">
<source>Update .onion hosts setting?</source>
<target>Aggiornare l'impostazione degli host .onion?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Update database passphrase" xml:space="preserve">
<source>Update database passphrase</source>
<target>Aggiorna la password del database</target>
@@ -6714,9 +6821,8 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
<target>Aggiornare le impostazioni di rete?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Update transport isolation mode?" xml:space="preserve">
<source>Update transport isolation mode?</source>
<target>Aggiornare la modalità di isolamento del trasporto?</target>
<trans-unit id="Update settings?" xml:space="preserve">
<source>Update settings?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Updating settings will re-connect the client to all servers." xml:space="preserve">
@@ -6724,11 +6830,6 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
<target>L'aggiornamento delle impostazioni riconnetterà il client a tutti i server.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Updating this setting will re-connect the client to all servers." xml:space="preserve">
<source>Updating this setting will re-connect the client to all servers.</source>
<target>L'aggiornamento di questa impostazione riconnetterà il client a tutti i server.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Upgrade and open chat" xml:space="preserve">
<source>Upgrade and open chat</source>
<target>Aggiorna e apri chat</target>
@@ -6839,11 +6940,6 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
<target>Selezione utente</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Using .onion hosts requires compatible VPN provider." xml:space="preserve">
<source>Using .onion hosts requires compatible VPN provider.</source>
<target>L'uso di host .onion richiede un fornitore di VPN compatibile.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Using SimpleX Chat servers." xml:space="preserve">
<source>Using SimpleX Chat servers.</source>
<target>Utilizzo dei server SimpleX Chat.</target>
@@ -7104,11 +7200,6 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
<target>Server XFTP</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="XFTP servers" xml:space="preserve">
<source>XFTP servers</source>
<target>Server XFTP</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You" xml:space="preserve">
<source>You</source>
<target>Tu</target>
@@ -7323,6 +7414,14 @@ Ripetere la richiesta di connessione?</target>
<target>Sei entrato/a in questo gruppo. Connessione al membro del gruppo invitante.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You may migrate the exported database." xml:space="preserve">
<source>You may migrate the exported database.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You may save the exported archive." xml:space="preserve">
<source>You may save the exported archive.</source>
<note>No comment provided by engineer.</note>
</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">
<source>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.</source>
<target>Devi usare la versione più recente del tuo database della chat SOLO su un dispositivo, altrimenti potresti non ricevere più i messaggi da alcuni contatti.</target>
@@ -8303,8 +8402,8 @@ ultimo msg ricevuto: %2$@</target>
<target>sconosciuto</target>
<note>connection info</note>
</trans-unit>
<trans-unit id="unknown relays" xml:space="preserve">
<source>unknown relays</source>
<trans-unit id="unknown servers" xml:space="preserve">
<source>unknown servers</source>
<target>relay sconosciuti</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -8529,4 +8628,214 @@ ultimo msg ricevuto: %2$@</target>
</trans-unit>
</body>
</file>
<file original="SimpleX SE/en.lproj/InfoPlist.strings" source-language="en" target-language="it" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
</header>
<body>
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
<source>SimpleX SE</source>
<target>SimpleX SE</target>
<note>Bundle display name</note>
</trans-unit>
<trans-unit id="CFBundleName" xml:space="preserve">
<source>SimpleX SE</source>
<target>SimpleX SE</target>
<note>Bundle name</note>
</trans-unit>
<trans-unit id="NSHumanReadableCopyright" xml:space="preserve">
<source>Copyright © 2024 SimpleX Chat. All rights reserved.</source>
<target>Copyright © 2024 SimpleX Chat. Tutti i diritti riservati.</target>
<note>Copyright (human-readable)</note>
</trans-unit>
</body>
</file>
<file original="SimpleX SE/en.lproj/Localizable.strings" source-language="en" target-language="it" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
</header>
<body>
<trans-unit id="%@" xml:space="preserve">
<source>%@</source>
<target>%@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="App is locked!" xml:space="preserve">
<source>App is locked!</source>
<target>L'app è bloccata!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cancel" xml:space="preserve">
<source>Cancel</source>
<target>Annulla</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cannot access keychain to save database password" xml:space="preserve">
<source>Cannot access keychain to save database password</source>
<target>Impossibile accedere al portachiavi per salvare la password del database</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cannot forward message" xml:space="preserve">
<source>Cannot forward message</source>
<target>Impossibile inoltrare il messaggio</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Comment" xml:space="preserve">
<source>Comment</source>
<target>Commento</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Currently maximum supported file size is %@." xml:space="preserve">
<source>Currently maximum supported file size is %@.</source>
<target>Attualmente la dimensione massima supportata è di %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database downgrade required" xml:space="preserve">
<source>Database downgrade required</source>
<target>Downgrade del database necessario</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database encrypted!" xml:space="preserve">
<source>Database encrypted!</source>
<target>Database crittografato!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database error" xml:space="preserve">
<source>Database error</source>
<target>Errore del database</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<target>La password del database è diversa da quella salvata nel portachiavi.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database passphrase is required to open chat." xml:space="preserve">
<source>Database passphrase is required to open chat.</source>
<target>La password del database è necessaria per aprire la chat.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database upgrade required" xml:space="preserve">
<source>Database upgrade required</source>
<target>Aggiornamento del database necessario</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error preparing file" xml:space="preserve">
<source>Error preparing file</source>
<target>Errore nella preparazione del file</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error preparing message" xml:space="preserve">
<source>Error preparing message</source>
<target>Errore nella preparazione del messaggio</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error: %@" xml:space="preserve">
<source>Error: %@</source>
<target>Errore: %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="File error" xml:space="preserve">
<source>File error</source>
<target>Errore del file</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incompatible database version" xml:space="preserve">
<source>Incompatible database version</source>
<target>Versione del database incompatibile</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Invalid migration confirmation" xml:space="preserve">
<source>Invalid migration confirmation</source>
<target>Conferma di migrazione non valida</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Keychain error" xml:space="preserve">
<source>Keychain error</source>
<target>Errore del portachiavi</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Large file!" xml:space="preserve">
<source>Large file!</source>
<target>File grande!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No active profile" xml:space="preserve">
<source>No active profile</source>
<target>Nessun profilo attivo</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<target>Ok</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open the app to downgrade the database." xml:space="preserve">
<source>Open the app to downgrade the database.</source>
<target>Apri l'app per eseguire il downgrade del database.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open the app to upgrade the database." xml:space="preserve">
<source>Open the app to upgrade the database.</source>
<target>Apri l'app per aggiornare il database.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Passphrase" xml:space="preserve">
<source>Passphrase</source>
<target>Password</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Please create a profile in the SimpleX app" xml:space="preserve">
<source>Please create a profile in the SimpleX app</source>
<target>Crea un profilo nell'app SimpleX</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
<source>Selected chat preferences prohibit this message.</source>
<target>Le preferenze della chat selezionata vietano questo messaggio.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sending a message takes longer than expected." xml:space="preserve">
<source>Sending a message takes longer than expected.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sending message…" xml:space="preserve">
<source>Sending message…</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Share" xml:space="preserve">
<source>Share</source>
<target>Condividi</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Slow network?" xml:space="preserve">
<source>Slow network?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unknown database error: %@" xml:space="preserve">
<source>Unknown database error: %@</source>
<target>Errore del database sconosciuto: %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unsupported format" xml:space="preserve">
<source>Unsupported format</source>
<target>Formato non supportato</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Wait" xml:space="preserve">
<source>Wait</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Wrong database passphrase" xml:space="preserve">
<source>Wrong database passphrase</source>
<target>Password del database sbagliata</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<target>Puoi consentire la condivisione in Privacy e sicurezza / impostazioni di SimpleX Lock.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
</body>
</file>
</xliff>
@@ -1,6 +1,9 @@
/* Bundle display name */
"CFBundleDisplayName" = "SimpleX NSE";
/* Bundle name */
"CFBundleName" = "SimpleX NSE";
/* Copyright (human-readable) */
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";
@@ -0,0 +1,7 @@
/*
Localizable.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -0,0 +1,7 @@
/*
InfoPlist.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -0,0 +1,7 @@
/*
Localizable.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -762,6 +762,10 @@
<target>消えるメッセージの送信を許可する。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Allow sharing" xml:space="preserve">
<source>Allow sharing</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Allow to irreversibly delete sent messages. (24 hours)" xml:space="preserve">
<source>Allow to irreversibly delete sent messages. (24 hours)</source>
<target>送信済みメッセージの永久削除を許可する。(24時間)</target>
@@ -1036,6 +1040,10 @@
<source>Blocked by admin</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Blur media" xml:space="preserve">
<source>Blur media</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
<source>Both you and your contact can add message reactions.</source>
<target>自分も相手もメッセージへのリアクションを追加できます。</target>
@@ -1201,6 +1209,10 @@
<target>チャットのデータベースが削除されました</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat database exported" xml:space="preserve">
<source>Chat database exported</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat database imported" xml:space="preserve">
<source>Chat database imported</source>
<target>チャットのデータベースが読み込まれました</target>
@@ -1465,6 +1477,10 @@ This is your own one-time link!</source>
<target>接続エラー (AUTH)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection notifications" xml:space="preserve">
<source>Connection notifications</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection request sent!" xml:space="preserve">
<source>Connection request sent!</source>
<target>接続リクエストを送信しました!</target>
@@ -1796,6 +1812,10 @@ This is your own one-time link!</source>
<target>削除</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Delete %lld messages of members?" xml:space="preserve">
<source>Delete %lld messages of members?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete %lld messages?" xml:space="preserve">
<source>Delete %lld messages?</source>
<note>No comment provided by engineer.</note>
@@ -2028,10 +2048,18 @@ This cannot be undone!</source>
<source>Desktop devices</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Destination server error: %@" xml:space="preserve">
<source>Destination server error: %@</source>
<note>snd error text</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Detailed statistics" xml:space="preserve">
<source>Detailed statistics</source>
<note>No comment provided by engineer.</note>
@@ -2095,6 +2123,10 @@ This cannot be undone!</source>
<target>すべて無効</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Disabled" xml:space="preserve">
<source>Disabled</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Disappearing message" xml:space="preserve">
<source>Disappearing message</source>
<target>消えるメッセージ</target>
@@ -2306,6 +2338,10 @@ This cannot be undone!</source>
<target>自己破壊パスコードを有効にする</target>
<note>set passcode view</note>
</trans-unit>
<trans-unit id="Enabled" xml:space="preserve">
<source>Enabled</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enabled for" xml:space="preserve">
<source>Enabled for</source>
<note>No comment provided by engineer.</note>
@@ -2468,6 +2504,10 @@ This cannot be undone!</source>
<target>設定変更にエラー発生</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error connecting to forwarding server %@. Please try later." xml:space="preserve">
<source>Error connecting to forwarding server %@. Please try later.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating address" xml:space="preserve">
<source>Error creating address</source>
<target>アドレス作成にエラー発生</target>
@@ -2943,6 +2983,18 @@ This cannot be undone!</source>
<source>Forwarded from</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarding server address is incompatible with network settings: %@." xml:space="preserve">
<source>Forwarding server address is incompatible with network settings: %@.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarding server version is incompatible with network settings: %@." xml:space="preserve">
<source>Forwarding server version is incompatible with network settings: %@.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarding server: %@&#10;Destination server error: %@" xml:space="preserve">
<source>Forwarding server: %1$@
Destination server error: %2$@</source>
@@ -3723,6 +3775,14 @@ This is your link for group %@!</source>
<target>最大 30 秒で即時受信します。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Media &amp; file servers" xml:space="preserve">
<source>Media &amp; file servers</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Medium" xml:space="preserve">
<source>Medium</source>
<note>blur media</note>
</trans-unit>
<trans-unit id="Member" xml:space="preserve">
<source>Member</source>
<target>メンバー</target>
@@ -3800,12 +3860,8 @@ This is your link for group %@!</source>
<source>Message reception</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message routing fallback" xml:space="preserve">
<source>Message routing fallback</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message routing mode" xml:space="preserve">
<source>Message routing mode</source>
<trans-unit id="Message servers" xml:space="preserve">
<source>Message servers</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message source remains private." xml:space="preserve">
@@ -4113,6 +4169,10 @@ This is your link for group %@!</source>
<source>Not compatible!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Nothing selected" xml:space="preserve">
<source>Nothing selected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>通知</target>
@@ -4139,7 +4199,7 @@ This is your link for group %@!</source>
<trans-unit id="Off" xml:space="preserve">
<source>Off</source>
<target>オフ</target>
<note>No comment provided by engineer.</note>
<note>blur media</note>
</trans-unit>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
@@ -4161,14 +4221,18 @@ This is your link for group %@!</source>
<target>使い捨ての招待リンク</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Onion hosts will be required for connection. Requires enabling VPN." xml:space="preserve">
<source>Onion hosts will be required for connection. Requires enabling VPN.</source>
<target>接続にオニオンのホストが必要となります。VPN を有効にする必要があります。</target>
<trans-unit id="Onion hosts will be **required** for connection.&#10;Requires compatible VPN." xml:space="preserve">
<source>Onion hosts will be **required** for connection.
Requires compatible VPN.</source>
<target>接続にオニオンのホストが必要となります。
VPN を有効にする必要があります。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Onion hosts will be used when available. Requires enabling VPN." xml:space="preserve">
<source>Onion hosts will be used when available. Requires enabling VPN.</source>
<target>オニオンのホストが利用可能時に使われます。VPN を有効にする必要があります。</target>
<trans-unit id="Onion hosts will be used when available.&#10;Requires compatible VPN." xml:space="preserve">
<source>Onion hosts will be used when available.
Requires compatible VPN.</source>
<target>オニオンのホストが利用可能時に使われます。
VPN を有効にする必要があります。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Onion hosts will not be used." xml:space="preserve">
@@ -4474,10 +4538,6 @@ Error: %@</source>
<target>パスフレーズを失くさないように保管してください。失くすと変更できなくなります。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Please try later." xml:space="preserve">
<source>Please try later.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Polish interface" xml:space="preserve">
<source>Polish interface</source>
<target>ポーランド語UI</target>
@@ -4998,11 +5058,6 @@ Enable in *Network &amp; servers* settings.</source>
<target>開示する</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Revert" xml:space="preserve">
<source>Revert</source>
<target>元に戻す</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>取り消す</target>
@@ -5032,11 +5087,6 @@ Enable in *Network &amp; servers* settings.</source>
<source>SMP server</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="SMP servers" xml:space="preserve">
<source>SMP servers</source>
<target>SMPサーバ</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Safely receive files" xml:space="preserve">
<source>Safely receive files</source>
<note>No comment provided by engineer.</note>
@@ -5065,6 +5115,10 @@ Enable in *Network &amp; servers* settings.</source>
<target>保存して、グループのメンバーにに知らせる</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Save and reconnect" xml:space="preserve">
<source>Save and reconnect</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Save and update group profile" xml:space="preserve">
<source>Save and update group profile</source>
<target>グループプロファイルの保存と更新</target>
@@ -5213,6 +5267,10 @@ Enable in *Network &amp; servers* settings.</source>
<trans-unit id="Select" xml:space="preserve">
<source>Select</source>
<target>選択</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Selected %lld" xml:space="preserve">
<source>Selected %lld</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
@@ -5442,10 +5500,6 @@ Enable in *Network &amp; servers* settings.</source>
<source>Server version is incompatible with network settings.</source>
<note>srv error text</note>
</trans-unit>
<trans-unit id="Server version is incompatible with network settings: %@." xml:space="preserve">
<source>Server version is incompatible with network settings: %@.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Server version is incompatible with your app: %@." xml:space="preserve">
<source>Server version is incompatible with your app: %@.</source>
<note>No comment provided by engineer.</note>
@@ -5553,6 +5607,10 @@ Enable in *Network &amp; servers* settings.</source>
<source>Share this 1-time invite link</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Share to SimpleX" xml:space="preserve">
<source>Share to SimpleX</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Share with contacts" xml:space="preserve">
<source>Share with contacts</source>
<target>連絡先と共有する</target>
@@ -5695,11 +5753,23 @@ Enable in *Network &amp; servers* settings.</source>
<target>小グループ(最大20名)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Soft" xml:space="preserve">
<source>Soft</source>
<note>blur media</note>
</trans-unit>
<trans-unit id="Some file(s) were not exported:" xml:space="preserve">
<source>Some file(s) were not exported:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Some non-fatal errors occurred during import - you may see Chat console for more details." xml:space="preserve">
<source>Some non-fatal errors occurred during import - you may see Chat console for more details.</source>
<target>インポート中に致命的でないエラーが発生しました - 詳細はチャットコンソールを参照してください。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Some non-fatal errors occurred during import:" xml:space="preserve">
<source>Some non-fatal errors occurred during import:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Somebody" xml:space="preserve">
<source>Somebody</source>
<target>誰か</target>
@@ -5789,6 +5859,10 @@ Enable in *Network &amp; servers* settings.</source>
<source>Stopping chat</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Strong" xml:space="preserve">
<source>Strong</source>
<note>blur media</note>
</trans-unit>
<trans-unit id="Submit" xml:space="preserve">
<source>Submit</source>
<target>送信</target>
@@ -5821,6 +5895,10 @@ Enable in *Network &amp; servers* settings.</source>
<target>システム認証</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="TCP connection" xml:space="preserve">
<source>TCP connection</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="TCP connection timeout" xml:space="preserve">
<source>TCP connection timeout</source>
<target>TCP接続タイムアウト</target>
@@ -5987,6 +6065,14 @@ It can happen because of some bug or when the connection is compromised.</source
<target>メッセージは、すべてのメンバーに対してモデレートされたものとして表示されます。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The messages will be deleted for all members." xml:space="preserve">
<source>The messages will be deleted for all members.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The next generation of private messaging" xml:space="preserve">
<source>The next generation of private messaging</source>
<target>次世代のプライバシー・メッセンジャー</target>
@@ -6320,11 +6406,6 @@ To connect, please ask your contact to create another connection link and check
<target>更新</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Update .onion hosts setting?" xml:space="preserve">
<source>Update .onion hosts setting?</source>
<target>.onionのホスト設定を更新しますか?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Update database passphrase" xml:space="preserve">
<source>Update database passphrase</source>
<target>データベースのパスフレーズを更新</target>
@@ -6335,9 +6416,8 @@ To connect, please ask your contact to create another connection link and check
<target>ネットワーク設定を更新しますか?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Update transport isolation mode?" xml:space="preserve">
<source>Update transport isolation mode?</source>
<target>トランスポート隔離モードを更新しますか?</target>
<trans-unit id="Update settings?" xml:space="preserve">
<source>Update settings?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Updating settings will re-connect the client to all servers." xml:space="preserve">
@@ -6345,11 +6425,6 @@ To connect, please ask your contact to create another connection link and check
<target>設定を更新すると、全サーバにクライントの再接続が行われます。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Updating this setting will re-connect the client to all servers." xml:space="preserve">
<source>Updating this setting will re-connect the client to all servers.</source>
<target>設定を更新すると、全サーバにクライントの再接続が行われます。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Upgrade and open chat" xml:space="preserve">
<source>Upgrade and open chat</source>
<target>アップグレードしてチャットを開く</target>
@@ -6449,11 +6524,6 @@ To connect, please ask your contact to create another connection link and check
<source>User selection</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Using .onion hosts requires compatible VPN provider." xml:space="preserve">
<source>Using .onion hosts requires compatible VPN provider.</source>
<target>.onionホストを使用するには、互換性のあるVPNプロバイダーが必要です。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Using SimpleX Chat servers." xml:space="preserve">
<source>Using SimpleX Chat servers.</source>
<target>SimpleX チャット サーバーを使用する。</target>
@@ -6690,11 +6760,6 @@ To connect, please ask your contact to create another connection link and check
<source>XFTP server</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="XFTP servers" xml:space="preserve">
<source>XFTP servers</source>
<target>XFTPサーバ</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You" xml:space="preserve">
<source>You</source>
<target>あなた</target>
@@ -6893,6 +6958,14 @@ Repeat connection request?</source>
<target>グループに参加しました。招待をくれたメンバーに接続してます。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You may migrate the exported database." xml:space="preserve">
<source>You may migrate the exported database.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You may save the exported archive." xml:space="preserve">
<source>You may save the exported archive.</source>
<note>No comment provided by engineer.</note>
</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">
<source>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.</source>
<target>あなたの最新データベースを1つの端末にしか使わなければ、一部の連絡先からメッセージが届きかねます。</target>
@@ -7837,8 +7910,8 @@ last received msg: %2$@</source>
<target>不明</target>
<note>connection info</note>
</trans-unit>
<trans-unit id="unknown relays" xml:space="preserve">
<source>unknown relays</source>
<trans-unit id="unknown servers" xml:space="preserve">
<source>unknown servers</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="unknown status" xml:space="preserve">
@@ -8053,4 +8126,178 @@ last received msg: %2$@</source>
</trans-unit>
</body>
</file>
<file original="SimpleX SE/en.lproj/InfoPlist.strings" source-language="en" target-language="ja" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
</header>
<body>
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
<source>SimpleX SE</source>
<note>Bundle display name</note>
</trans-unit>
<trans-unit id="CFBundleName" xml:space="preserve">
<source>SimpleX SE</source>
<note>Bundle name</note>
</trans-unit>
<trans-unit id="NSHumanReadableCopyright" xml:space="preserve">
<source>Copyright © 2024 SimpleX Chat. All rights reserved.</source>
<note>Copyright (human-readable)</note>
</trans-unit>
</body>
</file>
<file original="SimpleX SE/en.lproj/Localizable.strings" source-language="en" target-language="ja" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
</header>
<body>
<trans-unit id="%@" xml:space="preserve">
<source>%@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="App is locked!" xml:space="preserve">
<source>App is locked!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cancel" xml:space="preserve">
<source>Cancel</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cannot access keychain to save database password" xml:space="preserve">
<source>Cannot access keychain to save database password</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cannot forward message" xml:space="preserve">
<source>Cannot forward message</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Comment" xml:space="preserve">
<source>Comment</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Currently maximum supported file size is %@." xml:space="preserve">
<source>Currently maximum supported file size is %@.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database downgrade required" xml:space="preserve">
<source>Database downgrade required</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database encrypted!" xml:space="preserve">
<source>Database encrypted!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database error" xml:space="preserve">
<source>Database error</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database passphrase is required to open chat." xml:space="preserve">
<source>Database passphrase is required to open chat.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database upgrade required" xml:space="preserve">
<source>Database upgrade required</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error preparing file" xml:space="preserve">
<source>Error preparing file</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error preparing message" xml:space="preserve">
<source>Error preparing message</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error: %@" xml:space="preserve">
<source>Error: %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="File error" xml:space="preserve">
<source>File error</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incompatible database version" xml:space="preserve">
<source>Incompatible database version</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Invalid migration confirmation" xml:space="preserve">
<source>Invalid migration confirmation</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Keychain error" xml:space="preserve">
<source>Keychain error</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Large file!" xml:space="preserve">
<source>Large file!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No active profile" xml:space="preserve">
<source>No active profile</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open the app to downgrade the database." xml:space="preserve">
<source>Open the app to downgrade the database.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open the app to upgrade the database." xml:space="preserve">
<source>Open the app to upgrade the database.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Passphrase" xml:space="preserve">
<source>Passphrase</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Please create a profile in the SimpleX app" xml:space="preserve">
<source>Please create a profile in the SimpleX app</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
<source>Selected chat preferences prohibit this message.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sending a message takes longer than expected." xml:space="preserve">
<source>Sending a message takes longer than expected.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sending message…" xml:space="preserve">
<source>Sending message…</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Share" xml:space="preserve">
<source>Share</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Slow network?" xml:space="preserve">
<source>Slow network?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unknown database error: %@" xml:space="preserve">
<source>Unknown database error: %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unsupported format" xml:space="preserve">
<source>Unsupported format</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Wait" xml:space="preserve">
<source>Wait</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Wrong database passphrase" xml:space="preserve">
<source>Wrong database passphrase</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
</body>
</file>
</xliff>
@@ -1,6 +1,9 @@
/* Bundle display name */
"CFBundleDisplayName" = "SimpleX NSE";
/* Bundle name */
"CFBundleName" = "SimpleX NSE";
/* Copyright (human-readable) */
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";
@@ -0,0 +1,7 @@
/*
Localizable.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -0,0 +1,7 @@
/*
InfoPlist.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
@@ -0,0 +1,7 @@
/*
Localizable.strings
SimpleX
Created by EP on 30/07/2024.
Copyright © 2024 SimpleX Chat. All rights reserved.
*/
File diff suppressed because it is too large Load Diff

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